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

相关文章

IDEA实现回退提交的git代码(四种常见场景)

《IDEA实现回退提交的git代码(四种常见场景)》:本文主要介绍IDEA实现回退提交的git代码(四种常见场景),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.已提交commit,还未push到远端(Undo Commit)2.已提交commit并push到

Linux高并发场景下的网络参数调优实战指南

《Linux高并发场景下的网络参数调优实战指南》在高并发网络服务场景中,Linux内核的默认网络参数往往无法满足需求,导致性能瓶颈、连接超时甚至服务崩溃,本文基于真实案例分析,从参数解读、问题诊断到优... 目录一、问题背景:当并发连接遇上性能瓶颈1.1 案例环境1.2 初始参数分析二、深度诊断:连接状态与

Redis中RedisSearch使用及应用场景

《Redis中RedisSearch使用及应用场景》RedisSearch是一个强大的全文搜索和索引模块,可以为Redis添加高效的搜索功能,下面就来介绍一下RedisSearch使用及应用场景,感兴... 目录1. RedisSearch的基本概念2. RedisSearch的核心功能(1) 创建索引(2

Kotlin运算符重载函数及作用场景

《Kotlin运算符重载函数及作用场景》在Kotlin里,运算符重载函数允许为自定义类型重新定义现有的运算符(如+-…)行为,从而让自定义类型能像内置类型那样使用运算符,本文给大家介绍Kotlin运算... 目录基本语法作用场景类对象数据类型接口注意事项在 Kotlin 里,运算符重载函数允许为自定义类型重

Python datetime 模块概述及应用场景

《Pythondatetime模块概述及应用场景》Python的datetime模块是标准库中用于处理日期和时间的核心模块,本文给大家介绍Pythondatetime模块概述及应用场景,感兴趣的朋... 目录一、python datetime 模块概述二、datetime 模块核心类解析三、日期时间格式化与

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

Java Spring 中 @PostConstruct 注解使用原理及常见场景

《JavaSpring中@PostConstruct注解使用原理及常见场景》在JavaSpring中,@PostConstruct注解是一个非常实用的功能,它允许开发者在Spring容器完全初... 目录一、@PostConstruct 注解概述二、@PostConstruct 注解的基本使用2.1 基本代

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

SpringBoot条件注解核心作用与使用场景详解

《SpringBoot条件注解核心作用与使用场景详解》SpringBoot的条件注解为开发者提供了强大的动态配置能力,理解其原理和适用场景是构建灵活、可扩展应用的关键,本文将系统梳理所有常用的条件注... 目录引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、@ConditionalOn