栈生长的方向
栈生长方向的定义
若入栈之后(PSUH
后),栈指针寄存器(SP
)值变大了,那么栈就是向上生长的。
若入栈之后(PUSH
后),栈指针寄存器(SP
)值变小了,那么栈就是向下生长的。
栈生长方向影响的内容
栈方向影响溢出保护机制的设计,如栈保护页(Guard Page)的放置位置需与生长方向匹配
在FreeRTOS中栈生长的方向也影响任务栈分配与内存管理
通过调试查看SP指针验证
通过调试在stm32f103zet6中查看SP指针验证栈生长方向。
思路:在调试模式下,单步通过进入子函数时观察栈指针的变化,来判断栈生长的方向。
测试代码如下:
void func2(void)
{
char buf[100] = {0};
buf[0] = 'A';
}
void func1(void)
{
char buf[100] = {0};
buf[0] = 'A';
func2();
}
int main(void)
{
func1();
return 0;
}
进入main()函数时的SP
值是0x200009B8
进入func1()
函数后,可以发现SP
值变成了0x20000950
所以,在入栈后,stm32f103zet6的SP
指针值是减少的,因此stm32f103zet6的栈生长的方向是向下的。
通过变量地址验证
思路:
因为每次调用子函数时,编译器会生成代码来 动态调整栈指针(SP),为该函数分配新的栈帧,用于存储:函数的局部变量、函数调用时的 参数等内容。
所以通过判断进入子函数时,可以根据函数与其子函数中变量的地址大小来判断栈生长方向。
函数变量的地址值 大于 子函数中变量的地址值,则栈向下生长。
函数变量的地址值 小于 子函数中变量的地址值,则栈向下生长。
#include <stdio.h>
static int stack_dir = 0;
static void find_stack_direction (void)
{
static char* addr = NULL; /* address of first `dummy', once known */
char dummy; /* to get stack address */
if (addr == NULL)
{
addr = &dummy; /* initial entry */
find_stack_direction (); /* recurse once */
}
else
{
if(&dummy > addr)
{
stack_dir = 1;
}
else
{
stack_dir = -1;
}
}
}
int main(void)
{
find_stack_direction();
if(stack_dir == 1)
{
puts("Stack grew upward\n");
}
else
{
puts("Stack grew downward\n");
}
return 0;
}
stm32f103zet6芯片上运行改代码的结果如下
思考:谁分配的栈空间?
是编译器在帮助我们分配栈空间。
编译器在编译阶段会为每个函数生成指令,调整栈指针(SP) 以分配或释放栈空间。例如:
- x86架构:通过
sub esp, N
分配栈空间,add esp, N
释放; - ARM架构:通过
sub sp, sp, #N
分配,add sp, sp, #N
释放。
这里的sub
指令即是做减法的指令,add
指令是做加法的指令。
计算栈空间的过程是静态的过程
*在编译阶段时,编译器 分析函数的 局部变量、参数传递方式、寄存器保存需求 等,计算出该函数需要占用的总栈空间,并将其写入生成的代码中
每次调用函数时,编译器生成的代码会 动态调整栈指针(SP),为该函数分配新的栈帧,用于存储:
- 函数的 局部变量
- 函数调用时的 参数(若通过栈传递)
- 返回地址(调用结束后恢复执行的位置)
- 可能被修改的 寄存器值(需保存的上下文)
栈分配的本质是 移动栈指针(SP),而非显式的内存申请(如堆的 malloc)。
示例分析
在main()
函数调用func1()
函数时,通过BL.w 0x8000A78
指令,跳转到存储func1()
函数的代码行中。
在存储func1()
函数的代码中,
我们可以查看第一条指令就是入栈指令PSUH {lr}
,暂不分析该命令;
第二条指令是分配堆栈的指令SUB sp,sp,#0x64
,该命令主要是为char buf[100]
该变量分配栈空间。0x64=100,刚好是100个字节。
在func1()
函数执行完毕后,通过ADD sp, sp, #0x64
来修改SP指针,从而释放了栈空间。
小结
在该文章中,我们主要了解了如何通过SP指针的变化来判断栈生长的方向,而且我们也分析了arm系列的芯片是怎么去修改栈指针的。
留下个小疑问:栈生长的方向的是由 编译器生成的修改栈指针寄存器的指令 决定的吗?