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

相关文章

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