GCC和ARM组件堆栈清理

GCC和ARM组件堆栈清理,c,gcc,assembly,arm,C,Gcc,Assembly,Arm,如果我从C调用ARM汇编函数,有时我需要传入许多参数。如果它们不适合寄存器r0、r1、r2、r3,则通常预期第5、6位。。。第x个参数被推送到堆栈上,以便ARM部件可以从堆栈中读取它们 因此,在ARM函数中,我接收堆栈上的一些参数。完成汇编函数后,我可以从堆栈中删除这些参数,也可以将它们留在堆栈中,并期望C程序稍后处理它们 如果我们谈论的是GCC C和ARM组件,通常谁负责清理堆栈? 进行调用的函数(A) 或调用的函数(B) 我理解,在制定公约时,我们可以就其中任何一项公约达成一致。但在这种

如果我从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,其工作原理与下面答案中的代码非常相似。更多信息可在此链接中找到。更改函数的C/C++调用约定似乎并不常见/容易。对于较旧的C标准,我无法更改它,因此看起来使用A应该是一个不错的默认选择。

如果调用方函数在堆栈上分配了一些空间(参数传递),则在调用方函数内完成堆栈清除。你可能会问它是如何发生的。在ARM中@Olaf已完全清除,在x86中通常是这样的:

sub     esp, 8      ; make some room 
...                 ; move arguments on stack
call    func
add     esp, 8      ; clean the stack


ABI(应用程序二进制接口)中还解释了系统中调用方和被调用方之间的交互方式。您可能会发现它很有用。

如果调用方函数在堆栈上分配了一些空间(参数传递),则在调用方函数内完成堆栈清除。你可能会问它是如何发生的。在ARM中@Olaf已完全清除,在x86中通常是这样的:

sub     esp, 8      ; make some room 
...                 ; move arguments on stack
call    func
add     esp, 8      ; clean the stack


ABI(应用程序二进制接口)中还解释了系统中调用方和被调用方之间的交互方式。您可能会发现它很有用。

当前的ARM过程调用标准是

可以找到特定于语言的ABI。有关C的文档将是相关的,但其他文档应该是类似的(为什么要重新发明轮子?)

阅读AAPCS的第14页可能是一个良好的开端

它基本上要求调用方清理堆栈,因为这是最简单的方法:将附加参数推送到堆栈上,调用函数,返回后通过添加偏移量(推送到堆栈上的字节数;这始终是4的倍数(“自然32位ARM字大小”)简单地调整堆栈指针

但是如果您使用gcc,您可以通过使用内联汇编程序来避免自己处理堆栈这也会在需要时自动将参数加载到寄存器中。只要看一下gcc文档就可以了。这有点难以详细理解,但我更喜欢这样,而不是在某个地方有原始的assember存根

好的,我添加了这个,因为理解这个原则可能会有问题:

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是否会使用不同的标准。请注意,所有对象文件都必须使用相同的标准

编辑: 刚刚浏览了一下AAPC,它指出SP只需要4字节对齐。我可能会将这与Cortex-M中断处理系统混淆,后者(无论出于何种原因,可能对于具有64位总线的M7)默认将SP对齐到8字节(软件配置选项)。
但是,SP在公共接口上必须是8字节对齐的。好吧,这个标准实际上比我记忆中的要复杂。这就是为什么我更喜欢gcc关心这些东西。

当前的ARM过程调用标准是

可以找到特定于语言的ABI。相关的将是关于C的文档,但其他的应该是类似的(为什么要重新发明轮子?)

阅读AAPCS的第14页可能是一个良好的开端

它基本上要求调用方清理堆栈,因为这是最简单的方法:将附加参数推送到堆栈上,调用函数,返回后通过添加偏移量(推送到堆栈上的字节数;这始终是4的倍数(“自然32位ARM字大小”)简单地调整堆栈指针

但是如果您使用gcc,您可以通过使用内联汇编程序来避免自己处理堆栈这也会在需要时自动将参数加载到寄存器中。只要看一下gcc文档就可以了。这有点难以详细理解,但我更喜欢这样,而不是在某个地方有原始的assember存根

好的,我添加了这个,因为理解这个原则可能会有问题:

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),则前置码和后置码可能会有所不同