Linux内核崩溃诊断实战指南

Linux崩溃了,你能干什么

如果你发现你的Linux机器重启了,你能查出来是什么原因导致的吗。

绝大多数人是束手无策的,今天,本文(结合真实案例)教你怎么做。

一、首先你要有dump文件

接触过Linux的,大概知道有个core dump的,很多人以为这是核心转储,nonono,它是内存转储的意思,core来源于早年间磁芯存储器的那个磁芯(core)。

实现core dump机制有很多工具,kdump技术是目前最可靠、最常用的,已被主要的Linux厂商选用。当Linux内核崩溃时,kdump工具捕获当时内存等状态信息,生成转储文件(vmcore),保留现场证据,然后才重启。

当然,如果要用kdump,需要安装kdump工具、修改内核启动参数、修改kdump配置文件参数、启动kdump服务功能。

二、怎么解析dump文件

如果你装了kdump,Linux崩溃重启后,你会在/var/crash目录发现vmcore文件,恭喜你,你可以用它来发现根因了。

为了分析core dump,你需要安装crash工具,crash工具是RedHat公司提供的一个开源的内核分析工具,它在gdb的基础上实现了解析内核的功能。

你还需要安装Linux内核相应版本的debuginfo包,这个包安装好后会在操作系统上生成一个vmlinux文件,该文件包含完整的符号信息,用于提供调试信息。

三、怎么使用crash工具

crash工具包括了很多命令,包括查看内核日志的log命令、查看调用栈的bt命令、查看进程情况的ps命令、查看某个地址对应符号的sym命令、查看文件系统信息的files命令等。

总之,有了这个大杀器,你再有一点操作系统知识,有一点编程知识,就能干一般人干不了的事了。

比如,有一天,你发现你的若干台服务器意外重启了,你一脸懵,你的领导责令你务必找到根因。

你就可以悠然打开crash工具,来一趟探因之旅:

crash /usr/lib/debug/lib/modules/2.6.32-754.35.1.el6.x86_64/vmlinux  /var/crash/vmcore

上面那个vmlinux,是调试所需的内核镜像;上面那个vmcore,就是core dump。

四、用sys命令看看基本信息

用crash打开vmcore文件后,使用sys命令,你可以看到系统内核的基本信息,比如崩溃时系统中的进程数量、系统内核版本、内存大小、系统崩溃时的报错信息等。

比如,报错信息是:BUG: unable to handle kernel paging request at ffffffffa0395070

懂的人都懂,这是内核分页请求报错。

可能的原因是:错误的内存访问、内存不足、硬件故障等等。

当然最可能的是:错误的内存访问。

下面,我们用bt命令看看内核崩溃的调用栈信息。

所谓调用栈,就是谁调用了谁,谁又进一步调用了谁。

五、用bt命令查看进程信息

这一步是非常重要的,毕竟,我们非常想看到,机器是怎么一步步崩溃的。

使用crash工具的bt命令(栈跟踪backtrace的缩写)查看调用栈信息,显示信息如下:

crash> bt
第1行   PID: 177488  TASK: ffff880435b92ab0  CPU: 2   COMMAND: "ss"
第2行   #0 [ffff880437c0b7e0] machine_kexec at ffffffff8104179b
第3行   #1 [ffff880437c0b840] crash_kexec at ffffffff810d7a52
第4行   #2 [ffff880437c0b910] oops_end at ffffffff81560310
……
第10行   #8 [ffff880437c0bb40] page_fault at ffffffff8155f265
第11行      [exception RIP: strnlen+9]
第12行      RIP: ffffffff812ae3a9  RSP: ffff880437c0bbf8  RFLAGS: 00010286
……
第25行  #15 [ffff880437c0be70] proc_reg_read at ffffffff8120faf0
第26行  #16 [ffff880437c0bec0] vfs_read at ffffffff811a3447
第27行  #17 [ffff880437c0bf00] sys_read at ffffffff811a3791
第28行  #18 [ffff880437c0bf50] system_call_fastpath at ffffffff81566391

