什么是段错误

下面是来自 Answers.com 的定义:

A segmentation fault (often shortened to segfault) is a particular error condition that can occur during the operation of computer software. In short, a segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (e.g., attempts to write to a read-only location, or to overwrite part of the operating system). Systems based on processors like the Motorola 68000 tend to refer to these events as Address or Bus errors.

Segmentation is one approach to memory management and protection in the operating system. It has been superseded by paging for most purposes, but much of the terminology of segmentation is still used, “segmentation fault” being an example. Some operating systems still have segmentation at some logical level although paging is used as the main memory management policy.

On Unix-like operating systems, a process that accesses invalid memory receives the SIGSEGV signal. On Microsoft Windows, a process that accesses invalid memory receives the STATUS_ACCESS_VIOLATION exception.

另外,网上还有个基本上对照的中文解释:

所谓的段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由 gdtr 来保存的,他是一个 48 位的寄存器,其中的 32 位是保存由它指向的 gdt 表,后 13 位保存相应于 gdt 的下标,最后 3 位包括了程序是否在内存中以及程序的在 cpu 中的运行级别,指向的 gdt 是由以 64 位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界访问,cpu 就会产生相应的异常保护,于是 segmentation fault 就出现了

通过上面的解释,段错误应该就是访问了不可访问的内存,这个内存区要么是不存在的,要么是受到系统保护的。

常见段错误举例

这里列举一下常见的段错误例子。

1 scanf 参数:把 &i 写为 i

  1. int i;
  2. scanf("%d", i);

分析:i 被定义后,数值是不确定的,而 scanf 把 i 的值当作参数传入 scanf,而 scanf 则会把 i 当成了地址,把用户输入的内容存入该处。而该地址因为随机,可能根本就不存在或者不合法。

2 sprintf/printf 参数:%d/%c 写成 %s

  1. int i = 10;
  2. printf("%s", i);

分析:打印字串时,实际上是打印某个地址开始的所有字符,而这里把整数作为参数传递过去,这个整数被当成了一个地址,然后 printf 从这个地址开始打印字符,直到某个位置上的值为 \0。如果这个整数代表的地址不存在或者不可访问,自然也是访问了不该访问的内存 —— segmentation fault。

3 数组访问越界

  1. char test[1];
  2. printf("%c", test[1000000000]);

注:也可能报告为 Bus Error,可能存在对未对齐的地址读或写。

4 写只读内存

  1. char *ptr = "test";
  2. strcpy(ptr, "TEST");

分析:ptr 被定义成了 “test”,是一个只读的内存段,不能直接写入,要写入需要用 malloc 从堆中分配或者定义成一个字符串数组。

5 堆栈溢出

  1. void main()
  2. {
  3. main();
  4. }

分析:上面实际上是一个死循环的递归调用,会造成堆栈溢出。

6 pthread_create() 失败后 pthread_join()

  1. #define THREAD_MAX_NUM
  2. pthread_t thread[THREAD_MAX_NUM];

分析:用 pthread_create() 创建了各个线程,然后用 pthread_join() 来等待线程的结束。刚开始直接等待,在创建线程都成功时,pthread_join() 能顺利等到各个线程结束,但是一旦创建线程失败,用 pthread_join() 来等待那个本不存在的线程时自然会存在未知内存的情况,从而导致段错误的发生。解决办法是:在创建线程之前,先初始化线程数组,在等待线程结束时,判断线程是否为初始值,如果是的话,说明线程并没有创建成功,所以就不能等拉。

7 小结

综上所有例子,

  • 定义了指针后记得初始化,在使用时记得判断是否为 NULL
  • 在使用数组时记得初始化,使用时要检查数组下标是否越界,数组元素是否存在等
  • 在变量处理时变量的格式控制是否合理等

其他的就需要根据经验不断积累,更多例子会不断追加到上述列表中。

另外,也务必掌握一些基本的分析和调试手段,即使在遇到新的这类问题时也知道如何应对。

5 分析和调试手段

分析方法除了最简便的 catchsegv 外,还有诸多办法,它们的应用场景各异。

5.1 catchsegv 原理

该工具就是用来扑获段错误的,它通过动态加载器(ld-linux.so)的预加载机制(PRELOAD)把一个事先写好的库(/lib/libSegFault.so)加载上,用于捕捉段错误的出错信息。

5.2 gdb 调试

  1. gdb ./segfault-scanf
  2. ...
  3. Reading symbols from ./segfault-scanf...done.
  4. (gdb) r
  5. Starting program: segfault-scanf
  6. 100
  7. Program received signal SIGSEGV, Segmentation fault.
  8. 0x00007ffff7a6b61a in _IO_vfscanf_internal (s=<optimized out>,
  9. format=<optimized out>, argptr=argptr@entry=0x7fffffffddc8,
  10. errp=errp@entry=0x0) at vfscanf.c:1857
  11. 1857 vfscanf.c: No such file or directory.
  12. (gdb) bt
  13. #0 0x00007ffff7a6b61a in _IO_vfscanf_internal (s=<optimized out>,
  14. format=<optimized out>, argptr=argptr@entry=0x7fffffffddc8,
  15. errp=errp@entry=0x0) at vfscanf.c:1857
  16. #1 0x00007ffff7a72399 in __isoc99_scanf (format=<optimized out>)
  17. at isoc99_scanf.c:37
  18. #2 0x0000000000400580 in main ()

