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

2024-06-03 02:28
文章标签 场景 60 segmentfault

本文主要是介绍60 关于 SegmentFault 的一些场景 (1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

呵呵 此问题主要是来自于 帖子 月经结贴 -- 《Segmentation Fault in Linux》

这里主要也是 结合了作者的相关 case, 来做的一些 调试分享 

当然 很多的情况还是 蛮有意思 

 

本文主要问题如下 

1. 访问可执行文件中的 只读数据
2. 访问不存在的虚拟地址
3. 访问内核地址
4. 访问空指针
5. 访问异常堆地址1
6. 访问异常堆地址2
7. 访问异常堆地址3

 

 

1. 访问可执行文件中的 只读数据

比如如下数据, “Hello World” 会被放到 .text 段, 该段只读, 这里程序中试图更新该内存的数据 

#include <stdio.h>
#include <stdlib.h>int main() {char* s = "Hello World";s[1] = 'x';}

 

调试上下文如下 

page fault 的时候 ip 为 4195562 = 0x4004EA

error_code 为 7, PF_PROT | PF_WRITE | PF_USER

40dbcff97a0f44afa8f0bd3f6d18dba0.png

 

0x4004EA 对应的信息如下, 是一段执行代码 

07a7943c31f747159db10eed73f75a78.png

 

对应于 main 中的 如下代码, 映射到业务源代码就是 “s[1] = 'x';”

c7e70030529041989f609294980eee75.png

 

校验的时候 期望写操作, 但是实际 不支持写操作 

d032a0c5c1ed42e69c18b0f2840890a9.png

 

接下来就是 输出内核日志信息, 以及向目标进程发送 SIGSEGV 信号  1e23f599874c40c9afbc89f4881336c8.png

 

输出内核日志信息如下

日志中输出了 进程名称, 进程编号, 访问的地址, 指令寄存器, 栈顶寄存器, 错误编码 等等信息

3f493281ead14310b536c4f7ca1f382c.png

 

出现问题的异常代码为 0x4004ea, 栈顶寄存器的值为 0x7ffdc7c9b1f0

错误编码为 7 表示 PF_PROT | PF_WRITE | PF_USER

(initramfs) ./Test16SigSegvAccessConstants

[  207.776273] Test16SigSegvAc[258]: segfault at 400585 ip 00000000004004ea sp 00007ffdc7c9b1f0 error 7 in Test16SigSegvAccessConstants[400000+1000]

 

0x400585 为 .rodata 中 

d8b772dc60aa4ba494510dbc0a431816.png

 

0x4004ea 为 main 中执行出现异常的代码段 

f0d040600ba04069b94d20ed14c9c827.png

 

 

2. 访问不存在的虚拟地址  

#include <stdio.h>
#include <stdlib.h>int main() {int *p = (int *) 0x7ffff7a8e58f;*p = 10;}

 

这个是根据 address 查询虚拟地址, 查询不到

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

f36e05480c2f460aac5dbda97010e9de.png

 

报错日志信息为 

(initramfs) ./Test16SigSegvAccessUnknownAddr

[ 7575.969176] Test16SigSegvAc[262]: segfault at 7ffff7a8e58f ip 00000000004004ec sp 00007fffd34e74d0 error 6 in Test16SigSegvAccessUnknownAddr [400000+1000]

 

出现问题的进程为 262号进程, 异常访问的地址为 0x 7ffff7a8e58f

出现问题的异常代码为 0x4004ec, 栈顶寄存器的值为 0x 7fffd34e74d0

错误编码为 6 表示 PF_WRITE | PF_USER

 

0x7ffff7a8e58f 为 main 中定义的需要访问的异常地址 

 

0x4004ec 为 main 中执行出现异常的代码段 

f053ec239aba4f6d904db8e3c0e72d17.png

 

 

3. 访问内核地址

#include <stdio.h>
#include <stdlib.h>int main() {int *p = (int *) 0xffff88007fb89a80;*p = 10;}

 

如果是访问内核空间的地址

如果是普通用户程序访问, 直接发送 SIGSEGV 信号量 

91b8ed9a19e5415b970a81ff5a744042.png

 

报错日志信息为 

(initramfs) ./Test16SigSegvAccessKernelAddr

[ 1014.007466] Test16SigSegvAc[259]: segfault at ffff88007fb89a80 ip 00000000004004ec sp 00007fffc027d130 error 7 in Test16SigSegvAccessKernelAddr[400000+1000]

 

出现问题的进程为 259号进程, 异常访问的地址为 0x ffff88007fb89a80

出现问题的异常代码为 0x4004ec, 栈顶寄存器的值为 0x 7fffc027d130

错误编码为 7 表示 PF_PROT | PF_WRITE | PF_USER

 

0x ffff88007fb89a80 为 main 中定义的需要访问的异常地址 

 

0x4004ec 为 main 中执行出现异常的代码段 

6d525d21e10944b8a1acf3bab804cdb8.png

 

 

4. 访问空指针

#include <stdio.h>
#include <stdlib.h>int main() {int *p = NULL;*p = 10;}

 

这个是根据 address 查询虚拟地址, 查询不到

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

d3cee4862dc448fa908a580c86f9cb7f.png

 

 

报错日志信息为 

(initramfs) ./Test16SigSegvAccessNpe

[ 9696.656307] Test16SigSegvAc[264]: segfault at 0 ip 00000000004004e6 sp 00007fffd2d459c0 error 6 in Test16SigSegvAccessNpe[400000+1000]

 

出现问题的进程为 264号进程, 异常访问的地址为 0x 0

出现问题的异常代码为 0x4004e6, 栈顶寄存器的值为 0x 7fffd2d459c0

错误编码为 6 表示 PF_WRITE | PF_USER

 

0x0 为 main 中定义的需要访问的异常地址 

 

0x4004e6 为 main 中执行出现异常的代码段 

39ceb0c272dd4146ae020ba38d09b442.png

 

 

5. 访问异常堆地址1

这里调整了一下 原文档中的测试用例, 源文档中作者的理解应该是存在问题 

所以 当我看到 overflow 15k 的时候很奇怪, 源文档中每次增量是 0k, 1k, 2k, 3k, …, 15k

但是 按照原作者的期望应该每次 增量是 1k, 这里我们稍微 调整了一下 测试用例

然后 原作者文档中提到当初次分配16M的时候, SISSEGV 延迟到了 180k, 这个 按道理来说作者的理解应该也是存在问题, 初次分配 16M的时候 malloc 分配的虚拟地址是在 mmap 映射区 这两种情况 得分开讨论

#include <stdio.h>
#include <stdlib.h>#define K 1024
int main () {char* c;int i = 0;c = malloc (1);while (1) {char* off = c + i*K;*off = 'a';printf ("overflow %dK\n", i);i ++;}
}

 

按照我们对于 malloc 的理解, 程序开始的时候 malloc 分配的 chunk 会在 132kb 左右 

这里 malloc(1) 会暂用 32byte, printf 会占用 1kb 左右 

然后 第一个循环中 操作的是 c 所在的内存空间, 第二个循环 操作的是 printf 的缓冲区 

到后面 132kb 末尾, 每 4kb 会有一个缺页中断, 操作的是对应的偏移的空间 

超过 132kb 之后, 会因为找不到 vma, 而发生 SIGSEGV

 

 

6. 访问异常堆地址2

#include <stdio.h>
#include <stdlib.h>#define K 1024
int main () {int* a;a = malloc (sizeof(int));*a = 100;printf ("0x%x\n", a);printf ("%d\n", *a);free (a);printf ("%d\n", *a);
}

 

这个测试用例不会报错很正常 

因为 malloc, free 维护的空间, 不管 free 之前还是在之后, 其申请的虚拟地址空间 都属于当前进程

malloc(sizeof(int)) 会申请 132kb 的空间 

然后 a 对应的地址会为 0x602010, 然后 这块地址 可读可写

不会 出现 SIGSEGV 

这里 page_fault 产生的 address 为 0x602008 是因为是在 malloc 的过程中设置这块空间的头部信息, 这里会走正常的缺页中断 

fa7a0970a58a4400b984e0fd09caf33f.png

 

这里 走正常的缺页中断处理

第二次访问的时候, 地址合法, 并且 虚拟内存对应的物理内存已经加载 

ce234a8efadf488db1f7ba49f696c239.png

 

我们大致看一下这个过程中 glibc 的 free 的相关处理 

这里两次输出之所以 第二次值为 0, 是因为 free 的时候需要在 chunkptr 中维护空闲链表信息

这里是当前区域的 第一块空闲区域, 更新 p->forward 为 NULL, 值为 0

d2db6bc61d7246d8993521cf183200bd.png

 

调整一下代码, 我们从程序上面简单的验证一下 这里的 forward 的处理 

#include <stdio.h>
#include <stdlib.h>#define K 1024
int main () {int* a = malloc (sizeof(int));int* b = malloc (sizeof(int));*a = 100;printf ("0x%x\n", a);printf ("%d\n", *a);free (a);printf ("%d\n", *a);*b = 100;printf ("0x%x\n", b);printf ("%d\n", *b);free (b);printf ("%d\n", *b);}

 

b 对应的 chunkptr->fd[等价于b的数据空间] 为 6299648 为 0x602000, 记录的是前一块 空闲的chunkptr 的地址

a 对应的 chunkptr->fd[等价于a的数据空间] 为 0 为 NULL, 记录的是前一块空闲的 chunkptr 的地址

root@ubuntu:~/Desktop/linux/HelloWorld# ./Test16SigSegvAccessInvalidHeapAddr02
0x602010
100
0
0x602030
100
6299648

 

 

7. 访问异常堆地址3

这个主要是在 glibc 层面的限制, 处理 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>void foo () {char c;memset (&c, 0x55, 128);
}int main () {foo();
}

 

日志输出如下

8038f369d6894bb7bcc7ba647245a20a.png

 

在 glibc 层面处理如下, 输出了如上 日志信息, 然后向给定的进程 发送了 SIGABRT 信号  839bfcee47974c0cbfa7fbd120aa091c.png

 

内核这边 调试收到 SIG_ABRT 的信号的地方如下 

f6b42efa2b174b65b00c853921bd73ad.png

 

foo 编译结果如下 

b8ffa0c5636f4b92913f2d3ff9d45c00.png

 

 gdb 调试这个过程如下 

Reading symbols from Test16SigSegvAccessInvalidHeapAddr03...
(gdb) list
1
2       #include <stdio.h>
3       #include <stdlib.h>
4       #include <string.h>
5
6       void foo () {
7               char c;
8
9               memset (&c, 0x55, 128);
10      }
(gdb) b Test16SigSegvAccessInvalidHeapAddr03.c:9
Breakpoint 1 at 0x4005ad: file Test16SigSegvAccessInvalidHeapAddr03.c, line 9.
(gdb) run
Starting program: /root/linux/tmp/Test16SigSegvAccessInvalidHeapAddr03Breakpoint 1, foo () at Test16SigSegvAccessInvalidHeapAddr03.c:9
9               memset (&c, 0x55, 128);
(gdb) disassemble
Dump of assembler code for function foo:0x0000000000400596 <+0>:     push   %rbp0x0000000000400597 <+1>:     mov    %rsp,%rbp0x000000000040059a <+4>:     sub    $0x10,%rsp0x000000000040059e <+8>:     mov    %fs:0x28,%rax0x00000000004005a7 <+17>:    mov    %rax,-0x8(%rbp)0x00000000004005ab <+21>:    xor    %eax,%eax
=> 0x00000000004005ad <+23>:    lea    -0x9(%rbp),%rax0x00000000004005b1 <+27>:    mov    $0x80,%edx0x00000000004005b6 <+32>:    mov    $0x55,%esi0x00000000004005bb <+37>:    mov    %rax,%rdi0x00000000004005be <+40>:    call   0x400470 <memset@plt>0x00000000004005c3 <+45>:    nop0x00000000004005c4 <+46>:    mov    -0x8(%rbp),%rax0x00000000004005c8 <+50>:    xor    %fs:0x28,%rax0x00000000004005d1 <+59>:    je     0x4005d8 <foo+66>0x00000000004005d3 <+61>:    call   0x400460 <__stack_chk_fail@plt>0x00000000004005d8 <+66>:    leave0x00000000004005d9 <+67>:    ret
End of assembler dump.
(gdb) stepi
0x00000000004005b1      9               memset (&c, 0x55, 128);
(gdb) stepi
0x00000000004005b6      9               memset (&c, 0x55, 128);
(gdb) stepi
0x00000000004005bb      9               memset (&c, 0x55, 128);
(gdb) stepi
0x00000000004005be      9               memset (&c, 0x55, 128);
(gdb) stepi
0x0000000000400470 in memset@plt ()
(gdb) step
Single stepping until exit from function memset@plt,
which has no line number information.
foo () at Test16SigSegvAccessInvalidHeapAddr03.c:10
10      }
(gdb) stepi
0x00000000004005c4      10      }
(gdb) stepi
0x00000000004005c8      10      }
(gdb) stepi
0x00000000004005d1      10      }
(gdb) stepi
0x00000000004005d3      10      }
(gdb) disassemble
Dump of assembler code for function foo:0x0000000000400596 <+0>:     push   %rbp0x0000000000400597 <+1>:     mov    %rsp,%rbp0x000000000040059a <+4>:     sub    $0x10,%rsp0x000000000040059e <+8>:     mov    %fs:0x28,%rax0x00000000004005a7 <+17>:    mov    %rax,-0x8(%rbp)0x00000000004005ab <+21>:    xor    %eax,%eax0x00000000004005ad <+23>:    lea    -0x9(%rbp),%rax0x00000000004005b1 <+27>:    mov    $0x80,%edx0x00000000004005b6 <+32>:    mov    $0x55,%esi0x00000000004005bb <+37>:    mov    %rax,%rdi0x00000000004005be <+40>:    call   0x400470 <memset@plt>0x00000000004005c3 <+45>:    nop0x00000000004005c4 <+46>:    mov    -0x8(%rbp),%rax0x00000000004005c8 <+50>:    xor    %fs:0x28,%rax0x00000000004005d1 <+59>:    je     0x4005d8 <foo+66>
=> 0x00000000004005d3 <+61>:    call   0x400460 <__stack_chk_fail@plt>0x00000000004005d8 <+66>:    leave0x00000000004005d9 <+67>:    ret
End of assembler dump.

 

 

 

 

 

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



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

相关文章

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 输出格式化字符串示