arm64 實現(xiàn)閉包轉C函數
調用約定
arm64的C調用約定如下:
首八個不超過8bytes的非浮點數使用r0-r7寄存器
首八個浮點數使用d0-d7寄存器
大于八個參數則使用棧傳遞
結構體分為三種情況
不超過8bytes,那么此結構體直接使用上述規(guī)則傳遞
超過8bytes但不超過16bytes,那么此結構體使用兩個64bits寄存器傳輸
超過16bytes,傳遞結構體指針
返回值若不超過8bytes,則使用r0或d0傳遞,否則調用者使用x8傳遞接受對象的指針,被調用者將值寫入指針
實現(xiàn)
下面將使用Rust語言實現(xiàn)少于七個參數無浮點的閉包轉換
閉包是一個含有狀態(tài)的“函數”,使用起來十分方便,但是要使用一些C函數回調的時候就很不友好了
下面我們來“改造”閉包
一個閉包可以當作一個結構體,他當然也是有地址的
于是對于一個閉包
Fn(T) -> R
我們可以改造為
extern "C" fn(*const?(), T) -> R
要實現(xiàn)這點也不難,只需要將寄存器一一往后排,再將x0寄存器賦值對應指針
難點是如何生成這樣的機器碼
生成機器碼
通過上面的分析和查閱資料,找到我們需要使用的指令

BR
我們先來反匯編分析C函數的跳轉


可見其跳轉使用了BLR指令,那么為什么我們使用BR指令進行跳轉呢?
因為我們不需要“返回”
BLR指令的含義為:存儲返回地址到LR(x30)寄存器
RET指令的含義為:使用LR寄存器進行返回 (return)
那么使用BR指令直接跳轉即可,不需要動LR寄存器

MOVK
MOVK指令可以將一個16位立即數偏移存入指定寄存器,并且保留寄存器其他位的數值(即MOV'Keep')
那么我們可以硬編碼一個指針進入寄存器,以實現(xiàn)尋址,即:
MOVK x0, part0, LSL 0
MOVK x0, part1, LSL 16
MOVK x0, part2, LSL 32
MOVK x0, part3, LSL 48
使用四條指令即可存儲一個指針進入寄存器

MOV (register)
我們需要移動寄存器,那么是肯定需要使用MOV指令的了

機器碼生成
接下來就是生成機器碼,機器碼的詳細定義請參閱Arm手冊(見文末)
通過閱讀arm手冊我們得到這幾個指令的機器碼生成函數






然后就是期待已久的機器碼生成環(huán)節(jié)~
首先我們定義參數

根據參數列表生成對應的機器碼
規(guī)則如下:
如果小于32bits,則使用Wn傳遞
否則使用Xn傳遞
第一個參數為x0, 將其轉移至x1
第二個參數為x1, 將其轉移至x2
...?以此類推
然后將參數指針傳入x0, 函數指針傳入Xn (注意不可占用x8)
那么對于參數列表,我們可以做如下工作

遍歷參數列表,對寄存器數字遞增
然后將生成的機器碼反轉
第二個參數為x1, 將其轉移至x2
第一個參數為x0, 將其轉移至x1
(防止破壞參數)


最后存入參數指針和函數指針


那么到這里,機器碼的生成就完畢了,接下來就是將其寫入可執(zhí)行內存并嘗試執(zhí)行,此內容詳見文末(VirtualAlloc / mmap)
接下來就是執(zhí)行測試!



完美通過!

本文源碼:https://github.com/LaoLittle/binary-learn
Armv8 手冊:https://developer.arm.com/documentation/ddi0487/fc/
VirtualAlloc / VirtualProtect?/?VirtualFree?:??https://learn.microsoft.com/en-us/windows/win32/api/memoryapi
mmap / munmap / mprotect :?https://linux.die.net/man/2