Skip to content

LV010-coredump简介

Segmentation fault 这个提示应该不陌生,写过 c 语言的应该都知道,这意味着程序崩溃啦。那崩溃在哪了?要是崩溃前没有任何打印信息,该怎么找到出问题的范围,要是个大工程,那怎么找崩溃的地方?

一、核心转储

我们先来看看什么是核心转储,上面的 Segmentation fault 在安装有中文环境的终端中是这样提示的:

shell
段错误 (核心已转储)

1. 是什么?

在 Linux 系统中,常 将“主内存”称为核心(core),核心映像(core image) 就是 “进程”(process)执行当时的内存内容

当进程发生错误或收到“信号”(signal) 而终止执行时,操作系统将该进程的内存状态、寄存器值、堆栈信息等关键数据保存到一个磁盘文件中。这个文件通常命名为 corecore.pid(pid 为进程 ID),以作为调试之用,这就是所谓的 核心转储(core dump)

"核心"(Core)一词源于早期的磁芯存储器技术,现在泛指内存;"转储"(Dump)指的是将内存内容完整导出到磁盘文件。

2. 包含哪些内容?

(1)程序内存映像:包括代码段、数据段、堆和栈

(2)处理器寄存器状态:程序计数器、栈指针等

(3)线程信息:多线程程序中各线程的状态

(4)操作系统信息:信号信息、内存映射等

(4)调试符号(如果编译时包含)

3. 怎么生成?

3.1 是否打开

Linux 默认没有打开 core 文件生成功能,也就是发生段错误时不会 core dumped。可以执行 ulimit 命令查看资源限制。

shell
# ulimit 用于查看或设置当前 shell 及其启动的子进程的资源使用上限。
ulimit -a   # 列出所有当前限制
ulimit -c	# core dump 文件大小上限(KB)

可以看到如下信息:

shell
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
0

3.2 打开 core dumped

可以通过以下命令打开 core 文件的生成:

shell
# 不限制产生 core 的大小
ulimit -c unlimited

unlimited 意思是系统 不限制 core 文件的大小,只要有足够的磁盘空间,会转存程序所占用的全部内存,如果需要限制系统产生 core 的大小,可以使用以下命令:

c
# core 最大限制大小为 409600 字节
ulimit -c 409600

把核心转储功能关闭,只需要将限制大小设为 0 即可:

c
ulimit -c 0

4. core_pattern

4.1 是什么?

Tips:core(5) - Linux manual page

/proc/sys/kernel/core_pattern 于定义 进程崩溃时生成的 core dump 文件名或处理方式。它决定了 当进程因致命信号(如 SIGSEGV)崩溃时,内核将产生的 core dump 写到哪里、以什么格式命名,或者交给哪个用户态程序处理

ulimit -c 只决定“允不允许生成,以及允许生成多大的”core 文件,而 core_pattern 决定了“生成了之后怎么处理”。我们可以看一下它的内容:

shell
# 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 文件。相对路径(如 corecore.%p),文件会写入 崩溃进程的当前工作目录。这是传统的 Unix 默认行为。

  • 模式 2:管道传递(pipe)

| 开头,后跟一个用户态程序的绝对路径和参数。例如前面 ubuntu 中的 core_pattern。这种形式下,内核不会自动在磁盘上创建 core 文件,一切交由该 apport 程序处理。

4.2 有什么用?

主要就是统一管理 core 文件的存放位置。例如把 /var/core/core.%e.%p.%t 设进去,所有程序的 core dump 都集中到一处,按进程名、PID 和时间命名,方便管理,也能防止 core 文件杂乱污染工作目录

4.3 基本规则

Tips:关于 /proc/sys/kernel/ 的文档 — Linux 内核文档 - Linux 内核

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

转储模式,匹配 PR_SET_DUMPABLE/proc/sys/fs/suid_dumpable

%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 手动加载

shell
# 加载所有配置文件(包括 /etc/sysctl.d/)
sudo sysctl --system

# 加载所有配置文件(包括 sysctl.d/)
sudo sysctl -p

# 加载指定文件
sudo sysctl -p /etc/sysctl.d/99-custom.conf

# 查看所有当前生效的参数
sudo sysctl -a

2.2 验证参数

shell
# 检查参数是否生效
sysctl 参数名
# 示例:
sysctl net.ipv4.ip_forward

3. 文件格式

/etc/sysctl.conf就是一个纯文本文件,格式为 参数路径 = 值,例如:

text
net.ipv4.ip_forward = 1
  • 参数路径就是把 /proc/sys/ 下的路径中的 / 换成 .。例如 /proc/sys/net/ipv4/ip_forwardnet.ipv4.ip_forward

  • # 开头为注释。

4. coredump 配置

我们可以在这个/etc/sysctl.conf文件或者在/etc/sysctl.d/新建一个conf文件,写上下面的内容:

text
# 指定 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工具将其转为文本信息,可以使用下面的命令来转:

shell
gdb -c coredump_file -batch -ex "bt" -ex "info registers" -ex "info threads"

参数说明如下:

参数含义
gdbGNU 调试器,用于分析程序崩溃
-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. 源代码行号?

当我们编译了带调试信息的可执行程序时,还可以显示程序崩溃的行号,可以使用下面的命令:

shell
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 博客

Linux系统core文件设置方法_core pattern设置-CSDN博客

Linux的 /etc/sysctl.conf 笔记250404-CSDN博客