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

相关文章

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Spring Security 前后端分离场景下的会话并发管理

《SpringSecurity前后端分离场景下的会话并发管理》本文介绍了在前后端分离架构下实现SpringSecurity会话并发管理的问题,传统Web开发中只需简单配置sessionManage... 目录背景分析传统 web 开发中的 sessionManagement 入口ConcurrentSess

99%的人都选错了! 路由器WiFi双频合一还是分开好的专业解析与适用场景探讨

《99%的人都选错了!路由器WiFi双频合一还是分开好的专业解析与适用场景探讨》关于双频路由器的“双频合一”与“分开使用”两种模式,用户往往存在诸多疑问,本文将从多个维度深入探讨这两种模式的优缺点,... 在如今“没有WiFi就等于与世隔绝”的时代,越来越多家庭、办公室都开始配置双频无线路由器。但你有没有注

深入解析Java NIO在高并发场景下的性能优化实践指南

《深入解析JavaNIO在高并发场景下的性能优化实践指南》随着互联网业务不断演进,对高并发、低延时网络服务的需求日益增长,本文将深入解析JavaNIO在高并发场景下的性能优化方法,希望对大家有所帮助... 目录简介一、技术背景与应用场景二、核心原理深入分析2.1 Selector多路复用2.2 Buffer

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

Java Stream流之GroupBy的用法及应用场景

《JavaStream流之GroupBy的用法及应用场景》本教程将详细介绍如何在Java中使用Stream流的groupby方法,包括基本用法和一些常见的实际应用场景,感兴趣的朋友一起看看吧... 目录Java Stream流之GroupBy的用法1. 前言2. 基础概念什么是 GroupBy?Stream

java如何实现高并发场景下三级缓存的数据一致性

《java如何实现高并发场景下三级缓存的数据一致性》这篇文章主要为大家详细介绍了java如何实现高并发场景下三级缓存的数据一致性,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 下面代码是一个使用Java和Redisson实现的三级缓存服务,主要功能包括:1.缓存结构:本地缓存:使

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

在MySQL中实现冷热数据分离的方法及使用场景底层原理解析

《在MySQL中实现冷热数据分离的方法及使用场景底层原理解析》MySQL冷热数据分离通过分表/分区策略、数据归档和索引优化,将频繁访问的热数据与冷数据分开存储,提升查询效率并降低存储成本,适用于高并发... 目录实现冷热数据分离1. 分表策略2. 使用分区表3. 数据归档与迁移在mysql中实现冷热数据分

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 命令的详细解析,结合实际应