60 关于 SegmentFault 的一些场景 (2)

 前言

呵呵 此问题主要是来自于 帖子 月经结贴 -- 《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 给目标进程 

948bd755e59c40009f3cd695fff82a86.png

报错日志信息为 

(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 的参数的时候, 出现的异常 

81b85483bbaa4509a754986535b7cac2.png

 

我们看一下 foo 的编译之后的结果

可以看到的是 返回值是直接 定义的 0, 然后 返回回去了

其他部分的增加了一个 局部变量 处理的意义不明 

f15be55d36b747158e13ba0e7700e16e.png

 

 

2. 访问异常堆栈地址2 

可以看到的是这里的 address 依然是 0, 应该是 编译时 有什么处理

然后 情况和 上面 的情况一致 

直接走的 bad_area, 输出日志信息, 发送 SIGSEGV 给目标进程 

6ca92732367145c8b66074e2fe9da0b9.png

 

报错日志信息为 

(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 的区间内, 并且可读可写, 因此 没有异常

17ea05625fac45c5bc525ba8b1a09525.png

 

然后是走 正常的缺页中断, 然后之后是 对于该内存的赋值处理

fb8f43b3d52e4b839114a947731cb217.png

 

regs->ip 为 0x4005bf

3b3a0fce8b01479aaf2420008fd3fb98.png 

 

4. stack_size 为什么初始化为 136kb?

如下代码, rlimit_stack 默认值为 8192kb

stack 对应的 vma 默认大小为 8kb, 然后 stack_expand 为 128kb

如下代码处理之后 stack 对应的 vma 为 136kb

54d5746ddfbe4405b679a1c07cb3396d.png

 

具体的更新 stack 对应的 vma 的地方在如下地方

1157c50b4bee432eba40a06b117a9a46.png

 

看一下 stack 的初始化, 这里是关闭了 ASLR 的情况 

stack 初始化为 以 0x7ffffffff000 结束的 4kb

24495c4a9bba45729793c1adf7f2b42c.png

 

exec.setup_arg_pages 传入的新的 stack_top 是通过 binfmt_elf.randomize_stack_top 计算得到的, 默认的 STACK_TOP 是 0x7ffffffff000

然后 binfmt_elf.randomize_stack_top 的过程中会偏移 随机数个页面

d0e9a0afe5e74972a876abeab26369c3.png

 

拷贝参数的时候, 可能空间不够 向下扩充了 4kb, 目前合计 8kb

86782480199d44f4a0f5389de0f93c8e.png

 

然后就是 stack_expand, 扩充空间为 136kb

c7303114088c46a8b31666db95fc78cd.png

 

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

70fc9aa716e84c089b07b0e5399b9751.png

 

访问的地址属于 stack 的区间, 并且可读可写, 走正常的 缺页中断处理

4c7ee9d5d192461d9382ce60174066e9.png 

 

更新上面的 指针 p “int *p = (int *) 0x7ffffffcf100;”

从代码可以看出, 如果找不到 address 对应的 vma, 是允许 access [($sp – 65536 – 256), $sp] 的地址空间的 

可以的操作是将 sp 更新到 stack 对应的 vma 的低地址部分, 然后 在访问 sp – 64kb 范围内的内容, 这时候操作系统会 expan_stack 来扩充堆栈空间 

但是 我们这里 sp 还在 stack 对应的 vma 的高地址部分, 不满足这里 扩充堆栈 的条件, 因此 这里得到的是一个 SIGSEGV

15b57e461dd644fd8fe64ddedf383857.png

 

报错日志信息为 

(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 1024

int 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

12c4945418f943059db19fa9aa54447e.png

 

然后从 136kb – 8192kb 的区间, 如果下一个页面未使用, 向下 expand_stack, 每次 扩展一个页面

这里访问的地址是 0x7ffffffdde30

230247c43e9d4c388efe4e31d478d25d.png

 

堆栈空间访问到 目前的最低一个页面的时候, 需要自动向下扩展一个页面 

d348559cf2a849c08b2a29f052e6037c.png

2898d8ac40d44a1983baaa0165c9cbc8.png 

 

访问到超出界限的地方 

这里访问的空间是 0x7fffffff7fe30, 是刚好超出 8192kb 的第一个页面 

这里是 处理缺页中断失败, 然后 接着走的 下面的 mm_fault_error

39197274ac01462997ba504309a7d77b.png

 

然后 fault 带上了 FAULT_SIGSEGV, 发送 SIGSEGV 信号 

3010f194a8094de2b72d5e5aed67304f.png 

然后这里的 缺页中断 异常主要是在 check_stack_guard_page 中, 发现 到达了 stack 前一块空间的地方, 返回没有内存 NOMEM, 然后 check_stack_guard_page 这里响应给上级 VM_FAULT_SIGSEGV 给上层, 再最上面就是 __do_page_fault 的处理了 

93a671156ef94ab9a44ab00709a9b2d1.png 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值