如何从ARM程序集调用C函数



我正在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基本上是高级汇编?

最新更新