内核崩溃
什么是内核崩溃
内核崩溃指的是操作系统内核发生严重错误或异常情况,导致系统无法继续正常运行而崩溃。在内核崩溃时,操作系统通常会停止响应,并可能出现蓝屏、系统重启或者其他错误提示。内核崩溃可能由软件或硬件问题引起,对系统稳定性和可靠性造成影响。
分析core dump是Linux应用程序调试的一种有效方式,core dump又称为“核心转储”,是该进程实际使用的物理内存的“快照”。分析core dump文件可以获取应用程序崩溃时的现场信息,如程序运行时的CPU寄存器值、堆栈指针、栈数据、函数调用栈等信息。
Core dump是Linux基于信号实现的。Linux中信号是一种异步事件处理机制,每种信号都对应有默认的异常处理操作,默认操作包括忽略该信号(Ignore)、暂停进程(Stop)、终止进程(Terminate)、终止并产生core dump(Core)等
信号
信号处理逻辑
拆分成信号的接收、检测、处理三个步骤。
信号接收:接收信号的任务由内核代理,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。注意,此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。
信号检测:进程陷入内核态后,有两种场景会对信号进行检测:
进程从内核态返回到用户态前进行信号检测
进程在内核态中,从睡眠状态被唤醒的时候进行信号检测
信号处理:信号处理函数是运行在用户态的,调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。接下来进程返回到用户态中,执行相应的信号处理函数。信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。
信号处理函数
[有了core-dump文件,BUG终于解决了!-电子工程专辑](https://www.eet-china.com/mp/a174362.html)
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
内核函数来进行进一步的处理。
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
文件。
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
方法的代码,这里就不作进一步的解说了。
kill -l 查看信号
常见内核崩溃的原因
内存问题:内存泄漏、内存损坏或内存错误可能导致内核崩溃。
内存访问越界 (数组越界、字符串无\n结束符、字符串读写越界)
多线程程序中使用了线程不安全的函数,如不可重入函数
多线程读写的数据未加锁保护(临界区资源需要互斥访问),死锁
非法指针(如空指针异常或者非法地址访问)
堆栈溢出
驱动程序问题:设备驱动程序的错误、冲突或不兼容性可能会导致内核崩溃。
例如安装了不兼容的设备驱动程序,这可能会导致系统中断处理出现错误,从而引发内核崩溃。
硬件故障:硬件故障,如CPU、内存、硬盘或其他组件的问题也可能导致内核崩溃。
内存条损坏可能导致内存中的数据错误,进而导致内核崩溃。
软件内核态触发CPU等硬件异常指令调度
软件冲突:软件之间的冲突或者应用程序的bug也可能触发内核崩溃。
某些应用程序之间存在冲突,导致它们的行为影响到内核,造成内核崩溃。
恶意软件:恶意软件或病毒可能会修改系统文件或者操纵系统行为,导致内核崩溃。
恶意软件可能试图修改操作系统的核心文件或者系统设置,导致内核崩溃。
操作系统错误:操作系统本身的bug或错误也可能导致内核崩溃。
操作系统本身的bug可能导致内核崩溃,比如某些特定的操作序列可能触发操作系统的漏洞,导致内核崩溃
获取coredump
ulimit –c 查看core dump机制是否使能,若为0则默认不产生core dump,可以使用ulimit –c unlimited使能core dump
cat /proc/sys/kernel/core_pattern 查看core文件默认保存路径,默认情况下是保存在应用程序当前目录下,但是如果应用程序中调用chdir()函数切换了当前工作目录,则会保存在对应的工作目录
ulimit –c [size] 指定core文件大小,默认是不限制大小的,如果自定义的话,size值必须大于4,单位是block(1block = 512bytes)
指定core文件保存位置
echo “/data/xxx/
%% 单个%字符
%p 所dump进程的进程ID
%u 所dump进程的实际用户ID
%g 所dump进程的实际组ID
%s 导致本次core dump的信号
%t core dump的时间 (由1970年1月1日计起的秒数)
%h 主机名
%e 程序文件名
内核转储机制
系统一旦崩溃,内核就没法正常工作了,这个时候需要触发一种转存储机制(kernel中的kdump, unisoc的sysdump)。转存机制提供一个用于捕获当前运行现场的内核,该内核会将此时内存中的所有运行状态和数据信息收集到一个dump core文件中以便之后分析崩溃原因
kdump & sysdump
kdump 、sysdump 通常用于假死机(unresposive)和panic,也就是没有响应的情况下。硬件问题导致的死机,无能为力
1.kdump
kdump是kernel原生的一个系统奔溃时的现场转存工具。kdump是RHEL5之后才支持的,2006被主线接收为内核的一部分。它的原理简单来说是在内存中保留一块区域,这块区域用来存放capture kernel,当production kernel发生crash的时候,通过kexec把保留区域的capure kernel给运行起来,再由捕获内核负责把产品内核的完整信息,包括CPU寄存器、堆栈数据等转储到指定位置的文件中。
(1) kdump运行原理介绍
kdump实现了"双内核"布局,Kdump 在内核在内核panic后,立即调用kexec 引导到转储捕获内核(capture kernel),使用 kexec 引导 “覆盖” 当前运行的内核。
该“转储捕获内核”的内存区域由主内核的bootargs参数 crashkernel 或dts指定。“转储捕获内核”可以是专门build的单独 Linux 内核image,也可以在支持可重定位内核的系统架构上重用主内核映像。
kexec(kernel execution,类似于 Unix 或 Linux 的系统调用 exec)是 Linux 内核的一种机制,其允许从当前运行的内核启动新内核。kexec 会跳过由系统固件(BIOS或UEFI、bootloader)执行的引导加载程序阶段和硬件初始化阶段,直接将新内核加载到主内存并立即开始执行。这避免了完全重新启动的漫长时间,并且可以通过最小化停机时间来满足系统高可用性要求。
注意: 不经过bootloader或bios阶段,直接从主kernel启动到“转储捕获内核”进行。
(2) dump触发方式
1.手动触发
通过sysrq触发
echo c /proc/sysrq-trigger
通过IPMI触发不可屏蔽中断
ipomitool power diag
通过virsh触发不可屏蔽中断
virsh inject-nmi MyGuestName
Beware of
kernel.unkonow_nmi_panic=1
2.自动触发
watchdong(看门狗)
cmdline中:nmi_watchdog=1
softlockup(软锁)
sysctl kernel.softlockup_panic=1
内存越界
sysctl vm.panic_on_oom=1
2.sysdump
SysDump即Dump system memory,是sysdump的一种转存储机制,是将发生异常时的内存信息、寄存器信息等有效信息转存为文件,以便于借助分析工具分析问题现场。
在系统发生诸如Kernel crash等异常时,在Kernel中完成flush cache等处理后,重启进入Uboot(或LK等bootloader阶段)中完成所有数据的保存,操作系统在发生严重错误或异常情况时将系统内存中的内容保存到磁盘上的一个过程
coredump文件
如何阅读coredump文件:http://sunyongfeng.com/201609/programmer/tools/coredump
生成coredump文件
阅读coredump文件
gbd 阅读 core文件
[Coredump文件简易指南 | 孙勇峰的部落格](http://sunyongfeng.com/201609/programmer/tools/coredump)
评论区