5.3 coredump 分析

  1. $ ulimit -c 1024
  2. $ gdb segfault-scanf ./core
  3. Reading symbols from segfault-scanf...done.
  4. [New LWP 16913]
  5. Core was generated by `./segfault-scanf'.
  6. Program terminated with signal SIGSEGV, Segmentation fault.
  7. #0 0x00007fd2d24ec61a in _IO_vfscanf_internal (s=<optimized out>,
  8. format=<optimized out>, argptr=argptr@entry=0x7fff14dfa668,
  9. errp=errp@entry=0x0) at vfscanf.c:1857
  10. 1857 vfscanf.c: No such file or directory.

5.4 程序内捕获 SIGSEGV 信号并启动 gdb

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <signal.h>
  4. #include <string.h>
  5. void dump(int signo)
  6. {
  7. char buf[1024];
  8. char cmd[1024];
  9. FILE *fh;
  10. snprintf(buf, sizeof(buf), "/proc/%d/cmdline", getpid());
  11. if(!(fh = fopen(buf, "r")))
  12. exit(0);
  13. if(!fgets(buf, sizeof(buf), fh))
  14. exit(0);
  15. fclose(fh);
  16. if(buf[strlen(buf) - 1] == '\n')
  17. buf[strlen(buf) - 1] = '\0';
  18. snprintf(cmd, sizeof(cmd), "gdb %s %d", buf, getpid());
  19. system(cmd);
  20. exit(0);
  21. }
  22. int main(int argc, char *argv[])
  23. {
  24. int i;
  25. signal(SIGSEGV, &dump);
  26. scanf("%d\n", i);
  27. return 0;
  28. }

用法如下:

  1. $ gcc -g -rdynamic -o segfault-scanf segfault-scanf.c
  2. $ sudo ./segfault-scanf
  3. 100
  4. (gdb) bt
  5. #0 0x00007fb743e065cc in __libc_waitpid (pid=16988,
  6. stat_loc=stat_loc@entry=0x7fffb51d8fe0, options=options@entry=0)
  7. at ../sysdeps/unix/sysv/linux/waitpid.c:31
  8. #1 0x00007fb743d8b1d2 in do_system (line=<optimized out>)
  9. at ../sysdeps/posix/system.c:148
  10. #2 0x0000000000400ba1 in dump (signo=11) at segfault-scanf.c:21
  11. #3 <signal handler called>
  12. #4 0x00007fb743d9c61a in _IO_vfscanf_internal (s=<optimized out>,
  13. format=<optimized out>, argptr=argptr@entry=0x7fffb51da318,
  14. errp=errp@entry=0x0) at vfscanf.c:1857
  15. #5 0x00007fb743da3399 in __isoc99_scanf (format=<optimized out>)
  16. at isoc99_scanf.c:37
  17. #6 0x0000000000400bdd in main (argc=1, argv=0x7fffb51da508)
  18. at segfault-scanf.c:31

5.5 程序内捕获 SIGSEGV 信号并调用 backtrace 获取回调

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <signal.h>
  4. #include <string.h>
  5. #include <execinfo.h>
  6. void dump(int signo)
  7. {
  8. void *array[10];
  9. size_t size;
  10. char **strings;
  11. size_t i;
  12. size = backtrace (array, 10);
  13. strings = backtrace_symbols (array, size);
  14. printf ("Obtained %zd stack frames.\n", size);
  15. for (i = 0; i < size; i++)
  16. printf ("%s\n", strings[i]);
  17. free (strings);
  18. exit(0);
  19. }
  20. int main(int argc, char *argv[])
  21. {
  22. int i;
  23. signal(SIGSEGV, &dump);
  24. scanf("%d\n", i);
  25. return 0;
  26. }

用法如下:

  1. $ gcc -rdynamic -o ./segfault-scanf segfault-scanf.c
  2. $ ./segfault-scanf
  3. 100
  4. Obtained 7 stack frames.
  5. ./segfault-scanf() [0x40077e]
  6. /lib/x86_64-linux-gnu/libc.so.6(+0x36c30) [0x7f249fa43c30]
  7. /lib/x86_64-linux-gnu/libc.so.6(_IO_vfscanf+0x303a) [0x7f249fa6461a]
  8. /lib/x86_64-linux-gnu/libc.so.6(__isoc99_scanf+0x109) [0x7f249fa6b399]
  9. ./segfault-scanf-call-backtrace() [0x400837]
  10. /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f249fa2eec5]
  11. ./segfault-scanf-call-backtrace() [0x400699]

除此之外,还可以通过 dmesg 查看内核信息并通过 objdump 或者 addr2line 把 IP 地址转化为代码行,不过用法没有 catchsegv 来得简单。dmesg 获取的内核信息由 arch/x86/mm/fault.c: show_signal_msg() 打印。

参考:

Linux 段错误详解 - 泰晓科技 (tinylab.org)

Linux环境下段错误的产生原因及调试方法小结_yuzeze的专栏-CSDN博客_catchsegv

linux Segmentation faults 段错误详解的更多相关文章

  1. Linux下的段错误(Segmentation fault)

    Linux开发中常见段错误问题原因分析 1 使用非法的内存地址(指针),包括使用未经初始化及已经释放的指针.不存在的地址.受系统保护的地址,只读的地址等,这一类也是最常见和最好解决的段错误问题,使用G ...

  2. Linux下的段错误(Segmentation fault)

    Linux下的段错误(Segmentation fault) 段错误是指:访问了系统分配给程序的内存空间之外起的内存空间,比如: 访问不存在的地址 访问受系统保护的地址 访问了只读内存地址 内存访问越 ...

  3. Linux内存管理之mmap详解

    转发之:http://blog.chinaunix.net/uid-26669729-id-3077015.html Linux内存管理之mmap详解 一. mmap系统调用 1. mmap系统调用  ...

  4. Linux驱动开发必看详解神秘内核(完全转载)

    Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html   IT168 技术文档]在开始步入L ...

  5. linux下getsockopt和setsockopt详解及测试

    linux下getsockopt和setsockopt详解及测试 NAME 名字 getsockopt, setsockopt - get and set options on sockets 获取或 ...

  6. linux route命令的使用详解 添加永久静态路由 tracert traceroute

    linux route命令的使用详解 添加永久静态路由  tracert  traceroute route -n    Linuxroute  print  Windows traceroute  ...

  7. 【Python】Linux crontab定时任务配置方法(详解)

    CRONTAB概念/介绍 crontab命令用于设置周期性被执行的指令.该命令从标准输入设备读取指令,并将其存放于“crontab”文件中,以供之后读取和执行. cron 系统调度进程. 可以使用它在 ...

  8. (转)linux 中特殊符号用法详解

    linux 中特殊符号用法详解 原文:https://www.cnblogs.com/lidabo/p/4323979.html # 井号 (comments)#管理员  $普通用户 脚本中 #!/b ...

  9. Linux上的free命令详解、swap机制

    Linux上的free命令详解   解释一下Linux上free命令的输出. 下面是free的运行结果,一共有4行.为了方便说明,我加上了列号.这样可以把free的输出看成一个二维数组FO(Free ...

随机推荐

  1. 垃圾分类app--NABCD--团队项目需求与分析

    我们的产品是--智能垃圾分类APP,它的设计灵感的来自于"可持续化发展战略,走绿色发展道路",众所周知,垃圾是放错了地方的资源,因此我们团队为了响应国家"垃圾分类&quo ...

  2. C++吃金币小游戏

    上图: 游戏规则:按A,D键向左和向右移动小棍子,$表示金币,0表示炸弹,吃到金币+10分,吃到炸弹就GAME OVER. 大体思路和打字游戏相同,都是使用数组,refresh和run函数进行,做了一 ...

  3. session入库

    #存储session的数据表示列结构,可作为参考#创建数据库(可选)CREATE DATABASE session;#使用创建的数据库(可选)USE session;#创建存储session的数据表( ...

  4. Oracle基本入门

    一.数据的存储 1.java 程序中的对象:数组.集合保存.当运行的程序结束的时候,里面的数据就消亡. 2.文件存储系统: 存在的缺陷: 2.1)没有明确的数据类型划分. 2.2)没有用户身份验证机制 ...

  5. CF755G-PolandBall and Many Other Balls【倍增FFT】

    正题 题目链接:https://www.luogu.com.cn/problem/CF755G 题目大意 \(n\)个东西排成一排,每个组可以选择一个单独的物品或者两个连续的物品,一个物品不同同时在两 ...

  6. 好未来数据中台 Node.js BFF实践(一):基础篇

    好未来数据中台 Node.js BFF实践系列文章列表: 基础篇 实战篇(TODO) 进阶篇(TODO) 好未来数据中台的Node.js中间层从7月份开始讨论可行性,截止到9月已经支持了4个平台,其中 ...

  7. NOIP模拟66

    T1 接力比赛 解题思路 其实就是一个背包 DP ,也没啥好说的也就是一个优化,每次枚举之前的前缀和. 比较妙的就是一个 random_shuffle 可以整掉部分卡人的数据(但是好像 sort 一下 ...

  8. vite首次启动加载慢

    背景 随着vue3的到来,vite开始被各大vue3组件库使用,公司开始一个新项目,准备尝试用vite试一波. 问题发现 当把公司新项目移植到vite后,启动非常快,但发现页渲染时间慢了很多 可以看到 ...

  9. MySQL8.0允许外部访问

    MySQL8.0允许外部访问     版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/h99 ...

  10. 关于web项目中的资源跳转

    1.跳转包括两种方式: 转发 forward 重定向 redirect 2.两种方式的代码: AServlet类: //向request范围中存储数据 request.setAttribute(&qu ...