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

相关文章

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

PostgreSQL核心功能特性与使用领域及场景分析

PostgreSQL有什么优点? 开源和免费 PostgreSQL是一个开源的数据库管理系统,可以免费使用和修改。这降低了企业的成本,并为开发者提供了一个活跃的社区和丰富的资源。 高度兼容 PostgreSQL支持多种操作系统(如Linux、Windows、macOS等)和编程语言(如C、C++、Java、Python、Ruby等),并提供了多种接口(如JDBC、ODBC、ADO.NET等

嵌入式技术的核心技术有哪些?请详细列举并解释每项技术的主要功能和应用场景。

嵌入式技术的核心技术包括处理器技术、IC技术和设计/验证技术。 1. 处理器技术    通用处理器:这类处理器适用于不同类型的应用,其主要特征是存储程序和通用的数据路径,使其能够处理各种计算任务。例如,在智能家居中,通用处理器可以用于控制和管理家庭设备,如灯光、空调和安全系统。    单用途处理器:这些处理器执行特定程序,如JPEG编解码器,专门用于视频信息的压缩或解压。在数字相机中,单用途

『功能项目』更换URP场景【32】

上一章已经将项目从普通管线升级到了URP管线 现在我们打开上一篇31项目优化 - 默认管线转URP的项目, 进入战斗场景 将Land的子级全部隐藏 将新的URP场景预制体拖拽至Land子级 对场景预制体完全解压缩 将Terrain拖拽至Land的直接子级 将Terrain设置为Land 与 静态Static 清除烘培 重新烘培 修改脚本:LoadRe

70-java write类应用场景

在Java中,我们可以使用java.io包中的FileWriter和BufferedWriter类来写入数据到文件。以下是一个简单的例子,展示了如何使用FileWriter和BufferedWriter来写入数据到文件: import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException;pub

消息队列的理解和应用场景

知乎上的一个通俗理解的优秀答案 by 祁达方 小红是小明的姐姐。 小红希望小明多读书,常寻找好书给小明看,之前的方式是这样:小红问小明什么时候有空,把书给小明送去,并亲眼监督小明读完书才走。久而久之,两人都觉得麻烦。 后来的方式改成了:小红对小明说「我放到书架上的书你都要看」,然后小红每次发现不错的书都放到书架上,小明则看到书架上有书就拿下来看。 书架就是一个消息队列,小红是生产者,小明是

828华为云征文|基于Flexus云服务器X实例的应用场景-拥有一款自己的ssl监控工具

先看这里 写在前面效果图华为云Flexus云服务器X实例介绍特点可选配置购买 连接服务器Uptime-kuma简介开源信息部署准备工作:docker部署命令访问uptime-kuma 基本配置总结 写在前面 作为一个个人开发者,相信你手里肯定也有不少自己的服务,有的服务呢也是https的。 以前ssl各厂都是可以免费申请一年的,我们更换的频率还好,比较小;但是最近,各厂都

【语音告警】博灵智能语音报警灯JavaScript循环播报场景实例-语音报警灯|声光报警器|网络信号灯

功能说明 本文将以JavaScript代码为实例,讲解如何通过JavaScript代码调用博灵语音通知终端 A4实现声光语音告警。主要博灵语音通知终端如何实现无线循环播报或者周期播报的功能。 本代码实现HTTP接口的声光语音播报,并指定循环次数、播报内容。由于通知终端采用TTS语音合成技术,所以本次案例中无需预先录制音频。 代码实战 为了通过JavaScript调用博灵语音通知终端,实现HT

文本分类场景下微调BERT

How to Fine-Tune BERT for Text Classification 论文《How to Fine-Tune BERT for Text Classification?》是2019年发表的一篇论文。这篇文章做了一些实验来分析了如何在文本分类场景下微调BERT,是后面网上讨论如何微调BERT时经常提到的论文。 结论与思路 先来看一下论文的实验结论: BERT模型上面的

黑神话:悟空》增加草地绘制距离MOD使游戏场景看起来更加广阔与自然,增强了游戏的沉浸式体验

《黑神话:悟空》增加草地绘制距离MOD为玩家提供了一种全新的视觉体验,通过扩展游戏中草地的绘制距离,增加了场景的深度和真实感。该MOD通过增加草地的绘制距离,使游戏场景看起来更加广阔与自然,增强了游戏的沉浸式体验。 增加草地绘制距离MOD安装 1、在%userprofile%AppDataLocalb1SavedConfigWindows目录下找到Engine.ini文件。 2、使用记事本编辑