今天我们利用stm32来探究一下程序运行时的栈空间是怎么分布的,为什么栈空间设置不合理时会有栈溢出导致程序崩溃
下面是我们要使用的测试代码,先贴出来, 为了更好地分析栈空间我们把栈空间用串口dump出来
void hex_dump(unsigned char *p, int len)
{
uint32_t i, j;
//printf("\n===============dump start====================\n");
printf("\r\n[");
for (i = 0, j = 0; i < len; i++)
{
printf("%02x ", p[i]);
if (j < 15)
{
j++;
}
else
{
j = 0;
printf("\r\n");
}
}
printf("]\r\n");
//printf("\n===============dump end====================\n");
}
void statck_dump()
{
hex_dump((char *)(0x20000460-0x400), 0x400);
}
void test_func(int v)
{
printf("%d\r\n", v);
statck_dump();
}
void func(void)
{
char buf[50] = {0};
memset(buf, 0x22, sizeof(buf));
buf[0] = 0xFF;
statck_dump();
}
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
int a = 10;
int b = 20;
int c = a + b;
char buf[100] = {0};
memset(buf, 0x55, sizeof(buf));
buf[0] = 1;
buf[1] = 2;
buf[2] = 3;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("<-- start -->\r\n");
printf("&a = %p\r\n", &a);
printf("&b = %p\r\n", &b);
printf("&c = %p\r\n", &c);
test_func(c);
while (1)
{
printf("<-- run -->\r\n");
statck_dump();
func();
while(1);
}
}
1.获取栈顶的地址,栈空间大小
我们的工程是用keil建立的,栈空间的大小在启动文件中可以设置,这里我们设置的大小是0x400(1024字节)
栈顶地址我们可以在map文件中查看或者debug时查看sp寄存器:
需要注意的是stm32的栈是逆生长的,即进栈时是从高地址向低地址增长
通过debug我们可以看见SP寄存器的值是 0x20000460 这就是我们的栈顶地址,另外在仿真时我们可以看见汇编的指令地址都是 0x800xxxx,这是怎们回事呢,熟悉stm32的朋友都知道0x8000000是我们的ROM地址,0x20000000是我们的RAM地址,这是因为单片机并不像x86或者其他系统那样运行程序之前需要先把程序加载到内存中,单片机的内存空间有限,如果把程序全到加载到内存的话恐怕也不太现实,所有是直接在flash中运行的,限于成本慢一点我们也是可以接受的
2.栈空间的分布
我们在程序开头的地方定义了三个变量和一个数组,我们先把程序跑起来到**test_func()**的地方
int a = 10;
int b = 20;
int c = a + b;
char buf[100] = {0};
memset(buf, 0x55, sizeof(buf));
buf[0] = 1;
buf[1] = 2;
buf[2] = 3;
...
printf("<-- start -->\r\n");
printf("&a = %p\r\n", &a);
printf("&b = %p\r\n", &b);
printf("&c = %p\r\n", &c);
stack_dump();
test_func(c);
stack_dump();
在test_func() 之前我们dump了栈空间,我们看下栈空间中的内容
从上面dump中的内容我们可以看出来,前面的值分别是变量a, b ,c的值,而且从打印的地址来看也是一致的,包括buf数组中的内容
然后我们继续运行到test_func()中在dump下内容看下
void test_func(int v)
{
printf("%d\r\n", v);
char buf[100] = {0};
memset(buf, 0xAA, sizeof(buf));
stack_dump();
printf("%d\r\n", v);
return;
}
通过上面的dump我们可以看见在test_func()中的buf也被放在了栈中,通过分析我们可以看到函数中的局部变量都是在栈空间中开辟空间的,如果我们的栈空间过小很容易溢出,包括在调用子函数时寄存器的值也会被保存在栈空间中
接着往下看
printf("<-- run -->\r\n");
stack_dump();
func();
while(1);
---------------------
void func(void)
{
char buf[100] = {0};
memset(buf, 0x22, sizeof(buf));
buf[0] = 0xFF;
stack_dump();
}
在进入func()之前我们dump了一次栈空间我们发现在test_func()中dump的AA AA 已经被覆盖掉,那是因为退出子函数时会执行pop ,子函数的栈空间会被释放掉,或者说SP会指回进子函数之前的地方,所有里面的内容就被后面的数据覆盖掉了
最后我们在函数func中定义了一个数组,并且dump了栈空间,让我们来看下栈中的数据是怎样的
总结
栈其实就是我们提前在内存上分配好的一块空间,用来存储临时变量和函数调用时寄存器的值,在使用时要控制好大小,防止溢出
关于堆栈的更多细节推荐大家看《Cortex-M3权威指南(中文).pdf》
更多精彩内容欢迎关注小熊嵌入式