在学习资料满天飞的⼤环境下,知识变得⾮常零散,体系化的知识并不多,这就导致很多⼈每天都努⼒学习到感动⾃⼰,最终却收效甚微,甚⾄放弃学习。我的使命就是过滤掉⼤量的⽆效信息,将知识体系化,以短平快的⽅式直达问题本质,把⼤家从⼤海捞针的痛苦中解脱出来。
⽂章⽬录
1 问题引⼊
栈空间不⾜的问题出现的概率其实不是很⾼。因为默认的栈空间都是MB级别的,如果调⽤深度不是很深或者局部变量不是很⼤是很难发⽣栈空间不⾜的,除了以下3种情况:
使⽤了未知的第三⽅代码库。
业务剧烈变更。
新⼿。
出现栈空间不⾜的表象⼀般都是任务异常和段错误。按道理来讲,出现段错误很明显啊,在测试阶段直接定位解决了不就⾏了,也不算是什么疑难杂症。
如果真实的⼯程都和学校⾥的⽰例程序⼀样简单,当然这个推论是成⽴的。但是业务场景复杂之后,只有流程跑到这⾥才会暴露问题,⽽且这个问题还是⼀个致命问题。
为什么栈空间不⾜了还和流程相关呢?请看下⽂。。。
2 问题分析定位
当出现任务异常和段错误后,第⼀反应是指针解引⽤错误。往往都是⼀顿查指针的使⽤。然后,有时候排查完指针的使⽤也没有什么头绪时,就要考虑是否是栈空间不⾜导致的了(如果是第⼀次遇到,没有这个意识,确实会⼀头雾⽔)。
2.1 -fstack-protector-all只适⽤于栈泄露
有⼈推荐使⽤ -fstack-protector-all 选项定位是否栈空间不⾜了。但我亲测发现它 罩不住 。
考虑到有的TX对这个编译选项还不是很熟悉,借此机会简单介绍⼀下。
2.1.1 监控栈泄露
下⾯是⼀个C语⾔写的⼩例⼦。
# 编译时添加栈保护机制选项
gcc -fstack-protector-all -g stack.c
其中12和13⾏都有内存越界访问,但是,b数组的越界访问不属于栈泄露。因为在没有栈溢出保护机制下编译时,局部变量的定义顺序即⼊栈顺序。在添加了栈溢出保护机制后,局部变量的⼊栈顺序和类型相关,⽐如char类型先⼊栈,int类型后⼊栈;对于同⼀种类型(例如本例中的a和b数组),⼊栈顺序和定义顺序刚好相反。
所以,b数组虽然存在越界访问,但是不属于栈溢出。因此,-fstack-protector-all的能⼒很有限。
/*C源⽂件*/
1 #include <stdio.h>
2
3 #define MAX (1000)
4
5void test(void)
6{
7char a[MAX]={0};
8char b[8]={0};
9
10printf("debug add_a = %p; add_b = %p \n", a, b);
11
12 a[MAX-1+8]=3;/*可以被栈溢出机制检测到*/
13 b[16]=4;/*不可以被栈溢出机制检测到*/
14}
15
16int main(void)
17{
18test();
19
20return0;
21}
栈泄露的监控结果如下:
孟庭苇照片debug add_a = 0x7ffcec109b00; add_b = 0x7ffcec109af0
*** stack smashing detected ***: ./a.out terminated
======= Backtrace: =========
/lib64/libc.so.6(__fortify_fail+0x37)[0x7f2b039429e7]
/
lib64/libc.so.6(+0x1179a2)[0x7f2b039429a2]
./a.out[0x40060b]
./a.out[0x400629]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7f2b0384d3d5]
./a.out[0x4004c9]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:02 6988561411 /home/zhaoyuandong/mycode/0713_stack/1/a.out 00600000-00601000 r--p 00000000 fd:02 6988561411 /home/zhaoyuandong/mycode/0713_stack/1/a.out 00601000-00602000 rw-p 00001000 fd:02 6988561411 /home/zhaoyuandong/mycode/0713_stack/1/a.out 00ad7000-00af8000 rw-p 00000000 00:00 0 [heap]
7f2b03615000-7f2b0362a000 r-xp 00000000 fd:00 41943555 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f2b0362a000-7f2b03829000 ---p 00015000 fd:00 41943555 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f2b03829000-7f2b0382a000 r--p 00014000 fd:00 41943555 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f2b0382a000-7f2b0382b000 rw-p 00015000 fd:00 41943555 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f2b0382b000-7f2b039ed000 r-xp 00000000 fd:00 41960524 /usr/lib64/libc-2.17.so
7f2b039ed000-7f2b03bed000 ---p 001c2000 fd:00 41960524 /usr/lib64/libc-2.17.so
7f2b03bed000-7f2b03bf1000 r--p 001c2000 fd:00 41960524 /usr/lib64/libc-2.17.so
7f2b03bf1000-7f2b03bf3000 rw-p 001c6000 fd:00 41960524 /usr/lib64/libc-2.17.so
7f2b03bf3000-7f2b03bf8000 rw-p 00000000 00:00 0
7f2b03bf8000-7f2b03c1a000 r-xp 00000000 fd:00 41960517 /usr/lib64/ld-2.17.so
7f2b03df7000-7f2b03dfa000 rw-p 00000000 00:00 0
7f2b03e16000-7f2b03e19000 rw-p 00000000 00:00 0
7f2b03e19000-7f2b03e1a000 r--p 00021000 fd:00 41960517 /usr/lib64/ld-2.17.so
7f2b03e1a000-7f2b03e1b000 rw-p 00022000 fd:00 41960517 /usr/lib64/ld-2.17.so
7f2b03e1b000-7f2b03e1c000 rw-p 00000000 00:00 0什么时候考研
7ffcec0ea000-7ffcec10b000 rw-p 00000000 00:00 0 [stack]
7ffcec1f2000-7ffcec1f4000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Aborted (core dumped)
2.1.2 ⽆法监控栈空间不⾜
我们再构造⼀种栈空间不⾜的场景,编译时同样加上 -fstack-protector-all 选项,看看能否⽣效。
Tips:我使⽤的x86平台创建⼀个进程默认栈空间⼤⼩是8MB。⾄于如何查看和修改请看问题解决⼀节。
1 #include <stdio.h>
2
敖嘉年3 #define MAX (8192*1024)// 定义长度为默认的栈空间⼤⼩
4void test(void)
5{
6char a[MAX];
7
8 a[MAX]=3;// ok
9
10printf("a[-1] = %d.\n", a[MAX-8]);// not ok
11}
12
13int main(void)
14{
15test();
16
17return0;
18}
半截蜡烛续写运⾏结果如下所⽰。
Segmentation fault (core dumped)李诗韵个人资料
我们发现,-fstack-protector-all是⽆法有效监控栈空间不⾜的问题的。
那么我们如何跟踪定位这个问题呢?答案是使⽤GDB。
2.2 GDB跟踪调试
使⽤GDB可以精准的定位到是哪⼀⾏代码出现了问题(编译时加上-g选项),对GDB不太熟悉的TX可以看这篇:。
(gdb) r
Starting program: /home/zhaoyuandong/mycode/0713_stack/1/a.out
Program received signal SIGSEGV, Segmentation fault.
0x000000000040053f in test() at main2.c:10
10 printf("a[-1] = %d.\n", a[MAX-8]); // not ok
⽐较细⼼的TX可能会⽂了,即使⽤GDB跟到了这⾥,那⼜怎么确定是指针使⽤错误还是栈空间不⾜导致的呢?
好问题。有两个⽐较简单的⽅式可以验证:
⼀个是缩减变量长度测试。
⼀个是将局部变量定义成全局变量;或者直接在局部变量前添加static将其定义到静态区后测试。
如果上述两个⽅式⽣效了,就基本说明是栈空间不⾜导致的。现价工业总产值
当然,还可以实际查看⼀下当前线程的栈空间⼤⼩和地址(maps信息)以便进⼀步⽯锤。
3 问题解决
3.1 更改变量⼤⼩或位置
解决栈空间不⾜的问题优选删除或减⼩过⼤的局部变量。
如果该函数属于调⽤⽐较频繁的可以考虑使⽤局部static变量。
如果调⽤不是⾮常频繁,对性能也没有太⾼要求的话,可以考虑使⽤malloc动态申请内存。
3.2 扩展栈空间
如果优化效果不明显,或者由于业务扩张导致的栈空间严重不⾜的话就只能选择扩充栈空间了。
3.2.1 通过shell指令
# 查询当前默认的栈空间⼤⼩
[xxx@bogon 1]$ ulimit -s
8192 # 我的Linux系统上默认的是8192 MB
# 设置默认的栈空间⼤⼩
[xxx@bogon 1]$ ulimit -s 8193
3.2.2 通过posix接⼝
配置堆栈空间⼤⼩⼀般在创建线程之前进⾏。使⽤下述接⼝获取和配置:
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
如果你也遇到栈空间不⾜的问题,在代码⾥到上述接⼝,把栈空间⼤⼩改合适就可以了。
4 复盘
在问题引⼊时,我们提到了这个问题的引⼊⼤体分为3种情况:使⽤第三⽅代码;业务变动;新⼿编程。
最后⼀种情况抛开不讲(涉及到公司的培训制度)。另外两种情况出现时是否有应对策略?
其实是有的,但是⼤部分国内的公司并没有做。最简单直接的⽅式就是在上述两种情况产⽣时,⼀定要使⽤栈空间监测⼯具进⾏测试,提前发现 病灶 ,提前。
对于栈空间监测⼯具的制作也并不复杂,我们下回接着说。
恭喜你⼜坚持看完了⼀篇博客,⼜进步了⼀点点!如果感觉还不错就点个赞再⾛吧,你的点赞和关注将是我持续输出的哒哒哒动⼒~~
发布评论