LV010-coredump简介
Segmentation fault 这个提示应该不陌生,写过 c 语言的应该都知道,这意味着程序崩溃啦。那崩溃在哪了?要是崩溃前没有任何打印信息,该怎么找到出问题的范围,要是个大工程,那怎么找崩溃的地方?
一、核心转储
我们先来看看什么是核心转储,上面的 Segmentation fault 在安装有中文环境的终端中是这样提示的:
段错误 (核心已转储)1. 是什么?
在 Linux 系统中,常 将“主内存”称为核心(core), 而 核心映像(core image) 就是 “进程”(process)执行当时的内存内容。
当进程发生错误或收到“信号”(signal) 而终止执行时,操作系统将该进程的内存状态、寄存器值、堆栈信息等关键数据保存到一个磁盘文件中。这个文件通常命名为 core 或 core.pid(pid 为进程 ID),以作为调试之用,这就是所谓的 核心转储(core dump)。
"核心"(Core)一词源于早期的磁芯存储器技术,现在泛指内存;"转储"(Dump)指的是将内存内容完整导出到磁盘文件。
2. 包含哪些内容?
(1)程序内存映像:包括代码段、数据段、堆和栈
(2)处理器寄存器状态:程序计数器、栈指针等
(3)线程信息:多线程程序中各线程的状态
(4)操作系统信息:信号信息、内存映射等
(4)调试符号(如果编译时包含)
3. 怎么生成?
3.1 是否打开
Linux 默认没有打开 core 文件生成功能,也就是发生段错误时不会 core dumped。可以执行 ulimit 命令查看资源限制。
# ulimit 用于查看或设置当前 shell 及其启动的子进程的资源使用上限。
ulimit -a # 列出所有当前限制
ulimit -c # core dump 文件大小上限(KB)可以看到如下信息:
root@lubancat:~# ulimit -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15458
max locked memory (kbytes, -l) 498672
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15458
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
root@lubancat:~# ulimit -c
03.2 打开 core dumped
可以通过以下命令打开 core 文件的生成:
# 不限制产生 core 的大小
ulimit -c unlimitedunlimited 意思是系统 不限制 core 文件的大小,只要有足够的磁盘空间,会转存程序所占用的全部内存,如果需要限制系统产生 core 的大小,可以使用以下命令:
# core 最大限制大小为 409600 字节
ulimit -c 409600把核心转储功能关闭,只需要将限制大小设为 0 即可:
ulimit -c 04. core_pattern
4.1 是什么?
/proc/sys/kernel/core_pattern 于定义 进程崩溃时生成的 core dump 文件名或处理方式。它决定了 当进程因致命信号(如 SIGSEGV)崩溃时,内核将产生的 core dump 写到哪里、以什么格式命名,或者交给哪个用户态程序处理。
ulimit -c 只决定“允不允许生成,以及允许生成多大的”core 文件,而 core_pattern 决定了“生成了之后怎么处理”。我们可以看一下它的内容:
# ubuntu
✗ sumu@virtual-machine:~/workspace/c-learning/02-c-basic/21-debug [main ≡ +1 ~0 -0 !] $ cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E
# lubancat2
root@lubancat:~# cat /proc/sys/kernel/core_pattern
core一般来说默认值为 core,但可通过修改该文件实现自定义命名或将 core 数据传递给用户空间程序。但是 ubuntu 中默认值好像不是 core。这个文件一般有两种模式:
- 模式 1:直接指定文件路径
绝对路径(如 /var/coredumps/core.%e.%p),内核会按这个路径和文件名写入 core 文件。相对路径(如 core 或 core.%p),文件会写入 崩溃进程的当前工作目录。这是传统的 Unix 默认行为。
- 模式 2:管道传递(pipe)
以 | 开头,后跟一个用户态程序的绝对路径和参数。例如前面 ubuntu 中的 core_pattern。这种形式下,内核不会自动在磁盘上创建 core 文件,一切交由该 apport 程序处理。
4.2 有什么用?
主要就是统一管理 core 文件的存放位置。例如把 /var/core/core.%e.%p.%t 设进去,所有程序的 core dump 都集中到一处,按进程名、PID 和时间命名,方便管理,也能防止 core 文件杂乱污染工作目录。
4.3 基本规则
ore_pattern 用于指定核心转储模式名称。
(1)最大长度为 127 个字符;默认值为 “core”
(2)core_pattern 用作输出文件名的模式模板;某些字符串模式(以“%”开头)将替换为其真实值。
(3)与 core_uses_pid 向后兼容。如果 core_pattern 不包含 “%p”(默认不包含)并且设置了 core_uses_pid,则 .PID 将附加到文件名。
(4)如果 core_pattern 不包含 “%p”(默认不包含)并且设置了 core_uses_pid,则 .PID 将附加到文件名。
%<NUL> | “%” 被删除 |
%% | 输出一个 “%” |
%p | pid |
%P | 全局 pid(初始化 PID 命名空间) |
%i | tid |
%I | 全局 tid(初始化 PID 命名空间) |
%u | uid(在初始用户命名空间中) |
%g | gid(在初始用户命名空间中) |
%d | 转储模式,匹配 |
%s | 信号编号 |
%t | 转储的 UNIX 时间 |
%h | 主机名 |
%e | 可执行文件名(可能被缩短,可能被 prctl 等更改) |
%f | 可执行文件名 |
%E | 可执行路径 |
%c | 核心文件的最大大小,由资源限制 RLIMIT_CORE 决定 |
%C | 任务在其上运行的 CPU |
%<OTHER> | 两者都被删除 |
(4)如果模式的第一个字符是“|”,则内核会将模式的其余部分视为要运行的命令。核心转储将写入该程序的标准输入,而不是写入文件。
5. core_uses_pid
/proc/sys/kernel/core_uses_pid 文件是一个控制开关,决定是否在 core dump 文件名中自动添加进程ID (PID) 后缀,它也只有这一个功能。它在内核生成 core 文件的流程中,提供了比 core_pattern 更早、也更简单的一种命名方式。
当 /proc/sys/kernel/core_pattern 已经被配置时,core_uses_pid 的设置会有条件地被忽略。如果 core_pattern 中已经包含了代表 PID 的 %p 格式符,core_uses_pid 的设置会被完全忽略。此时文件命名完全由 core_pattern 决定。如果 core_pattern 没有包含 %p,且 core_uses_pid 设置为 1,内核会出于兼容性考虑,自动将 “.PID字符串” 追加到最终文件名的末尾。
二、sysctl.conf
/proc/sys目录下存放着大多数内核参数,并且可以在系统运行时进行更改,不过重新启动机器就会失效。像刚才的/proc/sys/kernel/core_uses_pid和/proc/sys/kernel/core_pattern就是,配置完重启后就会失效。想让它不失效的话,可以将其配置放入环境变量配置文件中或者在某个进程中配置一次,还有就是可以在/etc/sysctl.conf 文件中配置。
1. 是什么?
/etc/sysctl.conf 是 Linux 系统中用于 永久修改内核运行时参数 的核心配置文件。它通过 sysctl 工具实现参数的持久化存储 ,确保系统重启后配置依然生效。
还有一个/etc/sysctl.d目录,内部也是一些 .conf 文件,这些文件都会被系统加载,系统启动时按以下顺序加载配置(后加载的配置覆盖前者的冲突项):
(1)/etc/sysctl.conf
(2)/etc/sysctl.d/*.conf(按文件名字母顺序加载)
2. 基本命令
2.1 手动加载
# 加载所有配置文件(包括 /etc/sysctl.d/)
sudo sysctl --system
# 加载所有配置文件(包括 sysctl.d/)
sudo sysctl -p
# 加载指定文件
sudo sysctl -p /etc/sysctl.d/99-custom.conf
# 查看所有当前生效的参数
sudo sysctl -a2.2 验证参数
# 检查参数是否生效
sysctl 参数名
# 示例:
sysctl net.ipv4.ip_forward3. 文件格式
/etc/sysctl.conf就是一个纯文本文件,格式为 参数路径 = 值,例如:
net.ipv4.ip_forward = 1参数路径就是把
/proc/sys/下的路径中的/换成.。例如/proc/sys/net/ipv4/ip_forward→net.ipv4.ip_forward#开头为注释。
4. coredump 配置
我们可以在这个/etc/sysctl.conf文件或者在/etc/sysctl.d/新建一个conf文件,写上下面的内容:
# 指定 core 文件存放路径和命名格式:core.%e.%p.%t → core.可执行文件名.pid.转储unix时间
kernel.core_pattern = core.%e.%p.%t
# 是否在 core 文件名后自动追加 .PID(当 core_pattern 不含 %p 时生效)
kernel.core_uses_pid = 1
# 是否允许 setuid 程序生成 core dump(安全相关,0=禁止, 1=允许)
fs.suid_dumpable = 2不过需要注意,这里只是配置coredump的生成格式和位置,和配置/proc/sys/kernel/core_pattern效果一样,只是这里配置后重启系统也不需要重新配置,但是还是ulimit -c 决定是否允许生成以及文件大小限制。这两者缺一不可,ulimit -c 为 0 时即使 core_pattern 配置正确也不会生成文件。
三、coredump文件内容
1. 怎么查看?
后面会写一些实例,我们生成的coredump文件其实是二进制的,无法直接阅读,需要借助gdb工具将其转为文本信息,可以使用下面的命令来转:
gdb -c coredump_file -batch -ex "bt" -ex "info registers" -ex "info threads"参数说明如下:
| 参数 | 含义 |
|---|---|
gdb | GNU 调试器,用于分析程序崩溃 |
-c core.a.out.206659.1777718287 | 指定要分析的 coredump 文件(-c 表示 core file) |
-batch | 批处理模式,执行完命令后自动退出,不进入交互界面 |
-ex "bt" | 执行命令 bt(backtrace),显示函数调用栈 |
-ex "info registers" | 执行命令显示 CPU 寄存器状态 |
-ex "info threads" | 执行命令显示线程信息 |
当需要在ubuntu或者编译服务器中解析arm开发板的coredump文件时,需要用对应的交叉编译工具中的gdb。
2. 源代码行号?
当我们编译了带调试信息的可执行程序时,还可以显示程序崩溃的行号,可以使用下面的命令:
gcc -g src.c -o target.out
gdb ./target.out -c coredump_file -batch -ex "bt" -ex "info registers"添加了-g选项后,GDB 会将 DW_AT_comp_dir + / + DW_AT_name 拼接成完整的源代码路径嵌入可执行文件,当含有这两个字段并且源代码路径存在时,GDB可以直接显示崩溃行的代码,这个后面的实例中会看到。另外当需要在ubuntu或者编译服务器中解析arm开发板的coredump文件时,需要用对应的交叉编译工具中的gdb。
3. 自动解析?
要是开发板使用的芯片性能够强,内存够大,完全可以安装gdb的话,我们可以再写一个进程,这个进程中监控另一个程序的心跳(另一个程序定时发送数据给监控线程),当检测不到时,就扫描coredump文件,直接调用gdb来转成可阅读的txt文本文件。
还有一种方案就是当进程崩溃时,内核会依据 /proc/sys/kernel/core_pattern 的内容决定如何处理 core dump。如果该值以 | 开头,内核会把 core dump 的数据通过管道传给指定的用户态程序,同时可以附带一系列参数(如 PID、可执行文件路径等)。所以我们可以自己写一个脚本,在这个脚本中完成对coredump的处理。
参考资料:
Linux 核心转储(Core Dump)原理、配置与调试实践_coredump-CSDN 博客