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

2024-06-03 00:12
文章标签 场景 60 segmentfault

本文主要是介绍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 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

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 

 

 

 

 

 

这篇关于60 关于 SegmentFault 的一些场景 (2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1025492

相关文章

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

MyBatis-Plus 中 nested() 与 and() 方法详解(最佳实践场景)

《MyBatis-Plus中nested()与and()方法详解(最佳实践场景)》在MyBatis-Plus的条件构造器中,nested()和and()都是用于构建复杂查询条件的关键方法,但... 目录MyBATis-Plus 中nested()与and()方法详解一、核心区别对比二、方法详解1.and()

ModelMapper基本使用和常见场景示例详解

《ModelMapper基本使用和常见场景示例详解》ModelMapper是Java对象映射库,支持自动映射、自定义规则、集合转换及高级配置(如匹配策略、转换器),可集成SpringBoot,减少样板... 目录1. 添加依赖2. 基本用法示例:简单对象映射3. 自定义映射规则4. 集合映射5. 高级配置匹

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析

《Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析》InstantiationAwareBeanPostProcessor是Spring... 目录一、什么是InstantiationAwareBeanPostProcessor?二、核心方法解

Java 枚举的基本使用方法及实际使用场景

《Java枚举的基本使用方法及实际使用场景》枚举是Java中一种特殊的类,用于定义一组固定的常量,枚举类型提供了更好的类型安全性和可读性,适用于需要定义一组有限且固定的值的场景,本文给大家介绍Jav... 目录一、什么是枚举?二、枚举的基本使用方法定义枚举三、实际使用场景代替常量状态机四、更多用法1.实现接

java -jar命令运行 jar包时运行外部依赖jar包的场景分析

《java-jar命令运行jar包时运行外部依赖jar包的场景分析》:本文主要介绍java-jar命令运行jar包时运行外部依赖jar包的场景分析,本文给大家介绍的非常详细,对大家的学习或工作... 目录Java -jar命令运行 jar包时如何运行外部依赖jar包场景:解决:方法一、启动参数添加: -Xb

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示