华为欧拉服务器操作系统软件 V2.0 管理员指南 05
常见问题分析方法
踩堆栈问题
- 编写代码模拟stack被踩。
#include <linux/kernel.h> #include <linux/module.h> static int __init a_init(void) { char msg[10] = {0}; printk("a init\n"); strcpy(msg, "this modules is testing module, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); return 0; } static void __exit a_exit(void) { printk("a exit\n"); } module_init(a_init) module_exit(a_exit) MODULE_LICENSE("GPL");
- 分析方法:
使用“bt”命令显示出问题进程堆栈,发现无法解析堆栈。
根据RSP: ffff8801fddabf20,读取stack的内存,根据内存内容规律推出代码位置。
踩页表问题
- 编写代码模拟页表被踩。
#include <linux/kernel.h> #include <linux/module.h> #include <linux/kprobes.h> #include <linux/kallsyms.h> #include <linux/syscalls.h> #include <linux/slab.h> #include <linux/kdebug.h> #include <asm/apic.h> #include <asm/pgalloc.h> static int __init jprobe_init(void) { unsigned long address; pgd_t *pgd; pud_t *pud; pmd_t *pmd; printk(KERN_INFO "zk--- in \n"); address = (unsigned long)kmalloc(512 ,GFP_KERNEL); pgd = pgd_offset(current->active_mm, address); pud = pud_offset(pgd, address); pmd = pmd_offset(pud, address); memset(pmd, 0, 16); printk("test: %x \n", *(int*)address); return 0; } static void __exit jprobe_exit(void) { printk(KERN_INFO "zk--- out \n"); } module_init(jprobe_init) module_exit(jprobe_exit) MODULE_LICENSE("GPL");
- 分析方法:
观察出错的堆栈,可以发现是一个地址错误。
观察RIP: ffffffff8108af68的回报指令,该指令用到(RAX+0x10)的地址。
观察这个地址的页表,可以发现PMD指向了0,说明这个地址的页表有问题。
由此,初步分析出页表被踩后,可以通过被非法访问内存空间的内容,推出可能的模块;或者通过crash时间点的附近日志分析可疑模块。
踩静态变量问题
分析方法:
通过“sym -l”命令可以看到内核符号,先找到被踩静态变量的地址,然后观察其附近地址还有哪些静态变量。常见的场景是其前面的某个变量,操作过程中溢出,造成周围变量都被踩。
硬件DMA非法访问内存空间问题
- 问题现象
堆栈没有任何规律,唯一的相同点就是code区域是一样的,而且数值是有规律的,全部是“fe 0b ad ca”。
[ 3051.054204] Call Trace: [ 3051.057035] [<ffffffff8115faa9>] path_openat+0xd9/0x420 [ 3051.063054] [<ffffffff8115ff2c>] do_filp_open+0x4c/0xc0 [ 3051.069103] [<ffffffff81150b71>] do_sys_open+0x171/0x1f0 [ 3051.075223] [<ffffffff8144fc53>] ia32_do_call+0x13/0x13 [ 3051.081261] Code: fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad <ca> fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad ca fe 0b ad ca
- 分析方法:
由于内核的code区是只读的,通过线性地址无法去修改,因此可以得出造成这个修改的必然使用的是物理地址,硬件DMA有这个可能。但是目标地址的内容与硬件设备的DMA没有直接联系,除了硬件DMA之外,BIOS有直接操作物理内存的能力,需要分析BIOS代码。
最终发现驱动代码错误,DMA过程踩到BIOS,造成BIOS运行过程踩到内核内存。
死锁问题
- 分析方法
死锁问题,关键点是当时每个CPU的堆栈是什么。 通过“bt -a”命令可以打印出系统当时所有CPU运行的堆栈,通过解析锁的数据结构,可以分析出哪个线程持有锁。
- 常见死锁类型:
- spinlock保护区中执行了非原子性的流程,如sleep schedule流程。
- spinlock保护区中执行逻辑消耗的时间太长。
- AB-BA死锁。
- AA死锁,重复上锁。
- 环形锁,在某些复杂架构设计中,模块间的等待可能出现环形,这会造成死锁。
- 上锁和解锁不对称,锁指针运行中会被修改。