欢迎光临散文网 会员登陆 & 注册

arm64 实现闭包转C函数

2023-03-08 11:26 作者:你敢敢我就我敢  | 我要投稿

调用约定

arm64的C调用约定如下:

首八个不超过8bytes的非浮点数使用r0-r7寄存器

首八个浮点数使用d0-d7寄存器

大于八个参数则使用栈传递

结构体分为三种情况

  1. 不超过8bytes,那么此结构体直接使用上述规则传递

  2. 超过8bytes但不超过16bytes,那么此结构体使用两个64bits寄存器传输

  3. 超过16bytes,传递结构体指针


返回值若不超过8bytes,则使用r0或d0传递,否则调用者使用x8传递接受对象的指针,被调用者将值写入指针

实现

下面将使用Rust语言实现少于七个参数无浮点的闭包转换

闭包是一个含有状态的“函数”,使用起来十分方便,但是要使用一些C函数回调的时候就很不友好了

下面我们来“改造”闭包

一个闭包可以当作一个结构体,他当然也是有地址的

于是对于一个闭包

Fn(T) -> R

我们可以改造为

extern "C" fn(*const (), T) -> R

要实现这点也不难,只需要将寄存器一一往后排,再将x0寄存器赋值对应指针

难点是如何生成这样的机器码

生成机器码

通过上面的分析和查阅资料,找到我们需要使用的指令

BR

我们先来反汇编分析C函数的跳转

C
ASM

可见其跳转使用了BLR指令,那么为什么我们使用BR指令进行跳转呢?

因为我们不需要“返回”

BLR指令的含义为:存储返回地址到LR(x30)寄存器

RET指令的含义为:使用LR寄存器进行返回 (return)

那么使用BR指令直接跳转即可,不需要动LR寄存器

MOVK

MOVK指令可以将一个16位立即数偏移存入指定寄存器,并且保留寄存器其他位的数值(即MOV'Keep')

那么我们可以硬编码一个指针进入寄存器,以实现寻址,即:

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手册我们得到这几个指令的机器码生成函数

然后就是期待已久的机器码生成环节~

首先我们定义参数

根据参数列表生成对应的机器码

规则如下:

如果小于32bits,则使用Wn传递

否则使用Xn传递

第一个参数为x0, 将其转移至x1

第二个参数为x1, 将其转移至x2

... 以此类推

然后将参数指针传入x0, 函数指针传入Xn (注意不可占用x8)

那么对于参数列表,我们可以做如下工作

遍历参数列表,对寄存器数字递增

然后将生成的机器码反转

第二个参数为x1, 将其转移至x2

第一个参数为x0, 将其转移至x1

(防止破坏参数)

最后存入参数指针和函数指针

小端序

那么到这里,机器码的生成就完毕了,接下来就是将其写入可执行内存并尝试执行,此内容详见文末(VirtualAlloc / mmap)

接下来就是执行测试!

完美通过!

本文源码: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

arm64 实现闭包转C函数的评论 (共 条)

分享到微博请遵守国家法律