linux 根据sp和fp查调用栈

本文介绍了在Linux系统中,如何利用函数入口保存的SP(堆栈指针)和FP(帧指针)来追溯函数调用关系。通过FP和SP可以一级级回溯调用栈,LR寄存器保存了当前函数的返回地址,PC-8指向了上级函数的入口。文章还展示了具体的内存栈帧布局示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ARM寄存器的别名

寄存器名字

Reg#

APCS

意义

R0

a1

工作寄存器

R1

a2

"

R2

a3

"

R3

a4

"

R4

v1

必须保护

R5

v2

"

R6

v3

"

R7

v4

"

R8

v5

"

R9

v6

"

R10

sl

栈限制

R11

fp

桢指针

R12

ip

内部过程调用寄存器

R13

sp

栈指针

R14

lr

连接寄存器

R15

pc

程序计数器

linux中,函数的入口都会保存{fp,sp,lr,pc},比如:

80b25a34 <_raw_spin_lock>:
80b25a34:       e1a0c00d        mov     ip, sp
80b25a38:       e92dd800        push    {fp, ip, lr, pc}
80b25a3c:       e24cb004        sub     fp, ip, #4

 

sp是上级函数即调用者的堆栈首地址,fp是上级函数的堆栈结束地址,所以根据sp和fp就可以一级一级得出调用关系。

LR是当前函数的返回地址,地址位于上级函数中。PC-8即为上级函数的入口。

 

 

 下面是一个例子,当前CPU寄存器的SP为80f53c38,FP为80f53c44,所以这四个word即为当前函数的栈帧。蓝色部分为返回地址。

S:0x80F53C30:   0x80F53D24   0x80062704    0x80F53C7C(FP)   0x80F53C48(SP)

S:0x80F53C40:  0x80062704(LR)   0x80B25A40(PC)   0x00B40000   0x80F50900

S:0x80F53C50:   0x80F53C84   0x9149BE04   0x80052264    0x00000000

S:0x80F53C60:   0x96D73F08   0x00000000    0x00000000    0x00000003

S:0x80F53C70:   0x80F53C8C  0x80F53C80   0x8006298C   0x8006268C

S:0x80F53C80:   0x80F53CA4  0x80F53C90   0x80052280    0x8006297C

S:0x80F53C90:   0x00000000    0x96D73EFC  0x80F53CDC  0x80F53CA8

S:0x80F53CA0:  0x8005BC38 0x80052270    0x81A88900   0x00000001

S:0x80F53CB0:  0x60070193    0x96D73F04   0x600F0193   0x00000000

S:0x80F53CC0:  0x00000001    0x00000003    0x81097384    0x00000000

S:0x80F53CD0:  0x80F53D0C  0x80F53CE0  0x8005D9A0 0x8005BBE8

S:0x80F53CE0:     0x00000000    0x8003D108   0x810A5BC0  0x96D73F00

S:0x80F53CF0:   0x8C251088   0x00000000    0xFFFFFFFF  0x96D73EC0

S:0x80F53D00:   0x80F53D24   0x80F53D10   0x808A8D54 0x8005D964

S:0x80F53D10:   0x8C2AB840  0x96CB1200   0x80F53D3C  0x80F53D28

S:0x80F53D20:   0x808B61B4   0x808A8D04  0x96D73880   0x96D73EC0

S:0x80F53D30:   0x80F53D5C  0x80F53D40   0x808C23EC 0x808B614C

S:0x80F53D40:   0x96D73D00  0x8C2C05AC 0x00000000    0xFFFFFFFF

S:0x80F53D50:   0x80F53D84   0x80F53D60   0x808C2C14  0x808C22C4

S:0x80F53D60:   0xBEA96080  0x81267DB0  0x00000001    0x8C2C065C

S:0x80F53D70:   0x00000001    0x81098D60   0x80F53DD4  0x80F53D88

S:0x80F53D80:   0x808BAD14   0x808C2AD4  0x80077780    0xC6B332E0

S:0x80F53D90:   0x80F53DDC 0x00005180    0x8C2C0400   0x00000000

 

在内核的dump_stack中,也是这样来得到调用栈的。

  4 struct stackframe {
  5         unsigned long fp;
  6         unsigned long sp;
  7         unsigned long lr;
  8         unsigned long pc;
  9 };

 

在内核的dump_stack中,也是这样来得到调用栈的。

  4 struct stackframe {
  5         unsigned long fp;
  6         unsigned long sp;
  7         unsigned long lr;
  8         unsigned long pc;
  9 };

 

