微知SOP-定位Linux crash问题的几个常用方面和常用命令?
文章目录
- 1 背景
- 2 问题描述和SOP
- 2.1 进入目录
- 2.2 安装crasn和vmlinux
- 2.3 使用crash命令进入debug目录
- 2.4 收集主要信息
- 2.5 收集task关键信息
- 2.6 查看堆栈信息
- 2.7 使用反汇编查看crash所在行
- 2.8 根据寄存器获取一些关键数据
- 2.9 根据堆栈获取信息
- 2.10 加载内核模块
- 2.11 收集其他task信息
- 2.12 其他帮助信息
- 3 综述
1 背景
x86或者arm上存在crash,如何快速定位?隔一段时间老是忘记。本文主要记录从头到尾在一台新服务器出现异常如何快速定位的记录。如果没有文档容易少收集信息。本文详细记录需要收集的一些关键信息,虽然不全,但是基本能定位出个大概。尤其其中的结合SP堆栈指针SP + rd + struct能够看到表面现象背后很多隐藏的数据,打开了一闪通往真相的大门,灵活运用并结合反汇编和操作系统函数调用应该算一个高阶功法了。参悟可以打出“立棍式”。`
2 问题描述和SOP
本文是一次服务器上crash的日志,进行的一些分析。
2.1 进入目录
进入/var/crash中最新的crash目录:
快速进入最近一次crash的快捷命令:
# 快速进入最近一次crash的目录
cd /var/crash/`ls /var/crash -t | head -n 1` && pwd && ls
2.2 安装crasn和vmlinux
# 安装vmlinux
yum install -y crash
yum install -y kernel-debuginfo*
yum install -y kernel-debuginfo-common*
# 确定安装vmlinux成功
ls /usr/lib/debug/lib/modules/`uname -r`/vmlinux -l
如果没有在对应的rpm包网站下载
实操效果:
2.3 使用crash命令进入debug目录
crash /usr/lib/debug/lib/modules/`uname -r`/vmlinux ./vmcore
实操效果:
2.4 收集主要信息
主要通过2个收集,一个是进入时候显示的信息,另一个是vmcore-message.log文件
进入后读取关键信息,包括:
panic主要信息:比如图中是访问了一个空指针:BUG: unable to handle kernel NULL pointer dereference at 0000000000000030
PID信息,比如图中2948098
PID的名字,比如图中poweroff
另外vmcore-message文件获取堆栈数据,可以通过目录下的文件显示,也可以直接在crash中显示,使用log命令,快速获取最后crash点可以:
# 显示crash关键信息
log -T | tail -n 100
根据关键信息,能够获取几个点:
- pid:PID: 2948098
- 出问题时候的CPU: CPU: 1
- 进程名字:Comm: poweroff
- 主要问题原因:NULL pointer
- 堆栈关键点:mlx5e_destroy_mdev_resources+0x37/0xa0 [mlx5_core]
- 包括堆栈的触发源头:do_syscall_64 和 kernel_power_off 可见是关机
- 以及各种寄存器信息,主要包括PC指针和SP指针
实操:
2.5 收集task关键信息
# task命令收集关键task信息,包括task状态,所在CPU。非常多的信息
task
# 也可task命令直接指定响应的
task 2948098
非常多的信息,比如:
- stack = 0xffffa9a606ac4000 堆栈
- on_cpu = 1,
- prio = 120,
- sched_class = 0xffffffffb4c260c0 <fair_sched_class>, 调度
- cfs_rq = 0xffff9a9a9fa338c0, 调度队列
- mm = 0xffff9a934e458600, 内存 mm
- pid = 2948098,
实操:
2.6 查看堆栈信息
使用bt和bt -l ,其中-l可以显示代码所在行
# 查看堆栈
bt
# 查看堆栈所在代码行
bt -l
实操:
bt -l显示代码所在行,由于没有加载驱动所在的源文件,没有显示代码行
2.7 使用反汇编查看crash所在行
# 使用反汇编查看crash所在行,使用PC指针
dis -l ffffffffc03d792d
# 使用反汇编指定堆栈中某行代码汇编所在行
dis -l mlx5e_destroy_mdev_resources+0x37
# 使用反汇编指定堆栈中某行代码查看所在代码
dis -s mlx5e_destroy_mdev_resources+0x37
2.8 根据寄存器获取一些关键数据
除了dmesg打印的堆栈信息,以及register等信息,其实通过寄存器信息还能获取堆栈信息。堆栈信息里面包括了各种调用流程,以及暂存的参数信息,尤其是参数!结合rd以及struct有巨大效果
在X86上主要寄存器包括“
- RDI 第一 第一个参数 R是register DI(“第一”)
- RSI 第二个参数,S是second。 (“顺义”)
- RDX 第三个参数或者第二个返回值。 (“定西”)
- RCX 第四个参数(“慈溪”)
- R08 第五个参数
- R09 第六个参数
- RAX 临时寄存器或者第一个返回值
- RBP 栈帧寄存器 P时候pointer B是base
- RSP 是堆栈指针
在ARM上:
X0~X8都是参数,更多需要参考arm手册。
比如这里:
[Mon Oct 14 02:09:24 CST 2024] RSP: 0018:ffffa9a606ac7cd8 EFLAGS: 00010246
[Mon Oct 14 02:09:24 CST 2024] RAX: ffff9a8ba234e000 RBX: 0000000000000000 RCX: 00000000c01809f8
[Mon Oct 14 02:09:24 CST 2024] RDX: 0000000000000001 RSI: 0000000000000000 RDI: ffff9a8ba23801a0
[Mon Oct 14 02:09:24 CST 2024] RBP: ffff9a8ba23ad308 R08: ffff9a8ba2380058 R09: c0000000ffff7fff
[Mon Oct 14 02:09:24 CST 2024] R10: 0000000000000001 R11: ffffa9a606ac7aa0 R12: ffff9a8ba23ad328
[Mon Oct 14 02:09:24 CST 2024] R13: ffffffffb4f747e1 R14: ffff9a934e741138 R15: 0000000000000000
[Mon Oct 14 02:09:24 CST 2024] FS: 00007f8313fe3980(0000) GS:ffff9a9a9fa00000(0000) knlGS:0000000000000000
[Mon Oct 14 02:09:24 CST 2024] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[Mon Oct 14 02:09:24 CST 2024] CR2: 0000000000000030 CR3: 0000000847fe8001 CR4: 00000000000606e0
其中:
RSP: RSP: 0018:ffffa9a606ac7cd8
RDI: ffff9a8ba23801a0 第一个参数
RSI: 0000000000000000 第二个参数
RDX: 0000000000000001 参数3
RCX: 00000000c01809f8 参数4
注意这里如果调用函数只有一个参数,那么RSI这种可能是上一个函数的参数,注意只是可能。
通过这些参数结合struct,能够获取宝贵的数据
比如这里就可以良好利用RSP:ffffa9a606ac7cd8,使用内存读取rsp的前后数据
读取前,需要加载对应的内核模块。
# 如果这里知道第一个参数在代码中是某个struct ,比如struct mlx5e_priv ,则显示
struct mlx5e_priv 00000000fee1dead
这里关于寄存器要定位需要结合经验连猜带蒙和实验才能获取用信息。包括下面降到的真正的堆栈信息获取信息。
2.9 根据堆栈获取信息
这里堆栈信息获取专门拿一章节讲解,因为确实是太重要了。本文仅仅记录方式,后面有机会遇到crash点再单独记录文章。
RSP: 0018:ffffa9a606ac7cd8
这里的SP是堆栈指针,堆栈是从上到下长的。也就是SP是最低的地址,网上都是堆栈历史。如果是栈顶指针,则需要做一些移动。然后结合rd命令读取内存
# rd根据堆栈指针,获取堆栈数据,注意-s可以解析一些已知的数据,第二个参数是读取内存长度。如果是内存相关的可以用-S能够把kmalloc这些自动翻译出来,所以建议打印两次,原始的 -s的或者-S的
rd ffffa9a606ac7cd8 100
rd -s ffffa9a606ac7cd8 100
因为rd
这里读取的SP不对,看起来是一个用户态虚拟地址。
出现类似报错后,需要快速查看其他寄存器直接用前面提到的log -T | tail -n 50
,查看堆栈等信息
上面之所以报错,是使用的SP地址不对。使用正确的读取后:不加-s读取的信息比较难读取
实用-s查看详细信息:
这里重点关注mlx5e_destroy_mdev_resources
周边的数据,可能找到参数,另外就是读取出来的地址有0或者1这些非0xfff等比较大的地址,就要注意,可能是突破点。
。
结合该函数的定义:他只有一个参数,
这里没看到mlx5_free_bfreg函数在rd中打印,但是dmesg里面有。说明堆栈读取的还不够。
可以往前读取一些内存,比如从ffffa9a606ac7cd8开始,变成从ffffa9a606ac7ad8开始,都是读取100个:
可以看到mlx5_free_bfreg函数和mlx5e_destroy_mdev_resources函数有相同的mdev参数,可能也是一个帮助。
可以看到这里:ffff9a8ba23801a0 这个大概率是struct mlx5_core_dev mdev的值。那么就能用struct打印出mdev数据,从而帮助查看。但是需要注意这里是mdev,那么struct的时候需要*ffff9a8ba23801a0
然后用struct 获取数据:(这里因为用了内核自带的mlx的代码,没有源码,如果用OFED自己编译便能打印详细的详细的数据),下次自己编译后再处理。
至此,结合SP + rd + struct便能窥测到出问题时候关注的几个关键点信息。
另外这里有几个特殊值技巧,相同参数技巧都对于找到对应代码帮助极大!
将ko.xz解压后加载到内存中能够读取后效果:
解压:
加载:
查看:struct mlx5_core_dev ffff9a8ba23801a0
这里需要进一步确认正确性,确认方式是通过其中一些关键信息。比如查看玩不信息,看是否有不合理的地方,另外就是根据打印的数据,进一步去看其他数据结构的数据。
比如下面几个可以佐证这里是对的:
还可以进一步查看关联struct信息,比如其中的struct device和pci_dev:
device = 0xffff9a934e7410b8,
pdev = 0xffff9a934e741000
先看device:
再看pdev:基本判断现在内存读取是对的。
2.10 加载内核模块
# 使用modinfo获取内核模块
modinfo ib_core | head -n 2
modinfo mlx5_core | head -n 2
# 获取路径,假设这里是mlnx OFED主要包括ib_core目录的和hw目录的。core是通用,hw是不同网卡下的。使用mod -S加载指定路径下所有的ko符号,方便解析struct。不断按空格
mod -S /lib/modules/4.18.0-553.22.1.0.1.an8.x86_64/kernel/drivers/infiniband/core/
mod -S /lib/modules/4.18.0-553.22.1.0.1.an8.x86_64/kernel/drivers/infiniband/hw/
实操:一般报错不用管,但需要空格加载完否则可能失败
2.11 收集其他task信息
# 使用ps查看其他task信息,比如要查看一些业务进程是否在等信息以及数量
ps
实操:
2.12 其他帮助信息
忘记某些命令怎么操作直接help来查看
help task
help bt
help dis
help rd
help ps
比如:help task
还有很多他的关键信息
3 综述
本文详细记录需要收集的一些关键信息,虽然不全,但是基本能定位出个大概。尤其其中的结合SP堆栈指针SP + rd + struct能够看到表面现象背后很多隐藏的数据,打开了一闪通往真相的大门,灵活运用并结合反汇编和操作系统函数调用应该算一个高阶功法了。参悟可以打出“立棍式”。