不用细看,bt在第一行就说了,崩溃就是“ss”这个程序引起的。从第28行开始,ss调用了system_call_fastpath、然后是sys_read(第27行)、然后是我这里省略了的一连串调用,然后是第10行令人胆战心惊的page_fault,然后是oops_end(哦,要完蛋了,第4行)、crash_kexec(准备kdump,第3行)、machine_kexec(调起一个新内核采集信息,第1行),然后kdump就生成了core dump文件:vmcore。

注意第25行,可以看出ss干了一件事,调用了proc_reg_read,这表明它读了proc文件系统。

proc是一个虚拟文件系统,提供了内核和进程的运行信息。

ss读proc文件是正常的,因为要读取一些内核层面的信息,但正是这个读,导致了崩溃。

注:ss是一个用于查看和分析Linux系统中的网络连接和套接字(socket)状态的工具。它是 netstat命令的替代品,通常比netstat更加高效和快速。

下面我们看看ss这个进程的具体信息。

六、用ps命令查看都有哪些进程

使用crash工具的ps命令查询内核崩溃前所有进程的状态信息

比如:

crash> ps
PID    PPID    CPU        TASK           ST  %MEM  VSZ  RSS    COMM
...
177488 64302    1  ffff880436662ab0   RU   0.0  6280  568     ss
...

当然,崩溃时的进程有很多了,这里只显示ss进程,可以看到它的进程号为177488。

有了ss的进程信息,现在我们看看ss到底调用了哪个文件。

七、用files命令查看进程访问了哪个文件

一般是使用struct file命令查看某进程访问文件的信息。

刚才我们用ps命令查到ss的进程地址为ffff880436662ab0,用它作为struct file的输入参数。

crash> struct file.f_path ffff880436662ab0
  f_path = {
    mnt = 0xffff880432adbe80, 
    dentry = 0xffff880101cae5c0
  }

该命令显示了ss进程访问文件路径的结构信息,mnt表示文件所在的挂载点,dentry表示文件名在地址ffff880101cae5c0。

接下来使用files命令来解析这个dentry地址。

crash> files -d 0xffff880101cae5c0
     DENTRY           INODE           SUPERBLK     TYPE      PATH
ffff880101cae5c0 ffff880101c1d598 ffff88043a23e800 REG  /proc/slabinfo

此时可以得知,故障时,ss进程访问的文件是/proc/slabinfo。

注:/proc/slabinfo文件包含了当前内核中所有slab内存的详细信息。

八、使用sym命令看故障相关源码

从前面bt命令的调用栈信息看到,异常报错位置为[exception RIP: strnlen+9](第11行),RIP指向异常调用地址为ffffffff812ae3a9(第12行)。

使用sym命令,看看这个异常地址是个啥。

crash> sym ffffffff812ae3a9
ffffffff812ae3a9 (T) strnlen+9 /usr/src/debug/kernel-2.6.32-754.35.1.el6/ linux-2.6.32-754.35.1.el6.x86_64/lib/string.c: 407

这个命令很牛,它告诉我们,这个异常地址,对应的源码(string.c)和行号(第407行)都告诉我们了。

我第一次见到这个的时候,不禁惊呼,这么牛啊,从内存dump能看出问题源码在哪?

对,就这么牛,kdump很牛,debuginfo也很牛,一个用于调试,一个提供调试信息,程序员不会亏待自己的。

注:当你调试一个内核时,需要安装对应版本的debuginfo包,如下:

debuginfo-install kernel-debuginfo-common-2.6.32-754.35.1.el6.x86_64.rpm

当然,看到源码并不稀奇,Linux是开源的嘛!(如果你玩Windows,如果机器崩溃了,那就崩溃了吧。)

这段完整代码如下:

size_t strlen(const char *s) {
  const char *sc;
  for (sc = s; *sc != '\0'; ++sc)/*这就是那个第407行*/;
  return sc - s;
}

