GCC C和ARM组装堆栈清理

时间:2015-05-08 23:29:41

标签: c gcc assembly arm

如果我从C调用ARM汇编函数,有时我需要传入许多参数。如果它们不适合寄存器r0,r1,r2,r3,通常需要将第5,第6 ......第x个参数压入堆栈,以便ARM汇编可以从中读取它们。

因此在ARM函数中,我收到了堆栈中的一些参数。完成汇编函数后,我可以从堆栈中删除这些参数或将它们保留在那里,并期望C程序稍后处理它们。

如果我们谈论通常负责清理堆栈的GCC C和ARM程序集?

  • 拨打电话的功能(A)
  • 或者被调用的函数(B)

据我所知,在开发时我们可以同意这两种惯例。但是在这种特殊情况下(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应该是一个不错的默认选择。

2 个答案:

答案 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(应用程序二进制接口)中解释了如何在系统中调用者和被调用者之间的交互。您可能会发现它很有用。