目录
闲来无事,扣个指甲。
通常情况下,正常的工程里很难遇到栈溢出的案例。栈溢出情况可能有多种,这里列举无限递归的demo。
一,无限递归demo
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define STACK_SIZE 64
void *thread_func(void *arg) {
static int i = 0;
i++;
printf("thread recursion level: %d\n", i);
thread_func(NULL);
return NULL;
}
int main() {
pthread_attr_t attr;
pthread_t tid;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, STACK_SIZE);
pthread_create(&tid, &attr, thread_func, NULL);
pthread_join(tid, NULL);
return 0;
}
运行会有著名的segment fault:
结果显示在递归到261902次的时候,发生了栈溢出。
如果万一你的工程里遇到了,该怎么去分析呢?addr2line ?能解决?addr2line 最多带你到11行的代码,然后呢?
二,大道至简
看似复杂实则非常的简单。
跳到第0栈,即事故现场帧,注意这里的 sp 和 x18 即可:
x29 0xffffbed4c010
sp 0xffffbed4c010
x18 0xffffbed4c6d2
明显能看到sp < x18. 妥妥的栈溢出。
这里核心点在于,x18 一般是特殊寄存器,保存的是栈限制地址。
上学的时候就知道,栈的地址是向下生长的,如下(盗图)。
这里sp 已经增长超过了栈限制地址了,即:栈溢出。
其他情况,差不多也是如此,比如访问变量地址超出了栈空间。重点关注X29,SP 和X18.
再举第2个例子:
#include <stdio.h>
void function() {
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
printf("%s,stackoverflow,arr[14]:%p\n",__func__,arr+14);
arr[14] = 0;
}
int main() {
function();
return 0;
}
这里是栈上变量溢出,也是数组越界。
数组地址破了fp:
-exec info regis x18 x29 sp
x18 0xffffffffee0c
x29 0xffffffffef50
sp 0xffffffffef50
-exec p /x &arr
$1 = 0xffffffffef60
-exec p /x &arr[14]
$2 = 0xffffffffef98
终端显示:
function,stackoverflow,arr[14]:0xffffffffef98
Segmentation fault
大道至简,3个关键点:
1,aarch64/arm64 x18 栈限制寄存器
2,栈的生长方向是递减的
3,fp,sp 寄存器
备注下:Arm, 即32位的寄存器行为和用途有些许不一样.
附加arm64 32个寄存器介绍
引用自(原地址)
ARM64提供了32个通用寄存器,其用途如下表:
x0~x7:传递子程序的参数和返回值,使用时不需要保存,多余的参数用堆栈传递,64位的返回结果保存在x0中。
x8:用于保存子程序的返回地址,使用时不需要保存。
x9~x15:临时寄存器,也叫可变寄存器,子程序使用时不需要保存。
x16~x17:子程序内部调用寄存器(IPx),使用时不需要保存,尽量不要使用。
x18:平台寄存器,它的使用与平台相关,尽量不要使用。
x19~x28:临时寄存器,子程序使用时必须保存。
x29:帧指针寄存器(FP),用于连接栈帧,使用时必须保存。[这里需要修正,不完全这样]
x30:链接寄存器(LR),用于保存子程序的返回地址。
x31:堆栈指针寄存器(SP),用于指向每个函数的栈顶。
编译工具:极力推荐GDB 调试。VSCode只是看起来好看,实际上没有gdb 方便。