这段代码通过for循环,从输入字符串初始字符s开始,遍历其所指的内容,循环直到遇到字符串结尾的空字符’\0’,最后返回字符串长度。

那个*sc就是读sc这个地址里的内容。

但是,读着读着,就崩溃了,因为读到翔了。

八、用log命令查更多的内容

使用log命令查到的信息如下:

crash> log
...
VMAGENTMOD: 3846: init_module: get into init_module, syshook_enable:1
VMAGENTMOD: 177350: cleanup_module: get into cleanup_module 
...

可以看出,内核崩溃前,有加载和卸载某个驱动模块的动作。相关的进程号是3846和177350。

经查询,这2个进程号都属于防病毒工具的进程。它调用的cleanup_module是内核函数,用于卸载驱动模块。

用crash的mod -t命令,显示内核模块加载的详细信息:

crash> mod -t
NAME           TAINTS
syshook_linux  (U)
vmsecmod       (U)

在log命令的输出中,还可以看到“[last unloaded: vmsecmod]”,这说明,内核最后卸载的驱动模块就是vmsecmod。

另外,防病毒工具的本地日志也显示,服务器重启前刚刚执行了停止防病毒进程的操作。

这些信息都告诉我们,这次崩溃的发生,防病毒工具有相当的嫌疑。

注:mod的-t选项,是显示taints信息。所谓taints(污点),是内核运行时的一个标志,用来指示内核在运行过程中遇到了某些潜在问题或非标准情况。如果taints是U,表明该模块是未经签名的,是用户开发的。

九、回到sys命令

我们最开始使用sys命令查到有如下的报错信息:

BUG: unable to handle kernel paging request at ffffffffa0395070

用sym命令看看这个地方到底是何方神圣。

crash> sym ffffffffa0395070
ffffffffa0395070 (r) hash_info_mempool_name [vmsecmod]

可以看到,这个地址来自vmsecmod驱动,对应的源码是hash_info_mempool_name。

现在基本可以判断出来是怎么回事:

防病毒工具申请并使用了slab分配器提供的内存,相关信息记录在/proc/slabinfo中,ss进程会去查询slabinfo,获取必要的信息。就是在访问slabinfo时,造成了内核崩溃。

有人在崩溃前卸载了防病毒的驱动,停止了防病毒服务,按道理,防病毒申请的slab内存应该也释放掉,slabinfo中也不会有对应信息。但是,ss进程居然还在访问这块数据,说明防病毒进程申请的slab内存未正常释放!

知识普及:slab主要用于内核态中的内存分配,可高效分配和管理小块内存。在/proc/slabinfo这个虚拟文件中,记录了系统中所有slab内存块的信息,如对象数量和内存使用量等。ss读取/proc/slabinfo,获取与网络套接字相关的内存使用和分配信息,提供详细的网络连接状态。

九、原来是防病毒工具的bug

把上述信息给到防病毒厂商,他们的研发工程师分析确认,确实是防病毒客户端有bug,导致了这次重启。

bug很简单,就是在卸载vmsecmod驱动时,应该同步释放所申请的slab内存区,但程序员没有这么做。

在重启的服务器上,有服务器管理工具,它会定期调用ss命令,ss会读取slabinfo,防病毒没有释放slab内存,所以ss仍然可以读取slabinfo中的指针,该指针却指向了已经释放了内存区(vmsecmod驱动曾经用过的地方)。

由于指针指向的内存已经释放,所以这就是访问非法地址,其实就是分页机制无法将该地址映射到物理地址,此时处理器就会向操作系统发出一个“page fault”错误,如果处理器此时处于超级用户模式,系统就会产生一个Oops,哦,完蛋了。

注:如果在用户态访问了非法地址,那么,你大概会得到一个经典的Segmentation fault(初级程序员的噩梦)。

十、当时发生了什么?

那天,某个工程师做了一件事,更新防病毒工具的许可,这个防病毒工具是企业版的,有一个管理端,还有运行在若干台服务器上的防病毒客户端。

