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

相关文章

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

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Java中&和&&以及|和||的区别、应用场景和代码示例

《Java中&和&&以及|和||的区别、应用场景和代码示例》:本文主要介绍Java中的逻辑运算符&、&&、|和||的区别,包括它们在布尔和整数类型上的应用,文中通过代码介绍的非常详细,需要的朋友可... 目录前言1. & 和 &&代码示例2. | 和 ||代码示例3. 为什么要使用 & 和 | 而不是总是使

Java中Runnable和Callable的区别和联系及使用场景

《Java中Runnable和Callable的区别和联系及使用场景》Java多线程有两个重要的接口,Runnable和Callable,分别提供一个run方法和call方法,二者是有较大差异的,本文... 目录一、Runnable使用场景二、Callable的使用场景三、关于Future和FutureTa

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为