如何从ARM组件调用C函数?
我正在编写针对Android设备上的ARM Cortex-A的代码(使用GNU汇编程序和编译器),并尝试在汇编和C之间进行接口。特别是,我对从汇编调用用C编写的函数感兴趣。我尝试了很多方法,包括如何从ARM组件调用C函数?,c,assembly,arm,gnu-assembler,cortex-a8,C,Assembly,Arm,Gnu Assembler,Cortex A8,我正在编写针对Android设备上的ARM Cortex-A的代码(使用GNU汇编程序和编译器),并尝试在汇编和C之间进行接口。特别是,我对从汇编调用用C编写的函数感兴趣。我尝试了很多方法,包括.extern指令,用asm和\uuuuuasm\uuuuu声明C函数等等,但都不起作用,所以我正在寻找一个这样做的最小示例。同样欢迎参考此类示例。您需要armeabi-v7a的规范,描述调用堆栈、寄存器(被调用方与调用方)等。然后查看编译的C代码的汇编输出,了解语法,等等。当试图调用共享库或可重定位对象
.extern
指令,用asm
和\uuuuuasm\uuuuu
声明C函数等等,但都不起作用,所以我正在寻找一个这样做的最小示例。同样欢迎参考此类示例。您需要armeabi-v7a的规范,描述调用堆栈、寄存器(被调用方与调用方)等。然后查看编译的C代码的汇编输出,了解语法,等等。当试图调用共享库或可重定位对象中的函数时,事情就更复杂了。正如Brett所说,您真正需要做的就是将正确的值放入正确的寄存器中,并通过指向函数地址的链接进行分支。您需要知道编译后的函数将覆盖哪些寄存器,以及在返回之前将恢复哪些寄存器——这些都写在infocenter.arm.com上的ABI文档中。您还需要确保堆栈寄存器设置为编译器期望的值,也可能设置其他寄存器(对于PIC模式?)
但是,您真的需要在汇编文件中编写代码吗
如果您使用GCC的“asm”特性,那么您可以将汇编程序片段(只要您愿意)嵌入到常规的C函数中,并在更方便的时候返回到C中
在有些情况下,有C gubbins是不行的,但是如果你能调用C函数,我猜你不在其中
说到这里,为什么你需要使用汇编程序呢。。。。无论如何,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)
因此,如果你让你的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
当然,您需要另一个寄存器来执行此操作,如果您想保留链接寄存器和另一个寄存器,请记住在调用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上的示例和通用的样板:@user606723我考虑过这一点,但C使用带有函数声明的头文件在不同文件之间进行通信。这不是完全不同的情况吗?编译器查看头文件中函数的声明来编译调用。您需要手动执行同样的操作。例如,如果您还不知道调用约定以了解用于传递参数的寄存器,请执行以下两种操作之一,编写一些C代码来调用您的函数,然后反汇编以查看编译器执行了什么操作。正确的答案是查找其他人给你指出的调用约定。你是对的,我只演示了如何让ARM指令调用函数,而不是让thumb指令调用C函数,我可以编辑答案,并向你演示如何做……你提到了cortex a,所以我假设你正在运行ARM指令绝大多数情况下都不是拇指,利用你的马力,而不是一些总线限制的东西。正如Brett提到的可重新定位对象可能会使其变得更复杂,你需要使cfun_addr全局化,任何加载可重新定位对象的人都需要在使用它之前修改cfun_addr。尊重老定时器!哇,那是不受欢迎的。。。但没人说为什么?OP不是问如何输出hello world。他想调用一个
C
函数。@Samhammay不是put
C函数吗?:-)
push {ip, lr}
push {lr}