共计 1791 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点
在 ARM 开发过程中,栈使用不当常常引发一些难以调试的问题。最常见的就是栈溢出,导致相邻变量被破坏,程序行为异常。例如,局部数组越界写入可能会覆盖函数返回地址,造成程序崩溃。另一个典型问题是 LR 寄存器被意外修改,这会导致函数无法正确返回,产生莫名其妙的跳转行为。

在 GDB 调试时,我们经常会看到类似这样的错误信息:
Program received signal SIGSEGV, Segmentation fault.
0x00000000 in ?? ()
或者更具体的栈相关错误:
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
核心原理
ARM 栈帧结构
ARM 架构下的栈帧结构有其特点。以 ARMv7 为例,典型的栈帧包含以下部分:
- 参数区域(存放调用函数的参数)
- 返回地址(LR 寄存器保存的值)
- 上一帧指针(FP 寄存器)
- 局部变量区
- 保存的寄存器区
关键寄存器的作用:
- SP(Stack Pointer):始终指向栈顶
- FP(Frame Pointer):指向当前栈帧的基址
- LR(Link Register):保存函数返回地址
栈增长方向
与 x86 架构不同,ARM 架构的栈通常是向下增长的(从高地址向低地址)。这意味着每次压栈操作会使 SP 减小,而弹栈操作会使 SP 增大。
AAPCS 约定
ARM 架构过程调用标准 (AAPCS) 规定了栈必须 8 字节对齐(ARMv7)或 16 字节对齐(ARMv8)。寄存器使用也有严格规定:
- R0-R3 用于参数传递
- R4-R11 用于保存局部变量
- R12(IP)可作为临时寄存器
- R13(SP)、R14(LR)、R15(PC)有特殊用途
实战分析
问题代码示例
下面是一个故意设计的有栈问题的 C 代码:
void vulnerable_function() {char buffer[4];
strcpy(buffer, "This will overflow");
}
int main() {vulnerable_function();
return 0;
}
反汇编分析
使用 objdump 查看反汇编代码(ARMv7 指令集):
0001042c <vulnerable_function>:
1042c: e92d4800 push {fp, lr}
10430: e28db004 add fp, sp, #4
10434: e24dd008 sub sp, sp, #8
10438: e24b3008 sub r3, fp, #8
1043c: e59f2010 ldr r2, [pc, #16] ; 10454
10440: e1a01003 mov r1, r3
10444: e1a00002 mov r0, r2
10448: ebffffa5 bl 102e4 <strcpy@plt>
1044c: e24bd004 sub sp, fp, #4
10450: e8bd8800 pop {fp, pc}
关键指令分析:
push {fp, lr}保存帧指针和返回地址add fp, sp, #4设置新的帧指针sub sp, sp, #8为局部变量分配栈空间(这里只有 4 字节 buffer,但额外分配了 4 字节)
GDB 调试过程
在 GDB 中观察栈变化:
(gdb) break *0x10438
(gdb) run
(gdb) info registers sp fp lr
sp 0xbefff760 0xbefff760
fp 0xbefff764 0xbefff764
lr 0x10468 0x10468
(gdb) x/8xw $sp
0xbefff760: 0x00000000 0x00000000 0x00000000 0x00010468
0xbefff770: 0xb6fff000 0x00000000 0x00000000 0x00000000
当执行 strcpy 后,可以看到栈被破坏的情况。
避坑指南
栈大小估算
- 计算所有局部变量的总大小
- 加上保存寄存器所需空间
- 考虑对齐要求
- 至少预留 20% 的余量
中断上下文保护
在中断处理中:
- 立即保存关键寄存器
- 使用独立的中断栈(如果支持)
- 避免在中断中进行大量栈操作
编译器优化影响
编译器优化可能会:
- 省略帧指针(使用 -fomit-frame-pointer)
- 内联小函数,减少栈使用
- 重用栈空间
使用 -fno-omit-frame-pointer 可以保留帧指针,方便调试。
自测问题
- ARM 架构中,栈的增长方向是什么?与 x86 有何不同?
- 在 AAPCS 标准中,R0-R3 寄存器的主要用途是什么?
- 如何通过 GDB 命令查看当前函数的栈帧信息?
正文完
发表至: 未分类
近三天内