他先在防病毒管理端导入软件许可,接着管理端将软件许可分发给每台服务器上的客户端,由客户端本地更新许可文件。

在客户端更新许可文件时,会先停止防病毒客户端进程(更新完许可文件后,再启动进程),停止进程会导致vmsecmod驱动模块的卸载,由于有bug,清理动作不完善,残留了无主的slab内存。

而服务器上部署的自动化工具,会定时执行ss命令,ss遍历slabinfo信息时,读取了在野的指针,引发page fault,内核崩溃。

后记

Linux这么稳定的内核都会崩溃。

做一个内核稳定的人,很不容易呢。

原创文章,作者:AIRF,如若转载,请注明出处:http://www.ai-rf.com/index.php/2024/08/17/linux%e5%86%85%e6%a0%b8%e5%b4%a9%e6%ba%83%e8%af%8a%e6%96%ad%e5%ae%9e%e6%88%98%e6%8c%87%e5%8d%97/

(0)
AIRFAIRF
上一篇 2024年8月11日
下一篇 2024年9月22日

相关推荐

  • 麒麟系统Ping报错:未知的名称或服务

    前言 在/etc/hosts里添加了集群服务器的主机名+IP,但是ping主机名提示:未知的名称或服务 ping域名和其他的IP都可以正常返回 处理步骤 修改/etc/nsswit…

    2024年6月25日
  • LVM 缩减 / 根目录导致的开机错误

    前景描述 给服务器lv_var扩容,没空间扩容,于是将lv_root由200G缩小至100G,几分钟后,服务器告警,ping状态变为停止,于是登陆服务器管理口远程查看 报错过程 发…

    Linux 2022年9月25日
  • 探索与筑梦:我的博客网站进化传奇

    引言 在这片浩渺的数字宇宙里,每一个网站都是一个独特的星球,闪烁着创造者梦想的光芒。这不仅是一部技术探险的编年史,更是一段用热爱与坚韧书写的心灵之旅。让我们一同回溯,从一张空白的H…

    2024年6月15日
  • Linux 系列基础教程(一)

    Linux 是一种自由和开放源码的类 UNIX 操作系统。 Linux 英文解释为 Linux is not Unix。 Linux 是在 1991 由林纳斯·托瓦兹在赫尔辛基大学…

    2022年9月25日
  • 丝滑关闭企业版奇安信天擎开机自启动

    前言 公司要求安装奇安信天擎,并更新病毒库到最新版,我自己电脑安装天擎之后浏览器只有edge能打开,vmware workstation之类的软件全部打不开 过程 网上找到了好多资…

    2024年6月14日
  • 运维人员必须知道的10个系统进程

    前言 在日常运维工作中,经常会看到一些奇怪的系统进程占用资源比较高,但是又不敢随意的Kill这些进程 而这些系统级的内核进程都是会用中括号括起来的,它们会执行一些系统的辅助功能(如…

    2023年12月16日
  • Linux-tcpdump指令

    Linux-tcpdump指令 tcpdump是Linux和其他类Unix系统中用于捕获和分析网络流量的命令行工具。它被广泛应用于网络诊断、故障排除、安全分析以及网络流量监控等场景…

    Linux 2025年1月27日
  • Linux Shell巡检脚本

    系统一键巡检脚本: #!/bin/bash # -*- coding:utf-8 -*- echo -e “\033[31m >>>>>>&gt…

    2023年6月2日
  • 服务器IO读写/下载测速脚本SuperBench

    前言 有的朋友想测试一下自己的服务器硬盘读写速度及各地到自己服务器的网速,所以就诞生了这款脚本SuperBench,这款脚本原作者已停止更新,此脚本为修复版,支持Centos8 介…

    Linux 2022年12月2日
  • Linux系统中普通用户获取root权限

    一.原因 因公司服务器已被安全程序纳管,如需使用root权限需登录堡垒机后才可使用,因环境所需,故使用root登录修改普通用户admin的权限,将其提升为root权限。 二.步骤 …

    2021年11月21日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注