一、移植原因
在我们制造的死锁案例中,只开启了两个线程,我们可以通过添加打印来定位是哪个线程使用了锁而没有释放,但在linux内核中,同一个锁会存在被多个线程或者进程所使用,通过添加打印的方式就很难去判断了。
那有没有其它的方式呢?
在组内中,有人分享了使用Kudmp工具在非arm平台来进行定位死锁问题,但是由于日常开发基本都是在arm平台小系统上进行的,因此尝试在arm平台上进行测试。
二、Kdump原理介绍
1. Kdump 工具简介
Kdump是在系统崩溃、死锁或死机时用来转储内存运行参数的一个工具和服务,是一种新的crash dump捕获机制,用来捕获kernel crash(内核崩溃)的时候产生的crash dump。
2. Kdump的流程
在第一kernel在运行的时候,系统内部在内存中就已经留存好了给第二kernel(捕获内核)的预留空间(这个预留空间的大小可以自己设定)。在第一kernelcrash的时候,就会进入第二kernel,在第二kernel中执行用户态程序makedumpfile对第一kernel的内存镜像进行裁剪和压缩,最后将第一kernel的vmcore保留在磁盘中并重启。
3. Kexec机制、Kdump机制
【Kexec机制】 Kexec是基于kexec机制工作的,因此先了解一下Kexec。 kexec是一个快速启动机制,允许通过已经运行的内核的上下文启动一个Linux内核,不需要经过BIOS。
【Kdump机制】 kdump 是一种先进的基于 kexec 的内核崩溃转储机制。当系统崩溃时,kdump 使用 kexec 启动到第二个内核。第二个内核通常叫做捕获内核,以很小内存启动以捕获转储镜像。第一个内核保留了内存的一部分给第二内核启动用。由于 kdump 利用 kexec 启动捕获内核,绕过了 BIOS,所以第一个内核的内存得以保留。这是内核崩溃转储的本质。 kdump 需要两个不同目的的内核,生产内核和捕获内核。生产内核是捕获内核服务的对像。捕获内核会在生产内核崩溃时启动起来,与相应的 ramdisk 一起组建一个微环境,用以对生产内核下的内存进行收集和转存。
三、Kdump的使用
平台:RK
1. 内核配置
1.1 修改宏配置
修改内核中以下的配置宏,可在.config文件中修改,或者通过make menuconfig修改
1. 开启:CONFIG_PROC_VMCORE=y //此宏在RK的配置文件中是找不到的,需要依赖于CONFIG_PROC_FS、CONFIG_CRASH_DUMP
路径:在"Filesystems" -> “Pseudo filesystems"下使能”/proc/vmcore support"。
2. 开启:CONFIG_PROC_FS=y
路径:在"Filesystem" -> “Pseudo filesystems.“下使能"sysfs file system support”。
3. 开启:CONFIG_CRASH_DUMP=y
路径:在"Processor type and features"下使能"kernel crash dumps”。
在 menuconfig内搜索PROC_VMCORE这个宏,从以下结果中可以得知需要配置PROC_FS、CRASH_DUMP两个宏,配置完成后如下:
4. 开启:CONFIG_KEXEC=y
路径:在"Processor type and features.“下使能"kexec system call”。
5. 开启:CONFIG_DEBUG_INFO=y
路径:在"Kernel hacking.“下使能"Compile the kernel with debug info” 。
6. 开启:CONFIG_KEXEC_CORE=y
7. 开启:CONFIG_KALLSYMS=y
8. 开启:CONFIG_KALLSYMS_ALL=y
如果是其它的芯片,参考需要开启的宏配置如下
在"Processor type and features.“下使能"kexec system call”。
CONFIG_KEXEC=y
在"Filesystem" -> “Pseudo filesystems.“下使能"sysfs file system support”。
CONFIG_SYSFS=y
在"Kernel hacking.“下使能"Compile the kernel with debug info” 。
CONFIG_DEBUG_INFO=y
在"Processor type and features"下使能"kernel crash dumps”。
CONFIG_CRASH_DUMP=y
在"Filesystems" -> “Pseudo filesystems"下使能”/proc/vmcore support"。
CONFIG_PROC_VMCORE=y
然后重新编译内核,生成Image,并给系统进行升级。
1.2 确认修改成功
如果出现proc/kcore节点说明配置生效了。
2. 编译Kump工具
2.1 源码下载、解压
源码解压:tar -xvpf kexec-tools-2.0.24.tar.gz
2.2 源码编译
进入顶层目录配置,并以静态编译的方式进行编译
使用芯片对应的交叉编译器进行编译,需要指定CC LD STRIP的交叉链的位置
# setup 1
LDFLAGS=-static \
./configure \
ARCH=arm64 \
--host=aarch64-linux-gnu \
CC=gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc \
LD=gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-ld \
STRIP=gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-strip \
--prefix=/home/hiwa/code/kexec-tools-2.0.22/output
# setup 2
make
2.3 查看编译的版本,并确认文件格式
3. kdump 工具使用准备
3.1 拷贝文件kexec、vmlinux
将kdump编译出的工具kexec以及内核的vmlinux文件通过tftp发送到userapp目录内,并赋予权限。
3.2 查看kexec参数说明
/ # /bin/kexec
No kernel specified kexec-tools 2.0.24
Usage: kexec [OPTION]... [kernel] Directly reboot into a new kernel
-h, --help Print this help.
-v, --version Print the version of kexec.
-f, --force Force an immediate kexec, don't call shutdown.
-i, --no-checks Fast reboot, no memory integrity checks.
-x, --no-ifdown Don't bring down network interfaces.
-y, --no-sync Don't sync filesystems before kexec.
...省略
注意以下几个参数
-p : vmlinux的路径
--append : command line的内容
3.3 修改启动脚本
在userapp目录添加start.sh脚本,start.sh脚本在系统启动后会执行,并增加如下内容
touch start.sh
chmod 777 start.sh
/userapp/kexec -p /userdata/ --append="storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal root=PARTUUID=614e0000-1111 rw rootwait earlycon=uart8250,mmio32,0xff1a0000 swiotlb=1 console=ttyFIQ0 rootfstype=squashfs coherent_pool=1m pci=nomsi"
注:如何查看cmdline的内容,使用 cat /proc/cmdline
命令
开始执行start.sh,会出以下错误:
这是因为我们没有设置crashkernel的内存大小,为什么需要内核单独保留一小块内存? 因为这确保了来自系统内核的正在进行的直接内存访问(DMA)不会破坏转储捕获内核。
如何配置?
在X86-64主机上一般是修改/etc/default/grup中的参数来配置及检查, 但是在测试设备上因为是裁剪的系统,并没有grup这个文件,但我们可以知道,配置grup文件的目的就是更改cmdline中的内容,那我们如何去更改cmdline的内容呢?提供以下几个思路:
- 在dts中中添加:修改chosen
- 在BoardConfig中添加
- 在uboot中添加:在源码中添加或者通过setenv配置bootargs变量
- 在android的Makefile中添加
3.4 配置预留内存的大小
(1) 先了解下crashkernel的配置规则
1. 规则
if the RAM is smaller than 512M, then don’t reserve anything (this is the “rescue” case)
if the RAM size is between 512M and 2G (exclusive), then reserve 64M
if the RAM size is larger than 2G, then reserve 128M
2. 格式
crashkernel=<range1>:<size1>[,<range2>:<size2>,...][@offset] range=start-[end]
3. 示例
crashkernel=512M-2G:64M,2G-:128M
(2) rk平台中RAM的大小为4G左右,已经超过了2G
(3) 在ubot在线配置bootargs变量,添加crashkernel参数,,将它配置为258M,如下
(4) 重启设备,查看预留内存设置是否生效
cat /proc/iomen
(5) 查看cmdline
cat /proc/cmdline
(6) 根据cmdline的内容重新修改start.sh
/userapp/kexec -p /userdata/vmlinux --append="storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal crashkernel=128M rw rootwait earlycon=uart8250,mmio32,0xff1a0000 swiotlb=1 console=ttyFIQ0 rootfstype=squashfs coherent_pool=1m pci=nomsi"
3.5 执行 start.sh 脚本
报了以下的错误,崩溃~
kexec 源码中全局搜索,打印信息多在代码如下:
进入代码文件中,定位到具体的函数
现在相对来说就比较清楚了,原来是需要在proc目录下对kallsyms这个节点进行操作,所以现在需要确认,我们的测试设备系统中该节点是否存在
那说明在get_kernel_sym并没有获取到vaddr,返回值为0,接下来的问题自然而然就是为什么没有获取到vaddr呢?我们去分析下while中的代码逻辑
- 打开/proc/kallsyms文件
- 通过fgets依次获取文件中没行的内容,并与symbol = "_text"
- 进行比较;如果相同,则说明获取到了vaddr;
- 如果不同,则会继续,直到完成所有的信息比较,如果没有最后返回0。
为什么会获取不到_text的地址呢?分析下来有以下几个原因:
- 代码问题 --- 开源代码一般很少会出这个问题。
- 在/proc/kallsyms文件中没有 _text 这个信息。
针对原因二:做以下验证
直接使用 cat kallsyms | grep "_text" 命令来筛选"_text"
在RK平台中的结果中也没有_text的地址信息,那_text的信息到底是个什么模样呢?我这当前的服务器上进行了测试
在服务器上 _text 的vaddr = 0x0000000000000000
小总结:到目前为止,我们找到了为什么会报错的原因?就是因为在RK的/proc/kallsyms文件中没有_text的地址。
接下来的问题是:在RK中为什么没有_text的地址信息呢?难道是配置文件中宏没有被配置吗?
在配置文件中全局搜索下关键字:出现 CONFIG_KALLSYMS 和 CONFIG_KALLSYMS_ALL,全部设置为=y,编译烧录升级版本后查看,_text相关的信息有了。
接下来,我们再执行下 start.sh 脚本
出现以下警告:要多崩溃有多崩溃~
定位代码位置,还是在同一个文件中
对于版本在4.19之前的内核都会打印出这句话,因此不算错误信息,是个警告信息。
先看下RK所使用的内核版本
4. 检查捕获内核加载状态
执行完start.sh脚本后,我们需要查看以下几个信息
(1) 确认捕获内核相关的状态
1. 查看捕获内核的加载状态 0:未加载,1:已加载
cat /sys/kernel/kexec_crash_loaded
2. 查看捕获内核的大小
cat /sys/kernel/kexec_crash_size
rk中的捕获内核状态如下
(2) 确认 kexec_load_disabled 的状态
kexec_load_disabled:表示kexec_load系统调用是否被禁止,此系统调用用于kdump。当发生了一次kexec_load后,此值会自动设置为1。
- 0:开启kexec_load系统调用
- 1:禁止kexec_load系统调用
5. 测试启动捕获内核
在前面的准备工作完成后,如果触发系统崩溃,系统将重新引导到转储-捕获内核,触发点位于panic()、die()、die_nmi()和sysrq处理程序中。接下来我将通过 魔术键来触发系统panic。
【1】开启sysrq
echo 1 > /proc/sys/kernel/sysrq
【2】触发sysrq
echo c > /proc/sysrq-trigger
触发sysrq后,系统重启,但是在串口打印信息中并没有出现标志性 log:Starting crashdump kernel...,也就是验证下来并没有进入crash_kernel操作。
崩溃~~
记录一次失败的尝试~~