引言
解决Linux系统上疑难问题的常见方案之一是使用crash工具调试系统转储(coredump),
进入内核分析、排查。但是此方案时常不可行。这一点在内核较新的系统上尤甚。而捕捉
系统转储也有不少值得留意之处。我们一并讨论下使用crash工具调试系统转储的准备
事项及其背后的原因,以便更有效的使用此方案,减少无用功。
crash的黑魔法
crash工具对内核和gdb使用了很多黑魔法,而非其公开的API或者文档化的
features。比如,crash并不是使用了libGDB框架,也不是如
ddd等gdb前端那样采用C/S架构。当然也
没有使用在Emacs内嵌gdb支持时采用的
"通信协议" -- gdb的的annotate特性。
那么,crash是怎么使用gdb的呢?crash使用自带而非系统安装的gdb。对自带的gdb,crash对
其打了补丁。通过这种方式,crash把gdb收编了。两者共存于同一进程,crash通过函数调用就
可以使用gdb。通过修改gdb命令执行完毕后返回哪个函数,crash就接管了入口。不管命令来自
交互式界面还是批处理文件,都是crash接到输入,进而路由命令,而后由自身、shell或者gdb
三者之一来执行命令。
crash解析系统转储,对黑魔法的使用也不遑多让。
凭借黑魔法,crash功能可观,但是却也时常因之无能为力。如何解决crash不能正常工作的
问题呢?答案是做好规划和准备,简化目标环境,降低复杂度,收集辅助数据,增加对目标环境的
认知。这也有助于我们理解crash的工作过程和验证crash结果。
源码和调试符号
调试需要调试符号和源码,但是不同的发行版,对于调试符号和源码的处理方式是不一样的。
对于CentOS、Fedora之类RHEL-Like的发行版,其有专门的软件源提供发布软件包各个
版本的调试符号和源码。可以从软件源手工下载,也可以调整yum/dnf的配置,把有关
软件源补充进来即可。比如,对于CentOS,其对应的软件源在
debuginfo.centos.org。
而debian及其衍生的发行版,比如Ubuntu,几乎只能找到部分最新版本软件的调试符号,而且这些
调试符号对应的源码也往往无法拿到。
因此,如果使用的是debian或者其衍生的发行版,应备份内核源码等关键组建源码,并准备好相应的
构建环境,以备不时之需。
为makedumpfile提供额外信息
ECS之类的计算节点配备的内存越来越大,甚至内存大小都超出了其配备的磁盘空间。这种
情况下makedumpfile能够通过过滤和压缩产生较小系统转储,这个功能对存储和传递
系统转储无疑提供了便利。
但是便利并非没有成本。makedumpfile需要调试符号信息,以分析和判断哪些内存页需要压缩,
那些内存页可以丢弃。就是说在做系统转储时,需要有内核调试符号。变通之处是在有内核
调试符号的系统中,makedumpfile工具可以生成vmcoreinfo文件。这个文件在做系统
转储时可以替代内核调试符号使用。但是这个变通之处并非处处可行。比如qemu
虚机的monitor命令,其子命令dump-guest-memory就没有vmcoreinfo有关的选项。
但是,几乎所有的发行版,其提供的内核都开启了地址随机化(KASLR,
kernel address space layout randomization)。在没有关闭kaslr的情况下,
vmcoreinfo提供了系统的运行时信息,对于crash调试都是有益的。因此,在可能的情况下,
建议提供系统转储的同时,也提供vmcoreinfo文件。
使用内核命令行参数nokaslr即可关闭内核地址随机化。
使用kdump捕捉系统转储
在宿主机上我们可以把qemu进程视之为普通进程来做coredump;也可以使用qemu虚机的
monitor命令来保存虚拟机内存的方式来制作虚拟机的系统转储。这两种方案都有其应用
场景和便利之处。比如因为进程pid耗尽导致无法登陆或者业务进程无法启动的场景。但
这两种方案,即要在宿主机上有相应的权限,还缺少对虚拟机内部的知识,可用的
机制也不够灵活。
接下来我们讨论一个有竞争力的系统转储方案:基于kexec技术的Kdump。
kexec: 运行时启动新内核
kexec能够在运行时启动新内核。即在当前运行的Linux系统上,kexec能够让我们加载和
启用一个新的内核,代替老的内核来运行。这就带来了一种新的、生成系统转储的方案。
在Linux系统上,加载一个新的内核但不启动之。这时系统上就有了两个内核。一个是当前
在运行的内核,我们称之为生产内核;一个是刚刚加载但是没有运行的内核,我们称之为
捕获内核。然后把系统配置为生产内核panic时启动捕获内核。正如其名字所示的,捕获内核
负责为生产内核捕捉系统转储。
kdump怎么使用kexec?
预留内存
kdump要为捕获内核预留内存,这需要配置crashkernel内核命令行参数。RHEL-Like发行版
默认配置了这个参数,但是debian及其衍生版则未必。
持久化的配置内核命令行参数,需要调整GRUB的设定并刷新之,而后重启生效。因此,在
debian及其衍生版上部署kdump,如果新配置或者调整了内核命令行参数,需要重启系统。
提供守护进程
捕获内核依赖守护进程实现系统转储功能,这个守护进程需要kdump部署。这样捕获内核启动
后运行此守护进程即可。
那么,kdump是怎么通知捕获内核运行哪个守护进程的呢?答案是kdump提供了第二个守护进程。
这个守护进程运行在生产内核场景下,其职能就是实现某种机制用以告知捕获内核哪一个守护进程
是捕获进程(这里用进程不甚恰当,捕获进程不会在生产内核下运行)。这第二个守护进程
也负责处理内核有更新时的场景。
就是说,kdump提供了两个守护进程。一个运行在生产内核下,功能是告知捕获内核谁是捕获
进程;一个运行在捕获内核下,负责系统转储。
题外话,RHEL-Like发行版和debian及其衍生版实现上述功能采用了差异颇大的技术栈。
kdump的灵活性
可以充分借用makedumpfile的功能
捕获进程是单独的守护进程,使用的工具是makedumpfile。因之可以灵活的按需配置。比如说
把系统转储直接通过网络投递出去、过滤掉更多无用的内存页等等。更多细节请参考
makedumpfile的文档。
调配触发配置,扩大使用场景
生产内核panic时kdump才会生成系统转储。因此,可以按需调整系统,让其在特定条件下
panic,这样我们就能得到系统转储。
这里讨论两种情况。
调整内核参数
内核有参数控制某些情况出现时内核是否panic。比如vm.panic\_on\_oom参数。将之设定
为1则oom时会触发panic。其他类似的参数还有kernel.hung\_task\_panic、
kernel.panic\_on\_rcu\_stall等。
灵活使用魔术键
使用魔术键
可以手工触发或者在脚本中触发内核panic。了解这一点,则可以在生产内核场景下部署脚本,检测
到问题复现则触发内核panic。
如何部署kdump?
根据上面的讨论,部署需要:
部署软件包:
- RHEL-Like的发行版部署kexec-tools。除非必要,否则不必让kexec负责重启功能。
- debian及其衍生版需要部署kexec-tools和kdump-tools
- 检查grub配置,核实为捕获内核预留了内存
- 如果调整了内核命令行参数,则重启系统
- 验证守护进程运行了
- (可选)通过魔术键测试kdump工作。
小结
crash调试系统转储,源码、调试符号和转储内容,三者缺一不可。提前做好规划和准备,使用crash
解决疑难问题,会更加便利和有效。
参考
- 常见Linux发行版:CentOS、
- The History and Future of Core Dumps in FreeBSD
- Debugging a Crashed Application
- Kernel crash dump guide
- How to collect core dump file of a crashing program that is shipped in Red Hat Enterprise Linux 6/7/8?
- 内核调试器:crash官方站点和其git repo
- 调试器:gdb,其git repo可以在页面找到
- 制作初始RAM磁盘(initrd)的工具:debian及其衍生版使用
initramfs-tools,CentOS、Fedora之类
RHEL-Like的发行版使用dracut
- 目标文件格式:Executable and Linkable Format (ELF)
- 调试标准:Debugging With Attributed Record Formats (DWARF)
- makedumpfile
- 基于kexec的崩溃转储方案:Kdump
- 系统软件包管理工具:debian及其衍生版使用