在Linux上如何得到一个段错误的核心转储

什么是段错误?

“ 段错误(segmentation fault)”是指你的程序尝试访问不允许访问的内存地址的情况。这可能是由于:

  • 试图解引用空指针(你不被允许访问内存地址 0);
  • 试图解引用其他一些不在你内存(LCTT 译注:指不在合法的内存地址区间内)中的指针;
  • 一个已被破坏并且指向错误的地方的 C++ 虚表指针(C++ vtable pointer),这导致程序尝试执行没有执行权限的内存中的指令;
  • 其他一些我不明白的事情,比如我认为访问未对齐的内存地址也可能会导致段错误(LCTT 译注:在要求自然边界对齐的体系结构,如 MIPS、ARM 中更容易因非对齐访问产生段错误)。

这个“C++ 虚表指针”是我的程序发生段错误的情况。我可能会在未来的博客中解释这个,因为我最初并不知道任何关于 C++ 的知识,并且这种虚表查找导致程序段错误的情况也是我所不了解的。

但是!这篇博客后不是关于 C++ 问题的。让我们谈论的基本的东西,比如,我们如何得到一个核心转储?

步骤1:运行 valgrind

我发现找出为什么我的程序出现段错误的最简单的方式是使用 valgrind:我运行(我的arm-linux上没这玩意)

valgrind -v your-program

这给了我一个故障时的堆栈调用序列。 简洁!

但我想也希望做一个更深入调查,并找出些 valgrind 没告诉我的信息! 所以我想获得一个核心转储并探索它。

如何获得一个核心转储

核心转储(core dump)是您的程序内存的一个副本,并且当您试图调试您的有问题的程序哪里出错的时候它非常有用。

当您的程序出现段错误,Linux 的内核有时会把一个核心转储写到磁盘。 当我最初试图获得一个核心转储时,我很长一段时间非常沮丧,因为 - Linux 没有生成核心转储!我的核心转储在哪里?

这就是我最终做的事情:(这个arm-linux倒是可以启动)

1、在启动我的程序之前运行 ulimit -c unlimited
2、运行 sudo sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t

ulimit:设置核心转储的最大尺寸

ulimit -c 设置核心转储的最大尺寸。 它往往设置为 0,这意味着内核根本不会写核心转储。 它以千字节为单位。 ulimit 是按每个进程分别设置的 —— 你可以通过运行 cat /proc/PID/limit 看到一个进程的各种资源限制。(arm-linux上没cat)

例如这些是我的系统上一个随便一个 Firefox 进程的资源限制:

$ cat /proc/6309/limits 
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            8388608              unlimited            bytes     
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             30571                30571                processes 
Max open files            1024                 1048576              files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       30571                30571                signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us

内核在决定写入多大的核心转储文件时使用 软限制(soft limit)(在这种情况下,max core file size = 0)。 您可以使用 shell 内置命令 ulimit(ulimit -c unlimited) 将软限制增加到 硬限制(hard limit)。

kernel.core_pattern:核心转储保存在哪里

kernel.core_pattern 是一个内核参数,或者叫 “sysctl 设置”,它控制 Linux 内核将核心转储文件写到磁盘的哪里。

内核参数是一种设定您的系统全局设置的方法。您可以通过运行 sysctl -a 得到一个包含每个内核参数的列表,或使用 sysctl kernel.core_pattern 来专门查看 kernel.core_pattern 设置。

[root@RV1126_RV1109 /userdata/ky_ai_camera_error_test]# sysctl kernel.core_pattern
kernel.core_pattern = /tmp/core-%e.%p.%h.%t
[root@RV1126_RV1109 /userdata/ky_ai_camera_error_test]# 

所以 sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t 将核心转储保存到目录 /tmp 下,并以 core 加上一系列能够标识(出故障的)进程的参数构成的后缀为文件名

如果你想知道这些形如 %e、%p 的参数都表示什么,请参考 man core

(👇有点难看懂)

核心转储文件的命名:

默认情况下,核心转储文件名为 core,但可以设置 /proc/sys/kernel/core_pattern 文件(自 Linux 2.6  2.4.21 起)来定义用于命名核心转储文件的模板。模板可以包含 % 说明符,在创建核心文件时,这些说明符会替换为以下值:

           %% 单个 % 字符。
           %c 崩溃进程的核心文件大小软资源限制(自 Linux 2.6.24)。
           %d 转储模式——与 prctl(2) PR_GET_DUMPABLE 返回的值相同(自 Linux 3.7 起)。
           %e 进程或线程的 comm 值,通常与可执行文件名相同(不带路径前缀,并截断为最多 15 个字符),但可能已被修改为不同的内容;参见 proc(5) 中对 /proc/[pid]/comm  /proc/[pid]/task/[tid]/comm 的讨论。
           %E 可执行文件的路径名,斜线 ('/') 替换为感叹号 ('!')(自 Linux 3.0 起)。
           %g 转储进程的数字真实 GID。
           %h 主机名(与 uname(2) 返回的节点名相同)。
           %i 触发核心转储的线程的 TID,如线程所在的 PID 命名空间中所见(自 Linux 3.18 起)。
           %I 触发核心转储的线程的 TID,如初始 PID 命名空间中所示(自 Linux 3.18 起)。
           %p 转储进程的 PID,如进程所在的 PID 命名空间中所见。
           %P 转储进程的 PID,如初始 PID 命名空间中所示(自 Linux 3.12 起)。
           %s 导致转储的信号数。
           %t 转储时间,表示为自纪元以来的秒数,1970-01-01 00:00:00 +0000 (UTC)
           %u 转储进程的数字真实 UID。

