我正在 Android 设备上编写针对 ARM Cortex-A 的代码(使用 GNU 汇编器和编译器),并且我正在尝试在 Assembly 和 C 之间建立接口。特别是,我对从 Assembly 调用用 C 编写的函数感兴趣。我尝试了很多东西,包括
.extern
指令、用 asm
和 __asm__
声明 C 函数等等,但它们都不起作用,所以我正在寻找这样做的最小示例。同样欢迎引用此类示例。
你需要阅读ARM 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.
所以如果你让你的汇编看起来像这样:
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 位地址。 链接器稍后将用外部地址填充该地址。 这样你就可以到达地址空间中的任何地址。
如果您不想玩那样的汇编游戏并希望掌控一切,那么您可以创建一个位置来保存函数的地址并自己将其加载到电脑中:
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 之前和之后推送和弹出您的链接寄存器和另一个寄存器。
最小可运行的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-assemble-cheat/blob/82e915e1dfaebb80683a4fd7bba57b0aa99fda7f/c_from_arm.S
您需要
armeabi-v7a
的规范,描述调用堆栈、寄存器(被调用者与调用者)等。然后查看编译的 C 代码的汇编输出的语法等。当尝试在共享库或可重定位对象。
如果这是针对 MacOS(M1 等)上的 ARM64,据我所知,您需要使用前导
_
来声明您的 extern。例如:
.global _asm_function
.extern _malloc
_asm_function:
mov x0, #16
bl _malloc
注意
malloc()
被声明为_malloc
,而不是malloc
。
#include <stdlib.h>
void* asm_function();
int main() {
free(asm_function());
}
编译并链接类似:
$ as -o asm.o asm.s
$ clang -o main main.c asm.o
正如 Brett 所说,您真正要做的就是将正确的值放入正确的寄存器中,然后使用链接分支到函数的地址。您需要了解编译函数将覆盖哪些寄存器,以及它将在返回之前恢复哪些寄存器——这些都写在 infocentre.arm.com 的 ABI 文档中。您还需要确保堆栈寄存器设置为编译器期望的值,也许还有其他寄存器(对于 PIC 模式?)
但是,你真的需要在汇编文件中编写代码吗?
如果您使用 GCC“asm”功能,那么您可以将汇编器片段(只要您愿意)嵌入到常规 C 函数中,并在更方便的时候再返回到 C 中。
在某些情况下,使用 C gubbins 是行不通的,但如果你可以调用 C 函数,我猜你不属于这些情况。
说到这里,为什么你需要使用汇编程序......C 基本上是高级汇编程序?