我正在 Android 设备上编写针对 ARM Cortex-A 的代码(使用 GNU 汇编器和编译器),并且我正在尝试在 Assembly 和 C 之间进行接口。特别是,我对从 Assembly 调用用 C 编写的函数感兴趣。我尝试了很多东西,包括.extern
指令、用asm
等声明 C 函数__asm__
,但它们都不起作用,所以我正在寻找一个最小的例子。对此类示例的引用同样受欢迎。
4 回答
你需要阅读 ARM ARM 和/或知道指令集是全部,通常你会想做这样的事情
asm:
bl cfun
c:
void cfun ( void )
{
}
你可以自己试试这个。对于 gnu as 和 gcc 这工作得很好,如果你使用 clang 将 c 代码获取到一个对象和 gnu 作为汇编程序,它也应该工作得很好。不确定您使用的是什么。
上述问题是 bl 范围有限,
if ConditionPassed(cond) then
if L == 1 then
LR = address of the instruction after the branch instruction
PC = PC + (SignExtend_30(signed_immed_24) << 2)
知道 bl 指令将链接寄存器设置为 bl 指令之后的指令,那么如果您阅读了程序计数器寄存器:
For an ARM instruction, the value read is the address of the instruction
plus 8 bytes. Bits [1:0] of this
value are always zero, because ARM instructions are always word-aligned.
所以如果你让你的 asm 看起来像这样:
mov lr,pc
ldr pc,=cfun
你得到
d6008034: e1a0e00f mov lr, pc
d6008038: e51ff000 ldr pc, [pc, #-0] ; d6008040
...
d6008040: d60084c4 strle r8, [r0], -r4, asr #9
汇编器将在 ldr pc 指令的范围内保留一个内存位置(如果可能,否则会产生错误),它将放置指令的完整 32 位地址。链接器稍后将使用外部地址填充此地址。这样您就可以到达地址空间中的任何地址。
如果你不想玩这样的汇编游戏并且想要控制,那么你创建一个位置来保存函数的地址并将它自己加载到 pc 中:
mov lr,pc
ldr pc,cfun_addr
...
cfun_addr:
.word cfun
编译:
d6008034: e1a0e00f mov lr, pc
d6008038: e51ff000 ldr pc, [pc, #-0] ; d6008040 <cfun_addr>
...
d6008040 <cfun_addr>:
d6008040: d60084c4 strle r8, [r0], -r4, asr #9
最后,如果您想进入现代 ARM 世界,其中 ARM 和 thumb 混合使用或可以混合使用(例如使用 bx lr 而不是 mov pc,lr),那么您将需要使用 bx
add lr,pc,#4
ldr r1,cfun_addr
bx r1
...
cfun_addr:
.word cfun
当然,您需要另一个寄存器来执行此操作,并且如果您想保留它们,请记住在调用 C 之前和之后推送和弹出您的链接寄存器和另一个寄存器。
最小可运行 armv7 示例
这个问题归结为“什么是ARM调用约定(AAPCS)”。一个例子a.S
:
/* Make the glibc symbols visible. */
.extern exit, puts
.data
msg: .asciz "hello world"
.text
.global main
main:
/* r0 is the first argument. */
ldr r0, =msg
bl puts
mov r0, #0
bl exit
然后在 Ubuntu 16.04 上:
sudo apt-get install gcc-arm-linux-gnueabihf qemu-user-static
# Using GCC here instead of as + ld without arguments is needed
# because GCC knows where the C standard library is.
arm-linux-gnueabihf-gcc -o a.out a.S
qemu-arm-static -L /usr/arm-linux-gnueabihf a.out
输出:
hello world
在更复杂的示例中最容易犯的错误是忘记堆栈必须是 8 字节对齐的。例如,你想要:
push {ip, lr}
代替:
push {lr}
GitHub 上通用样板的示例:https ://github.com/cirosantilli/arm-assembly-cheat/blob/82e915e1dfaebb80683a4fd7bba57b0aa99fda7f/c_from_arm.S
您需要armeabi-v7a
描述调用堆栈、寄存器(被调用者与调用者)等的规范。然后查看编译后的 C 代码的汇编输出以了解语法等。尝试调用共享库或可重定位函数中的函数时,事情会更加复杂对象。
正如布雷特所说,您真正需要做的就是将正确的值放入正确的寄存器中,并通过链接分支到函数的地址。您需要知道编译后的函数将覆盖哪些寄存器,以及在返回之前将恢复哪些寄存器——这些都写在 infocentre.arm.com 上的 ABI 文档中。您还需要确保堆栈寄存器设置为编译器所期望的,也许还有其他寄存器(对于 PIC 模式?)
但是,您真的需要在汇编文件中编写代码吗?
如果您使用 GCC “asm” 功能,那么您可以将汇编程序片段(只要您喜欢)嵌入到常规 C 函数中,并在更方便时退回到 C 中。
在某些情况下,周围有 C gubbins 是行不通的,但如果你可以调用 C 函数,我猜你不在其中。
说到这里,你为什么需要使用汇编程序......无论如何,C 基本上是高级汇编程序?