有一点很重要,kernel.core_pattern 是一个全局设置 —— 修改它的时候最好小心一点,因为有可能其它系统功能依赖于把它被设置为一个特定的方式(才能正常工作)。

kernel.core_pattern 和 Ubuntu

默认情况下在 ubuntu 系统中,kernel.core_pattern 被设置为下面的值:

$ sysctl kernel.core_pattern
kernel.core_pattern = |/usr/share/apport/apport %p %s %c %d %P

(果然是的)
在这里插入图片描述

这引起了我的迷惑(这 apport 是干什么的,它对我的核心转储做了什么?)。以下关于这个我了解到的:

  • Ubuntu 使用一种叫做 apport 的系统来报告 apt 包有关的崩溃信息。
  • 设定 kernel.core_pattern=|/usr/share/apport/apport %p %s %c %d %P 意味着核心转储将被通过管道送给 apport 程序。
  • apport 的日志保存在文件 /var/log/apport.log 中。
  • apport 默认会忽略来自不属于 Ubuntu 软件包一部分的二进制文件的崩溃信息
    我最终只是跳过了 apport,并把 kernel.core_pattern 重新设置为 sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t,因为我在一台开发机上,我不在乎 apport 是否工作,我也不想尝试让 apport 把我的核心转储留在磁盘上。

现在你有了核心转储,接下来干什么?

好的,现在我们了解了 ulimit 和 kernel.core_pattern ,并且实际上在磁盘的 /tmp 目录中有了一个核心转储文件。太好了!接下来干什么?我们仍然不知道该程序为什么会出现段错误!

下一步将使用 gdb 打开核心转储文件并获取堆栈调用序列。

(查看我的核心转储文件并把它拷贝到pc-linux下)

先运行报错的程序

[root@RV1126_RV1109 /userdata/ky_ai_camera_error_test]# ./run.sh 

然后一顿输出之后

……

在这里插入图片描述
然后我们打开根目录下的/tmp目录查看,里面果然有我们的转储文件core-ky_ai_rtsp_ssd.1740.RV1126_RV1109.172780

[root@RV1126_RV1109 /tmp]# ls -lah
total 20M
drwxr-xr-x  4 root root  200 Jan  2 23:59 .
drwxr-xr-x 30 root root 4.0K Jan  2 23:20 ..
-rwxr-xr-x  1 root root   11 Jan  1 00:00 .usb_config
-rwxr-xr-x  1 root root  65K Jan  1 00:16 cl_viv_vx_ext.h
-rw-------  1 root root 234M Jan  2 23:59 core-ky_ai_rtsp_ssd.1740.RV1126_RV1109.172780
----------  1 root root    6 Jan  3 00:26 loglevel
-rw-r--r--  1 root root 1.2K Jan  1 00:00 mountall.log
drwx------  2 root root   40 Jan  1 00:00 tmp.me4T9JcnbC
-rwxr-xr-x  1 root root 147K Jan  1 00:00 upgrade_tool
drwxrwxrwx  5 1004 1004  300 Jan  1 00:00 webs

用xftp将转储文件拷贝到pc-linux下,我将它放在arnold_core_dump_analysis这个文件夹中:

[root@ubuntu /home/yg/rv/sdk/rv1.8/arnold_core_dump_analysis]18# ls -lah
总用量 234M
drwxr-xr-x 2 yg hhh 4.0K Oct  2 16:35 .
drwxrwxrwx 6 yg hhh 4.0K Oct  2 16:34 ..
-rw-r--r-- 1 yg hhh 234M Oct  2 16:35 core-ky_ai_rtsp_ssd.1740.RV1126_RV1109.172780

从 gdb 中得到堆栈调用序列

你可以像这样用 gdb 打开一个核心转储文件:

$ gdb -c my_core_file

我这样打开:

[root@ubuntu /home/yg/rv/sdk/rv1.8/arnold_core_dump_analysis]19# gdb -c core-ky_ai_rtsp_ssd.1740.RV1126_RV1109.172780 
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".

warning: Couldn't find general-purpose registers in core file.

warning: Unexpected size of section `.reg2' in core file.
Core was generated by `./ky_ai_rtsp_ssd -a /opt/sbin/iqfiles -p model/rv1109_rv1126/yolov5s_relu_rv110'.

warning: Couldn't find general-purpose registers in core file.

warning: Unexpected size of section `.reg2' in core file.
#0  <unavailable> in ?? ()
(gdb) 

提示warning: Couldn't find general-purpose registers in core file.,上网查说是gdb版本太低,要升级下gdb版本

于是开始尝试升级gdb:arm-linux下如何安装GDB?pc-linux下如何升级GDB?

老yuan暂时把问题解决了,接下来先不升级gdb和查看核心转储,暂时告一段落

在Linux上如何得到一个段错误的核心转储

接下来,我们想知道程序崩溃时的堆栈是什么样的。在 gdb 提示符下运行 bt 会给你一个 调用序列(backtrace)。在我的例子里,gdb 没有为二进制文件加载符号信息,所以这些函数名就像 “???”。幸运的是,(我们通过)加载符号修复了它。

下面是如何加载调试符号。

symbol-file /path/to/my/binary
sharedlibrary
这从二进制文件及其引用的任何共享库中加载符号。一旦我这样做了,当我执行 bt 时,gdb 给了我一个带有行号的漂亮的堆栈跟踪!

如果你想它能工作,二进制文件应该以带有调试符号信息的方式被编译。在试图找出程序崩溃的原因时,堆栈跟踪中的行号非常有帮助。😃

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