ARM:链接寄存器和帧指针

时间:2013-04-01 21:26:59

标签: c arm

我试图理解链接寄存器和帧指针在ARM中是如何工作的。我去过几个网站,我想证实我的理解。

假设我有以下代码:

int foo(void)
{
    // ..
    bar();
    // (A)
    // ..
}

int bar(void)
{
    // (B)
    int b1;
    // ..
    // (C)
    baz();
    // (D)
}

int baz(void)
{
    // (E)
    int a;
    int b;
    // (F)
}

我打电话给foo()。链接寄存器是否包含点(A)处代码的地址,帧指针是否包含代码(B)处的地址?在声明了所有本地语之后,堆栈指针可以是bar()内的任何位置吗?

[edit]添加了另一个函数调用baz()

2 个答案:

答案 0 :(得分:67)

某些寄存器调用约定依赖于ABI(应用程序二进制接口)。 APCS 标准中需要FP,而较新的 AAPCS (2003)则不需要FP。对于 AAPCS (GCC 5.0+),static不会使用,但肯定可以;调试信息使用堆栈和帧指针进行注释,用于堆栈跟踪和使用 AAPCS 展开代码。如果函数是lr,则编译器实际上不必遵守任何约定。

通常所有ARM寄存器都是通用pc(链接寄存器,也是R14)和lr(程序计数器也是R15)是特殊的,并且在指令集中包含。你是正确的pc指向 A lrsp是相关的。一个是“你在哪里”,另一个是“你在哪里”。它们是函数的代码方面。

通常,我们有fp(堆栈指针,R13)和foo()frame pointer,R11)。这两者也有关系。这个 Microsoft layout做得很好。 堆栈用于在您的函数中存储临时数据或 locals bar()fp中的任何变量都存储在此处,位于堆栈或可用寄存器中。 lr跟踪从函数到函数的变量。它是该功能的堆栈上的或图片窗口。 ABI 定义了此框架的布局。通常,fp和其他寄存器由编译器保存在幕后,以及main()的先前值。这会生成堆栈帧的链接列表,如果需要,您可以将其追溯到fp root struct,它指向一个堆栈帧(如struct),fp中的一个变量是前一个fp。您可以查看列表,直至最终NULL,通常为sp

所以fp是堆栈的位置,pc是堆栈的位置,很像lrlr。每个旧fp(链接寄存器)都存储在旧sp(帧指针)中。 fppc是函数的数据方面。

您的观点 B 是有效spfp。点 A 实际上是lrfp;除非你调用另一个函数,然后编译器可能已准备好设置; Prologue - setup mov ip, sp ; get a copy of sp. stmdb sp!, {fp, ip, lr, pc} ; Save the frame on the stack. See Addendum sub fp, ip, #4 ; Set the new frame pointer. ... ; Maybe other functions called here.
; Older caller return lr stored in stack frame. bl baz ... ; Epilogue - return ldm sp, {fp, sp, lr} ; restore stack, frame pointer and old link. ... ; maybe more stuff here. bx lr ; return.
以指向 B 中的数据。

以下是一些ARM汇编程序,可能会演示这一切是如何工作的。这将根据编译器的优化方式而有所不同,但它应该给出一个想法,

lr
这就是foo()的样子。如果您不调用bar(),则编译器会执行叶优化,而不需要保存框架;只需要bx lr。最有可能的原因可能是您对Web示例感到困惑。它并不总是一样的。

外卖应该是,

  1. pclr是相关的代码寄存器。一个是“你在哪里”,另一个是“你在哪里”。
  2. spfp是相关的本地数据寄存器。
    一个是“本地数据在哪里”,另一个是“最后一个本地数据在哪里” ”
  3. parameter passing一起工作以创建功能机器。
  4. 很难描述一般情况,因为我们希望编译器尽可能快速,因此他们会尽可能地使用它们。
  5. 这些概念对所有CPU和编译语言都是通用的,但细节可能有所不同。使用链接寄存器帧指针function prologue和结尾的一部分,如果您了解了所有内容,就会知道堆栈是如何溢出的适用于ARM。

    另请参阅:ARM calling convention MSDN ARM stack article
    University of Cambridge APCS overview
    ARM stack trace blog
    Apple ABI link

    基本框架布局是,

    • fp [-0]已保存pc,我们存储了此框架。
    • fp [-1]保存lr,此函数的返回地址。
    • fp [-2]之前的sp,此函数堆栈之前。
    • fp [-3]之前的fp,最后一个堆栈框架
    • 许多可选寄存器......

    ABI 可能会使用其他值,但上述内容对于大多数设置都是典型的。

    附录:这不是汇编程序中的错误;这是正常的。 ARM generated prologs问题中有一个解释。

答案 1 :(得分:0)

免责声明:我认为这是大致正确的;请根据需要进行更正。

如本问答中其他地方所示,请注意,可能不需要编译器生成使用帧指针的(ABI)代码。调用堆栈中的帧通常可能需要将无用的信息放在那里。

如果编译器选项要求“无帧”(伪选项标志),则编译器可以生成较小的代码,从而使调用堆栈数据较小。调用函数被编译为仅将所需的调用信息存储在堆栈中,而被调用函数被编译为仅从堆栈中弹出所需的调用信息。

这节省了执行时间和堆栈空间-但使调用代码中的向后跟踪变得异常困难(我放弃了尝试...)

有关堆栈上调用信息的大小和形状的信息仅由编译器知道,并且该信息在编译后被丢弃。