<think>我们正在讨论ARM架构下函数调用栈的工作过程。根据ARM架构的特点,函数调用栈是通过栈指针(SP)链接寄存器(LR)来管理的。当一个函数调用另一个函数时,它会将返回地址存入LR,同时调整栈指针来为局部变量保存的寄存器分配空间。 在ARM中,函数调用遵循一定的约定,称为AAPCS(ARM Architecture Procedure Call Standard)。根据这个标准,函数调用时,参数通过寄存器R0-R3传递,如果参数多于4个,则多余的参数通过栈传递。函数返回时,返回值通常存放在R0中。 函数调用栈的关键操作包括: 1. 调用函数时(使用BL指令),将返回地址(当前PC+4)存入LR(R14)。 2. 被调用函数负责保存它将要修改的寄存器(包括LR,因为LR在函数执行过程中可能会被覆盖),以及为局部变量分配栈空间。 3. 被调用函数通常以“POP {PC}”或类似的指令返回,这将从栈中恢复LR到PC,实现返回。 具体步骤: a) 函数入口(prologue): - 将LR需要保存的寄存器压入栈(通常使用PUSH指令)。 - 调整栈指针SP)为局部变量分配空间(如果需要)。 b) 函数体执行。 c) 函数出口(epilogue): - 释放局部变量占用的栈空间(如果需要)。 - 从栈中恢复保存的寄存器,包括将返回地址从栈弹出到PC(通常使用POP指令,或者直接恢复LR然后使用MOV PC, LR)。 需要注意的是,在ARM中,栈通常是从高地址向低地址增长的(满递减栈)。 下面我们用一个简单的例子来说明: 假设有两个函数:func1func2,func1调用func2。 func1的代码片段: ... BL func2 ; 调用func2,LR设置为返回地址(下一条指令地址) ... ; 返回点 func2的代码片段(假设需要保存R4-R6LR): PUSH {R4-R6, LR} ; 保存寄存器,包括LR SUB SP, SP, #8 ; 为局部变量分配8字节空间 ... ; func2的函数体 ADD SP, SP, #8 ; 释放局部变量空间 POP {R4-R6, PC} ; 恢复寄存器,并将LR的值弹出到PC,实现返回 注意:在POP指令中,我们将LR弹出到PC,这样函数就直接返回到调用处。 另外,在ARM Thumb模式下,POP指令可以直接弹出到PC,这是常见的返回方式。 引用[1]中提到了ARM架构的机器描述结构,其中包含了架构编号、名称、设备树兼容字符串等信息,虽然不直接涉及调用栈,但说明ARM架构有不同的变体,而调用栈机制在核心上是一致的。 引用[2]提到主要针对x86/ARM Linux二进制文件,说明我们的讨论同样适用于ARM Linux环境。 引用[3][4]与当前问题关联不大,因此不参考。 因此,ARM架构下函数调用栈的工作过程可以总结为: - 使用栈来保存返回地址寄存器的值。 - 遵循标准的调用约定(AAPCS)。 - 通过BL指令调用函数并设置LR,通过POP {PC}或等效指令返回。 相关问题: 1. ARM架构中,当函数参数多于4个时,如何传递额外的参数? 2. 在ARM函数调用中,哪些寄存器是调用者保存的(caller-saved),哪些是被调用者保存的(callee-saved)? 3. ARM的栈帧中通常包含哪些内容? 4. 在异常处理(如中断)时,ARM的栈操作有何不同?</think>### ARM架构函数调用栈工作过程详解 在ARM架构中,函数调用栈通过寄存器协作栈内存管理实现,核心过程如下: #### 1. **关键寄存器作用** - **SP(Stack Pointer, R13)**:指向当前栈顶位置 - **LR(Link Register, R14)**:保存函数返回地址 - **PC(Program Counter, R15)**:存储下一条指令地址 - **FP(Frame Pointer, R11)**:可选,指向当前栈帧基址(用于调试回溯) #### 2. **函数调用过程(以函数A调用函数B为例)** **步骤1:调用准备(Caller)** - 参数传递:前4个参数通过$R0-R3$传递,多余参数压入栈 - 返回地址保存:执行`BL func_B`指令时,当前PC+4自动存入$LR$ ```assembly MOV R0, #5 ; 参数1 → R0 MOV R1, #10 ; 参数2 → R1 BL func_B ; 调用func_B (LR = PC+4) ``` **步骤2:被调用函数开场(Callee Prologue)** - 保存上下文:将$LR$需保留的寄存器压栈 - 栈空间分配:调整$SP$为局部变量预留空间 ```assembly func_B: PUSH {R4-R6, LR} ; 保存寄存器 & 返回地址 SUB SP, SP, #8 ; 分配8字节局部变量空间 ``` **步骤3:函数执行** - 局部变量存储在栈中(通过$SP$偏移访问) - 函数体执行期间可修改$R0-R3$ **步骤4:函数返回(Callee Epilogue)** - 释放栈空间:恢复$SP$到开场前位置 - 恢复上下文:弹出寄存器及返回地址到$PC$ ```assembly ADD SP, SP, #8 ; 释放局部变量空间 POP {R4-R6, PC} ; 恢复寄存器并直接返回 (PC ← LR) ``` #### 3. **栈帧结构示例 ``` 高地址 +-----------------+ | 调用者保存的寄存器 | ← 旧SP +-----------------+ | 参数5~n | (若有) +-----------------+ | 返回地址(PC) | ← 新FP (可选) +-----------------+ | 保存的LR | +-----------------+ | 保存的R4-R6 | +-----------------+ | 局部变量空间 | ← SP 低地址 ``` #### 4. **关键特性** 1. **栈增长方向**:ARM采用**满递减栈**(FD, Full Descending),栈向低地址增长 2. **寄存器保存规则**: - 调用者保存:$R0-R3$(参数/临时寄存器) - 被调用者保存:$R4-R11$(需长期保留的寄存器) 3. **返回机制**:通过`POP {..., PC}`直接将返回地址载入$PC$,等效于`BX LR` #### 5. **异常处理差异** 在中断/异常场景下: - 处理器自动保存$PC$$CPSR$到栈中 - 使用专用异常栈指针(如ARMv7的$SP_irq$) - 需手动保存其他寄存器[^1][^2] > 此过程遵循ARM AAPCS标准(ARM Architecture Procedure Call Standard),确保二进制兼容性[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值