我正在Android设备上编写针对ARM Cortex-A的代码(使用GNU汇编器和编译器),并且我正在尝试在汇编和C之间进行接口。特别是,我对从汇编调用用C编写的函数感兴趣。我尝试了很多东西,包括.extern
指令,用asm
和__asm__
等声明C函数,但它们都不起作用,所以我正在寻找这样做的最小示例。我也很乐意看到这样的例子。
你需要读取ARM和/或知道指令集是全部,通常你会想做这样的事情
asm:
bl cfun
c:
void cfun ( void )
{
}
你可以自己试试。对于gnu as和GCC,这工作得很好,如果您使用clang将c代码获取到对象和gnu as用于汇编器,它也应该工作得很好。不知道你在用什么
上面的问题是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世界(例如使用bx lr而不是mov pc,lr),那么你会想使用bx
add lr,pc,#4
ldr r1,cfun_addr
bx r1
...
cfun_addr:
.word cfun
当然,你需要另一个寄存器来做到这一点,记住在调用C之前和之后push和pop你的链接寄存器和另一个寄存器,如果你想保留它们
最小可运行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代码的汇编输出,以了解语法等。当尝试调用共享库中的函数或可重定位对象时,情况会更加复杂。
正如Brett所说,您真正要做的就是将正确的值放入正确的寄存器中,并将branch-with-link链接到函数的地址。您需要知道编译函数将覆盖哪些寄存器,以及在返回之前将恢复哪些寄存器——这些都写在infocentre.arm.com的ABI文档中。您还需要确保堆栈寄存器设置为编译器所期望的,也可能设置为其他寄存器(对于PIC模式?)
但是,你真的需要在汇编文件中编写代码吗?
如果你使用GCC的"asm"特性,那么你可以将汇编器片段(只要你喜欢)嵌入到常规的C函数中,并在更方便的时候返回C。
在某些情况下,使用C语言是不行的,但如果你可以调用C函数,我猜你不在这些情况下。
说到这里,你到底为什么需要使用汇编程序....C基本上是高级汇编?