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)的代码地址? 在所有的locals被声明之后,栈指针可能会在bar()里面的任何地方。
增加另一个函数调用baz()
一些注册调用约定依赖于ABI (应用程序二进制接口)。 FP
是APCS标准要求的FP
,而不是新的AAPCS (2003)。 对于AAPCS (GCC 5.0+) FP
不一定要用,但肯定可以; debugging信息使用堆栈和帧指针进行注释,用于堆栈跟踪和使用AAPCS展开代码。 如果一个函数是static
,编译器实际上不必遵守任何约定。
通常所有的ARM寄存器都是通用的 。 lr
(链接寄存器,也是R14)和pc
(程序计数器也是R15)是特殊的,并包含在指令集中。 你是正确的, lr
会指向A。 pc
和lr
是相关的。 一个是“你在哪里”,另一个是“你在哪里”。 它们是函数的代码方面。
通常,我们有sp
(堆栈指针,R13)和fp
( 帧指针 ,R11)。 这两个也有关系。 这个微软的布局做了很好的描述。 该堆栈用于在您的function中存储临时数据或本地数据。 foo()
和bar()
任何variables都存储在堆栈或可用寄存器中。 fp
跟踪从function到function的variables。 它是该function的堆栈或图片窗口。 ABI定义了这个框架的布局。 通常, lr
和其他寄存器在编译器的后台保存在这里,以及fp
的前一个值。 这使得一个堆栈帧的链表 ,如果你想,你可以追溯到main()
。 根是fp
,它指向一个堆栈框架(像一个struct
), struct
中的一个variables是前一个fp
。 你可以沿着列表直到最终的fp
,通常是NULL
。
所以sp
是栈的位置, fp
是栈的位置,很像pc
和lr
。 每个旧的lr
(链接寄存器)都存储在旧的fp
(帧指针)中。 sp
和fp
是函数的数据方面。
你的B点是活动pc
和sp
。 A点实际上是fp
和lr
; 除非你调用另一个函数,然后编译器可能已经准备好将fp
设置为指向B中的数据。
以下是一些ARM汇编器,可能会演示如何工作。 这取决于编译器如何优化会有所不同,但它应该给出一个想法,
; 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 returnlr
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.
这就是foo()
样子。 如果你不调用bar()
,那么编译器会进行叶子优化 ,不需要保存帧 ; 只需要bx lr
。 很可能这也许就是为什么你被networking例子弄糊涂了。 它并不总是一样的。
外卖应该是,
-
pc
和lr
是相关的代码寄存器。 一个是“你在哪里”,另一个是“你在哪里”。 -
sp
和fp
是相关的本地数据寄存器。
一个是“本地数据在哪里”,另一个是“本地数据在哪里”。 - 与parameter passing一起共同创造function机械。
- 由于我们希望编译器尽可能快 ,所以很难描述一般情况,所以他们尽可能地利用每一个技巧。
这些概念对于所有的CPU和编译语言都是通用的,尽pipe细节可能有所不同。 使用链接寄存器 , 帧指针是函数序言和结尾的一部分,如果你理解了所有的东西,你就知道堆栈溢出在ARM上是如何工作的。
另请参阅: ARM调用约定 。
MSDN ARM堆栈文章
剑桥大学APCS概述
ARM堆栈跟踪博客
苹果ABI链接
基本的框架布局是,
- fp [-0]保存了
pc
,我们在这里存储了这个帧。 - fp [-1]保存了
lr
,这个函数的返回地址。 - fp [-2]之前的
sp
,在此function之前吃堆栈。 - fp [-3]前面的
fp
,最后一个堆栈帧 。 - 许多可选寄存器…
ABI可能会使用其他值,但以上是大多数设置的典型值。
附录:这在汇编程序中不是错误的。 这是正常的。 一个解释是在ARM生成的序言问题。