前言
呵呵 此问题主要是来自于 帖子 月经结贴 -- 《Segmentation Fault in Linux》
这里主要也是 结合了作者的相关 case, 来做的一些 调试分享
当然 很多的情况还是 蛮有意思
本文主要问题如下
1. 访问异常堆栈地址1
2. 访问异常堆栈地址2
3. 访问异常堆栈地址3
4. stack_size 为什么初始化为 136kb?
5. 访问异常堆栈地址4
6. 访问异常堆栈地址5
1. 访问异常堆栈地址1
#include <stdio.h>
#include <stdlib.h>int* foo() {int a = 10;return &a;
}int main() {int* b;b = foo();printf ("%d\n", *b);
}
可以看到的是这里的 address 依然是 0, 应该是 编译时 有什么处理
然后 情况和 NPE 的情况一致
直接走的 bad_area, 输出日志信息, 发送 SIGSEGV 给目标进程
报错日志信息为
(initramfs) ./Test16SigSegvAccessInvalidStackAddr01
[ 9895.840368] Test16SigSegvAc[265]: segfault at 0 ip 00000000004005e9 sp 00007ffda8506930 error 4 in Test16SigSegvAccessInvalidStackAddr01[400000+1000]
-
出现问题的进程为 265号进程, 异常访问的地址为 0x 0
出现问题的异常代码为 0x4005e9, 栈顶寄存器的值为 0x 7ffda8506930
错误编码为 4 表示 PF_USER
0x0 为 foo 返回的地址信息, 应该是被编译器处理过
0x4005e9 为 main 中执行出现异常的代码段
在准备 printf 的参数的时候, 出现的异常
我们看一下 foo 的编译之后的结果
可以看到的是 返回值是直接 定义的 0, 然后 返回回去了
其他部分的增加了一个 局部变量 处理的意义不明
2. 访问异常堆栈地址2
可以看到的是这里的 address 依然是 0, 应该是 编译时 有什么处理
然后 情况和 上面 的情况一致
直接走的 bad_area, 输出日志信息, 发送 SIGSEGV 给目标进程
报错日志信息为
(initramfs) ./Test16SigSegvAccessInvalidStackAddr02
[10934.883918] Test16SigSegvAc[267]: segfault at 1388 ip 0000000000400644 sp 00007ffd1a7d5660 error 4 in Test16SigSegvAccessInvalidStackAddr02[400000+1000]
出现问题的进程为 267号进程, 异常访问的地址为 0x 1388
出现问题的异常代码为 0x400644, 栈顶寄存器的值为 0x 7ffd1a7d5660
错误编码为 4 表示 PF_USER
3. 访问异常堆栈地址3
#include <stdio.h>
#include <stdlib.h>int main() {char* c;c = (char*)&c - 8192*2;*c = 'a';printf ("%c\n", *c);
}
可以看到的是这里的 address 是 140735643557120 这是真实的用户栈帧的空间地址 0x7FFF 920A9D00
然后这里 堆栈对应的 vma 有 136k, 然后这里的 address 是在这个 vma 的区间内, 并且可读可写, 因此 没有异常
然后是走 正常的缺页中断, 然后之后是 对于该内存的赋值处理
regs->ip 为 0x4005bf
4. stack_size 为什么初始化为 136kb?
如下代码, rlimit_stack 默认值为 8192kb
stack 对应的 vma 默认大小为 8kb, 然后 stack_expand 为 128kb
如下代码处理之后 stack 对应的 vma 为 136kb
具体的更新 stack 对应的 vma 的地方在如下地方
看一下 stack 的初始化, 这里是关闭了 ASLR 的情况
stack 初始化为 以 0x7ffffffff000 结束的 4kb
exec.setup_arg_pages 传入的新的 stack_top 是通过 binfmt_elf.randomize_stack_top 计算得到的, 默认的 STACK_TOP 是 0x7ffffffff000
然后 binfmt_elf.randomize_stack_top 的过程中会偏移 随机数个页面
拷贝参数的时候, 可能空间不够 向下扩充了 4kb, 目前合计 8kb
然后就是 stack_expand, 扩充空间为 136kb
stack 空间观察的实际例子, 可以看到这里 stack 占用的是 0x7ffffffff000 - 0x7ffffffde000 = 0x21000 合计 132kb
估算大概就是 4kb[init] + 128kb[stack_expand] = 132kb
root@ubuntu:~/linux/linux-4.10.14# ps -ef | grep Test14
root 43248 42986 0 21:59 pts/0 00:00:00 gdb Test14ReadFileTwice
root 43250 43248 0 21:59 pts/0 00:00:00 /root/linux/tmp/Test14ReadFileTwice
root 43255 42488 0 21:59 pts/3 00:00:00 grep --color=auto Test14
root@ubuntu:~/linux/linux-4.10.14# cat /proc/43250/maps | grep stack
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
5. 访问异常堆栈地址4
#include <stdio.h>
#include <stdlib.h>int main() {// int *p = (int *) 0x7ffffffcf100;int *p = (int *) 0x7ffffffdf100;*p = 10;}
首先需要关闭 linux 的地址随机化
根据起始地址可以观察到的是 vma 是属于 堆栈所属的 vma, 大小为 136kb
stack 对应的 vma 区间为 0x7ffffffdd000 - 0x7ffffffff000
访问的地址属于 stack 的区间, 并且可读可写, 走正常的 缺页中断处理
更新上面的 指针 p “int *p = (int *) 0x7ffffffcf100;”
从代码可以看出, 如果找不到 address 对应的 vma, 是允许 access [($sp – 65536 – 256), $sp] 的地址空间的
可以的操作是将 sp 更新到 stack 对应的 vma 的低地址部分, 然后 在访问 sp – 64kb 范围内的内容, 这时候操作系统会 expan_stack 来扩充堆栈空间
但是 我们这里 sp 还在 stack 对应的 vma 的高地址部分, 不满足这里 扩充堆栈 的条件, 因此 这里得到的是一个 SIGSEGV
报错日志信息为
(initramfs) ./Test16SigSegvAccessKernelAddr03
[78329.025697] Test16SigSegvAc[297]: segfault at 7ffffffcf100 ip 00000000004004ec sp 00007fffffffea60 error 6 in Test16SigSegvAccessKernelAddr03[400000+1000]
出现问题的进程为 297号进程, 异常访问的地址为 0x 7ffffffcf100
出现问题的异常代码为 0x4004ec, 栈顶寄存器的值为 0x 7fffffffea60
错误编码为 4 表示 PF_USER | PF_WRITE
6. 访问异常堆栈地址5
#include <stdio.h>
#include <stdlib.h>#define GET_rsp(rsp) do { \asm volatile ("movq %%rsp, %0\n\t" : "=m" (rsp)); \
} while (0)#define K 1024int main() {char* c;int i = 0;unsigned long rsp;GET_rsp (rsp);printf ("Current stack pointer is %#x\n", rsp);while (1) {c = (char*)rsp - i * K;*c = 'a';GET_rsp (rsp);printf ("rsp = %#x, overflow %dK\n", rsp, i);i ++;}
}
日志输出如下
(initramfs) ./Test16SigSegvAccessInvalidStackAddr04
Current stack pointer is 0xffffea30
rsp = 0xffffea30, overflow 0K
rsp = 0xffffea30, overflow 1K
rsp = 0xffffea30, overflow 2K
rsp = 0xffffea30, overflow 3K
rsp = 0xffffea30, overflow 4K
rsp = 0xffffea30, overflow 5K
rsp = 0xffffea30, overflow 6K
rsp = 0xffffea30, overflow 7K
rsp = 0xffffea30, overflow 8K
rsp = 0xffffea30, overflow 9K
rsp = 0xffffea30, overflow 10K
// 省略 .. 一部分连续的输出
rsp = 0xffffea30, overflow 8181K
rsp = 0xffffea30, overflow 8182K
rsp = 0xffffea30, overflow 8183K
rsp = 0xffffea30, overflow 8184K
rsp = 0xffffea30, overflow 8185K
rsp = 0xffffea30, overflow 8186K
rsp = 0xffffea30, overflow 8187K
rsp = 0xffffea30, overflow 8188K
rsp = 0xffffea30, overflow 8189K
rsp = 0xffffea30, overflow 8190K
[ 1569.537658] Test16SigSegvAc[273]: segfault at 7fffff7fee30 ip 0000000000400578 sp 00007fffffffea30 error 6 in Test16SigSegvAccessInvalidStackAddr04[400000+1000]
Segmentation fault
stack 默认有 136kb 合计 34 个 page
当前 rsp 为 0x7fffffffea30, STACK_TOP 为 0x7ffffffff000, 当前已经使用的空间为 0x5d0 = 1488 合计约 1.5kb
从 1.5kb – 8kb 的区间有正常的物理内存, 并且可写, 无缺页中断
从 8kb – 136kb 的区间走的是正常的缺页中断
然后从 136kb – 8192kb 的区间, 开始以页为单位, 向下 expand_stack, 每次 扩展一个页面, 比如访问 135kb 所在的页面时, 会向下 expand 136kb – 140kb 对应的页面
到达 STACK_LIMIT 8192kb 的时候, stack 根据约束无法向下再扩展, 访问到异常地址, 发生 SIGSEGV 信号
8kb – 132kb 走正常的缺页中断
这里 vma 区间是 0x7ffffffdd000 - 0x7ffffffff000, 需要访问的空间为 0x7fffffffce30
然后从 136kb – 8192kb 的区间, 如果下一个页面未使用, 向下 expand_stack, 每次 扩展一个页面
这里访问的地址是 0x7ffffffdde30
堆栈空间访问到 目前的最低一个页面的时候, 需要自动向下扩展一个页面
访问到超出界限的地方
这里访问的空间是 0x7fffffff7fe30, 是刚好超出 8192kb 的第一个页面
这里是 处理缺页中断失败, 然后 接着走的 下面的 mm_fault_error
然后 fault 带上了 FAULT_SIGSEGV, 发送 SIGSEGV 信号
然后这里的 缺页中断 异常主要是在 check_stack_guard_page 中, 发现 到达了 stack 前一块空间的地方, 返回没有内存 NOMEM, 然后 check_stack_guard_page 这里响应给上级 VM_FAULT_SIGSEGV 给上层, 再最上面就是 __do_page_fault 的处理了
完