如果我从C调用ARM汇编函数,有时我需要传入许多参数。如果它们不适合寄存器r0,r1,r2,r3,通常需要将第5,第6 ......第x个参数压入堆栈,以便ARM汇编可以从中读取它们。
因此在ARM函数中,我收到了堆栈中的一些参数。完成汇编函数后,我可以从堆栈中删除这些参数或将它们保留在那里,并期望C程序稍后处理它们。
如果我们谈论通常负责清理堆栈的GCC C和ARM程序集?
据我所知,在开发时我们可以同意这两种惯例。但是在这种特殊情况下(ARM汇编和GCC C)通常用作默认值?
一般来说,低级别的代码如何描述它实现的行为?似乎应该有一些标准的描述。如果没有,你似乎只需要尝试它们并看看哪一个不会崩溃。
如果有人对代码的外观感兴趣:
arm_function:
stmfd sp, {r4-r12, lr} # Save registers that are not the first three registers, SP->PASSED ARGUMENTS
ldmfd sp, {r4-r6} # Load 3 arguments that were passed through the stack, SP->PASSED ARGUMENTS
sub sp, sp, #40 # Adjust the stack pointer so it points to saved registers, STACK POINTER->SAVED REGISTERS->PASSED ARGUMENTS
#The main function body.
ldmfd sp!, {r4-r12, lr}, # Load saved registers STACK POINTER->PASSED ARGUMENTS
add sp, sp, #12 # Increment stack pointer to remove passed arguments, SP->NOTHING
# If the last code line would not be there, the caller would need to remove the arguments from stack.
更新 似乎对于C / C ++,选择A.是非常标准的。编译器通常使用像 cdecl 这样的调用约定,它们与下面的答案中的代码非常相似。可在此链接about calling conventions中找到更多信息。更改函数的C / C ++调用约定似乎并不那么常见/容易。使用较旧的C标准,我无法改变它,所以看起来使用A应该是一个不错的默认选择。
答案 0 :(得分:3)
当前的ARM过程调用标准为AAPCS。
可以找到特定语言的ABI here。相关的是关于C的文件,但其他的应该是相似的(为什么重新发明轮子?)。
阅读的良好开端可能是AAPCS中的第14页。
它基本上需要调用者清理堆栈,因为这是最简单的方法:将附加参数压入堆栈,调用函数并在返回后只需通过添加偏移量调整堆栈指针(推送的字节数)在堆栈上;这总是4的倍数("自然的32位ARM字大小)。
但是如果你使用gcc,你可以通过使用内联汇编程序来避免自己处理堆栈。这提供了将C变量(等)传递给汇编代码的功能。如果需要,这还会自动将参数加载到寄存器中。看看gcc文档吧。有点难以详细说明,但我更喜欢在某处使用原始的存根存根。
好的,我补充说,因为理解这个原则可能有问题:
caller:
...
push r5 // argument which does not fit into r0..r3 anymore
bl callee
add sp,4 // adjust SP
callee:
push r5-r7,lr // temp, variables, return address
sub sp,8 // local variables
// processing
add sp, 8 // restore previous stack frame
pop r5-r7,pc // restore temp. variables and return (replaces bx)
您可以通过取消一些示例C函数来验证这一点。请注意,如果没有使用临时寄存器或函数不调用另一个函数(不需要为此堆栈lr),则前置和后置可能会有所不同。
此外,呼叫者可能必须在呼叫前堆叠r0..r3。但这是编译器优化的问题。
例如,可以使用gdb和objdump进行反汇编。
我使用-mabi=aapcs
进行gcc调用;不确定gcc否则会使用不同的标准。请注意,所有目标文件都必须使用相同的标准。
修改强> 刚刚看了AAPCS,并指出SP只需要4字节对齐。我可能会将这与Cortex-M中断处理系统混淆,后者(无论出于何种原因,可能对于具有64位总线的M7)默认情况下将SP对齐为8个字节(software-config选项)。 但是,SP必须在公共接口处对齐8字节。好吧,标准 比我记忆中的更复杂。这就是为什么我更喜欢gcc关心这些东西的原因。
答案 1 :(得分:2)
如果通过调用函数(参数传递)在堆栈上分配了一些空格,则在调用函数内完成堆栈清除。你可能会问,它是如何发生的。在ARM中@Olaf已经完全清除了,在x86中它通常是这样的:
sub esp, 8 ; make some room
... ; move arguments on stack
call func
add esp, 8 ; clean the stack
或
push eax ; push the arguments
push ebx ; or pusha, then after call, popa
call func
add esp, 8 ; assuming registers are 4 bytes each
ABI(应用程序二进制接口)中解释了如何在系统中调用者和被调用者之间的交互。您可能会发现它很有用。