【Linux Debug】如何生成core-dump文件?
See the source image
1、core-dump文件
首先,我们来明白
core-dump
文件是什么?保存了哪些信息?主要作用是什么?
core-dump
文件,又称为核心转储,是操作系统在进程收到某些信号终止运行时,将此时进程的地址空间、进程状态以及其他信息写入到一个文件中,这个文件就是core-dump
文件,其主要是为了方便开发人员调试,定位问题。
2、core-dump如何生成
core-dump
文件是操作系统生成的,虽然是操作系统的事情,但是也得有个开关来把控吧!
那么如何生成
core-dump
文件呢?
2.1 打开core-dump开关
我们在命令行输入ulimit -a
来查看相关信息:
dong@ubuntu:~$ 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) 18811
max locked memory (kbytes, -l) 610792
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) 18811
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
注意core file size
,该项表示了core-dump
文件的大小限制,为0
表示关闭!
我们输入ulimit -c unlimited
,来将其大小设置为无限制,即可打开
2.2 路径设置
一般默认情况下,生成的core-dump
文件在我们进程的当前目录下,要是我们想自定义保存路径该怎么办?
修改 /proc/sys/kernel/core_pattern
文件!
例如:
代码语言:javascript复制echo "/data/coredump/corefile-%e-%p-%s-%t" > /proc/sys/kernel/core_pattern
/data/coredump/
:为要设置的路径信息,如果不加路径信息,默认生成在进程目录下。
corefile-%e-%p-%s-%t
:为设置的文件名称,这些特殊格式的意义如下:
#常用选项如下:
%p #出Core进程的PID
%u #出Core进程的UID
%s #造成Core的signal号
%t #出Core的时间,从1970-01-0100:00:00开始的秒数
%e #出Core进程对应的可执行文件名
#完整如下:
Naming of core dump files
By default, a core dump file is named core, but the /proc/sys/kernel/core_pattern file (since Linux 2.6 and
2.4.21) can be set to define a template that is used to name core dump files. The template can contain %
specifiers which are substituted by the following values when a core file is created:
%% a single % character
%c core file size soft resource limit of crashing process (since Linux 2.6.24)
%d dump mode—same as value returned by prctl(2) PR_GET_DUMPABLE (since Linux 3.7)
%e executable filename (without path prefix)
%E pathname of executable, with slashes ('/') replaced by exclamation marks ('!') (since Linux 3.0).
%g (numeric) real GID of dumped process
%h hostname (same as nodename returned by uname(2))
%i TID of thread that triggered core dump, as seen in the PID namespace in which the thread resides
(since Linux 3.18)
%I TID of thread that triggered core dump, as seen in the initial PID namespace (since Linux 3.18)
%p PID of dumped process, as seen in the PID namespace in which the process resides
%P PID of dumped process, as seen in the initial PID namespace (since Linux 3.12)
%s number of signal causing dump
%t time of dump, expressed as seconds since the Epoch, 1970-01-01 00:00:00 0000 (UTC)
%u (numeric) real UID of dumped process
设置完成后,我们输入cat /proc/sys/kernel/core_pattern
来查看文件是否设置成功!
到此,我们的
core-dump
就配置完成了,当发生崩溃或者异常终止的时候,就自动生成core-dump
文件了。
2.3 特殊信息配置
这一部分,发现很少有人描述到,再此也记录一下!
core-dump
信息还可以定制,在每个进程下,会有coredump_filter
的一个文件(/proc/<pid>/coredump_filter
),该文件会对core-dump
生成的信息进行定制!
dong@ubuntu:~$ cat /proc/2395/coredump_filter
00000033
一般coredump_filter
的值为0x33
,该值对应的信息如下:
- (bit 0) anonymous private memory
- (bit 1) anonymous shared memory
- (bit 2) file-backed private memory
- (bit 3) file-backed shared memory
- (bit 4) ELF header pages in file-backed private memory areas (it is effective only if the bit 2 is cleared)
- (bit 5) hugetlb private memory
- (bit 6) hugetlb shared memory
- (bit 7) DAX private memory
- (bit 8) DAX shared memory
默认情况下,包含发生coredump
时会将所有anonymous内存
、ELF头页面
、hugetlb private memory
内容保存。
3、什么情况下产生core-dump文件?
发生core-dump
一般都是在进程收到某个信号的时候,那么到底收到什么信号能够触发core-dump
文件生成呢?
Linux上现在大概有60多个信号,可以使用 kill -l
命令全部列出来。
dong@ubuntu:~/WorkSpace/Donge_Programs/Donge_Demo/build$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN 1 36) SIGRTMIN 2 37) SIGRTMIN 3
38) SIGRTMIN 4 39) SIGRTMIN 5 40) SIGRTMIN 6 41) SIGRTMIN 7 42) SIGRTMIN 8
43) SIGRTMIN 9 44) SIGRTMIN 10 45) SIGRTMIN 11 46) SIGRTMIN 12 47) SIGRTMIN 13
48) SIGRTMIN 14 49) SIGRTMIN 15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
我们可以输入man 7 signal
,来查看更加详细的SIGNAL
信号信息!
Signal Standard Action Comment
────────────────────────────────────────────────────────────────────────
SIGABRT P1990 Core Abort signal from abort(3)
SIGALRM P1990 Term Timer signal from alarm(2)
SIGBUS P2001 Core Bus error (bad memory access)
SIGCHLD P1990 Ign Child stopped or terminated
SIGCLD - Ign A synonym for SIGCHLD
SIGCONT P1990 Cont Continue if stopped
SIGEMT - Term Emulator trap
SIGFPE P1990 Core Floating-point exception
SIGHUP P1990 Term Hangup detected on controlling terminal
or death of controlling process
SIGILL P1990 Core Illegal Instruction
SIGINFO - A synonym for SIGPWR
SIGINT P1990 Term Interrupt from keyboard
SIGIO - Term I/O now possible (4.2BSD)
SIGIOT - Core IOT trap. A synonym for SIGABRT
SIGKILL P1990 Term Kill signal
SIGLOST - Term File lock lost (unused)
SIGPIPE P1990 Term Broken pipe: write to pipe with no
readers; see pipe(7)
SIGPOLL P2001 Term Pollable event (Sys V);
synonym for SIGIO
SIGPROF P2001 Term Profiling timer expired
SIGPWR - Term Power failure (System V)
SIGQUIT P1990 Core Quit from keyboard
SIGSEGV P1990 Core Invalid memory reference
SIGSTKFLT - Term Stack fault on coprocessor (unused)
SIGSTOP P1990 Stop Stop process
SIGTSTP P1990 Stop Stop typed at terminal
SIGSYS P2001 Core Bad system call (SVr4);
see also seccomp(2)
SIGTERM P1990 Term Termination signal
SIGTRAP P2001 Core Trace/breakpoint trap
SIGTTIN P1990 Stop Terminal input for background process
SIGTTOU P1990 Stop Terminal output for background process
SIGUNUSED - Core Synonymous with SIGSYS
SIGURG P2001 Ign Urgent condition on socket (4.2BSD)
SIGUSR1 P1990 Term User-defined signal 1
SIGUSR2 P1990 Term User-defined signal 2
SIGVTALRM P2001 Term Virtual alarm clock (4.2BSD)
SIGXCPU P2001 Core CPU time limit exceeded (4.2BSD);
see setrlimit(2)
SIGXFSZ P2001 Core File size limit exceeded (4.2BSD);
see setrlimit(2)
SIGWINCH - Ign Window resize signal (4.3BSD, Sun)
The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
我们可以看Action
这一列,该列有几项,分别为Term
、Core
、Ign
、Stop
、Cont
。
Term
:terminal,终止程序!Core
:dump core,核心转储!Ign
:ignore,忽略信号Stop
:stop,停止进程Cont
:continue,继续执行进程
4、触发core-dump的信号处理流程
由上文可知,操作系统在接收到某个信号后,进而生成
core-dump
文件,那么该信号处理流程是怎么样的呢?
4.1 信号处理逻辑
img
我们把这个过程拆分成信号的接收、检测、处理三个步骤。
- 信号接收:接收信号的任务由内核代理,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。注意,此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。
- 信号检测:进程陷入内核态后,有两种场景会对信号进行检测:
- 进程从内核态返回到用户态前进行信号检测
- 进程在内核态中,从睡眠状态被唤醒的时候进行信号检测
- 信号处理:信号处理函数是运行在用户态的,调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。接下来进程返回到用户态中,执行相应的信号处理函数。信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。
4.2 信号处理源码分析
img
进程从内核态返回到用户态的地方有很多,如
从系统调用返回
、从硬中断处理程序返回
和从进程调度程序返回
等。上图主要通过从进程调度程序返回
作为示例,来展示内核是怎么生成coredump
文件的。
4.2.1 do_signal()
当进程从 内核态
返回到 用户态
前,内核会查看进程的信号队列中是否有信号没有处理,如果有就调用 do_signal
内核函数处理信号。
static void fastcall do_signal(struct pt_regs *regs)
{
siginfo_t info;
int signr;
struct k_sigaction ka;
sigset_t *oldset;
...
signr = get_signal_to_deliver(&info, &ka, regs, NULL);
...
}
上面代码去掉了很多与生成
coredump
文件无关的逻辑,最终我们可以看到,do_signal
函数主要调用get_signal_to_deliver
内核函数来进行进一步的处理。
4.2.2 get_signal_to_deliver
get_signal_to_deliver
内核函数的主要工作是从进程的信号队列中获取一个信号,然后根据信号的类型来进行不同的操作。我们主要关注生成 coredump
文件相关的逻辑,如下代码:
int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
struct pt_regs *regs, void *cookie)
{
sigset_t *mask = ¤t->blocked;
int signr = 0;
...
for (;;) {
...
// 1. 从进程信号队列中获取一个信号
signr = dequeue_signal(current, mask, info);
...
// 2. 判断是否会生成 coredump 文件的信号
if (sig_kernel_coredump(signr)) {
// 3. 调用 do_coredump() 函数生成 coredump 文件
do_coredump((long)signr, signr, regs);
}
...
}
...
}
上面代码去掉了与生成 coredump
文件无关的逻辑,最后我们可以看到 get_signal_to_deliver
函数主要完成三个工作:
- 调用
dequeue_signal
函数从进程的信号队列中获取一个信号。 - 调用
sig_kernel_coredump
函数判断信号是否会生成coredump
文件。 - 如果信号会生成
coredump
文件,那么就调用do_coredump
函数生成coredump
文件。
4.2.3 do_coredump
如果要处理的信号会触发生成 coredump
文件,那么内核就会调用 do_coredump
函数来生成 coredump
文件。do_coredump
函数的实现如下:
int do_coredump(long signr, int exit_code, struct pt_regs *regs)
{
char corename[CORENAME_MAX_SIZE 1];
struct mm_struct *mm = current->mm;
struct linux_binfmt *binfmt;
struct inode *inode;
struct file *file;
int retval = 0;
int fsuid = current->fsuid;
int flag = 0;
int ispipe = 0;
binfmt = current->binfmt; // 当前进程所使用的可执行文件格式(如ELF格式)
...
// 1. 判断当前进程可生成的 coredump 文件大小是否受到资源限制
if (current->signal->rlim[RLIMIT_CORE].rlim_cur < binfmt->min_coredump)
goto fail_unlock;
...
// 2. 生成 coredump 文件名
ispipe = format_corename(corename, core_pattern, signr);
...
// 3. 创建 coredump 文件
file = filp_open(corename, O_CREAT|2|O_NOFOLLOW|O_LARGEFILE|flag, 0600);
...
// 4. 把进程的内存信息写入到 coredump 文件中
retval = binfmt->core_dump(signr, regs, file);
fail_unlock:
...
return retval;
}
经过代码精简后,最终可以看到 do_coredump
函数完成四个工作:
- 判断当前进程可生成的
coredump
文件大小是否受到资源限制。 - 如果不受限制,那么调用
format_corename
函数生成coredump
文件的文件名。 - 接着调用
filp_open
函数创建coredump
文件。 - 最后根据当前进程所使用的可执行文件格式来选择相应的填充方法来填充
coredump
文件的内容,对于ELF文件格式
使用的是elf_core_dump
方法。
elf_core_dump
方法的主要工作是:把进程的内存信息和内容写入到 coredump
文件中,并且以 ELF文件格式
作为 coredump
文件的存储格式。有兴趣的可以自行阅读 elf_core_dump
方法的代码,这里就不作进一步的解说了。
补充:
信号处理线程:信号可以发给整个进程,也可以发给特定线程;发给整个进程的信号,随机选取一个线程进行执行;发给特定线程的信号,只能有特定的线程负责处理。一些信号如果是某些线程代码的直接执行而引发,那么只能由特定的线程负责执行,例如SIGILL, SIGSEG
.
A signal may be directed to either the process as a whole or to a specific thread. A signal is thread-directed if it is generated as the direct result of the execution of a specific hardware instruction within the context of the thread (
SIGBUS, SIGFPE, SIGILL, and SIGSEGV
)应用例子,比如进程触发了
SIGSEG
异常,我在异常处理函数中进行了while
循环,最终仍然是卡死在异常线程的,此时可以看一下CPU
占用率,哪个最高就是哪个线程触发的!
4.3 生产环境要不要打开core-dump限制
最后,我们来讨论一下在生产环境应不应该打开
coredump
功能。
最近遇过在生产环境打开 coredump
功能而导致的事故,故事如下:
最近我们的应用程序概率性极低出现
SIGSEGV
段错误,无论是DGB
仿真还是排查代码,都不能直接定位到该问题所在。 为了能够抓到现场瞬间,我们就默认把coredump
选项打开了,以便能够捕获到core-dump
文件。 由于每次不单单只是SIGSEGV
会引发core-dump
文件,其他某些信号触发仍然会生成core-dump
文件,这样随着一段时间过去,引发了一些OOM
内存溢出的问题,或者磁盘变为只读,经过一些排查,发现core-dump
文件异常的大,一个正常的文件都到300-500M
,直接把磁盘撑爆了!!!
所以,经过上面的事故,我建议大家不要在生成环境打开 coredump
功能。
建议是拿出来一台机器特殊标记,打开 coredump
功能,然后模拟发生异常的情况来进行排查。生成 coredump
文件后,可以使用 GDB
来进行调试。
5、文章参考
[1]:https://zhuanlan.zhihu.com/p/240633280
[2]:https://blog.csdn.net/zhouhailiang1991/article/details/119172697