最近出现了比较多的内核崩溃的问题,之前也曾转发过网上一篇关于内核崩溃问题的跟踪方法,我在这里借xxx提供的这个死机现场,再给大家描述一下这类问题的分析方法和debug过程,也希望能让大家真正掌握这类问题的调试方法。让大家明白,内核崩溃问题的调试方法其实也就是这样一些固定的套路。问题的分析过程如下:
确认死机现场
1 | [56400.437450] Unable to handle kernel paging request at virtual address f7fe44a1 |
根据死机现场的打印分析,死机的原因是因为执行0xc004b93c处的代码访问数据时,取到一个非法地址:0xf7fe44a1,访问该地址的时候,会导致系统异常;
反汇编内核代码,分析原因
通过命令"arm-none-linux-gnueabi-objdump -d vmlinux > vmlinux.lst ",可以反汇编出内核的代码。 固件不是在我的机器上编出来的,所以反汇编得到的代码和在机器上实际运行的不是完全一致的,但是,没关系,这不影响我们分析问题。 由于本地的反汇编代码和设备上的不一致,所以不能用PC值0xc004b93c去定位代码,而是用"PC is at cpuacct_charge+0x68/0xa8"这个信息来确认死机时的代码位置。 "cpuacct_charge+0x68/0xa8"的含义是:函数"cpuacct_charge"的代码为0xa8个字节,出错时,PC位于"cpuacct_charge"偏移0x68字节处。 OK,根据此信息,我们来查看反汇编得到的代码。cpuacct_charge位于0xc004bab4处,偏移0x68字节,即0xc004bab4 + 0x68 = 0xc004bb1c处出现异常。
1 | c004bab4 <cpuacct_charge>: |
再回头看异常现场:r3 : f7fe4479,发现r3 + 0x28 = f7fe44a1,代码和异常现场相吻合。 接下来就是尝试分析出错的代码执行过程,这个有一定的难度,需要结合C源代码,和对汇编代码的理解来进行分析。
定位出错位置的源代码
通过反汇编得知,出错时的PC位置在我编出来的vmlinux的0xc004bb1c地址处,采用命令:
1 | arm-none-linux-gnueabi-addr2line -e vmlinux -f c004bb1c |
来行源码定位: 1
2
3kevin@Exdroid6:~/aw1650-47/lichee/linux-3.4$ arm-none-linux-gnueabi-addr2line -e vmlinux -f c004bb1c
cgroup_subsys_state
/home/kevin/aw1650-47/lichee/linux-3.4/include/linux/cgroup.h:508
发现出错的源代码是在操作一个链表上的指针,这些数据不是全局变量,很难分析出其出错的本质原因,所以源码分析一步在这时可以放弃;
分析出错的汇编代码
结合"cpuacct_charge"函数的C代码实现,再回头看出错时的汇编代码,发现出错的代码其实是位于一个循环体中:
1 | c004baf0: ea00000a b c004bb20 <cpuacct_charge+0x6c> |
出错的代码是:"c004bb1c: e5931028 ldr r1, [r3, #40] ; 0x28",即r3的值是错的,我们来尝试分析r3这个数据的来源。 从:"c004bb1c: e5931028 ldr r1, [r3, #40] ; 0x28"这句代码向前找,发现r3数据的直接来源是r1,
1 | c004bb0c: e5913000 ldr r3, [r1] |
再回头看异常现场的r1值为"r1 : c07312c8",以及r1所指向的数据:
1 | [56400.440040] R1: 0xc0731248: |
可以看到"c004bb0c: e5913000 ldr r3, [r1]"取到的r3值为0xd8dd2018,再看接下来的一条汇编: "c004bb10: e5933018 ldr r3, [r3, #24]",即错误的数据是从0xd8dd2018 + 24 = 0xd8dd2030处取到的。
此时如果想再往下分析,就只能借助Jtag了。
Jtag分析
当前死机的这一台,DS-5无法打开,所以没有办法直接通过DS-5来查看0xd8dd2030处的数据是否真的错了。 此时,可以先推断CPU模块本身已经出错了,导致jtag无法通过CPU访问数据。 我们再来尝试一下用JTAG的CSAT方式,访问一下,发现CSAT可以连接上,而且可以读取物理内存的内容。 由于CSAT是绕过CPU来访问内存的数据,所以无法直接访问"0xd8dd2030"处的内容。但是,我们知道,内核 空间有近700Mbyte的是直接映射的,即0x40000000 -> 0xc0000000,那么0xd8dd2030所对应的物理地址应该 为0x58dd2030,通过CSAT查看一下0x58dd2030处的数据:
1 | %>dmr 0 0x58dc6010 16 |
我们发现,0x58dd2030处的数据是0x00000000,即:0xd8dd2030处的数据应该是0x00000000。
再回头看把汇编出来的代码:
1 | c004bb10: e5933018 ldr r3, [r3, #24] |
如果r3是0,应该是退出循环,而不是再去取值,因此,可以先推断,出错的原因可能是:
- cpu本身出错,导致指令执行不正常,从而导致r3中的数据出错
- DRAM访问出错,导致取回到r3的数据是错的
再结合我们之前的推断,CPU模块本身可能已经出错了,因此,我们更环疑该问题是CPU工作不稳定导致的。 我们再来确认一下此时CPU的工作频率和电压:
1 | %>dmr 0 0x1c20000 16 |
根据寄存器的值可以推出CPU当前工作频率为1008Mhz;测得当前的CPU工作电压为:1.17v;
分析下来,并没有真正查明问题的原因,只是初步推测了一个问题可能的方向,后续的实验可以重点观注该方向上的一些实验。
其他常用命令
根据函数名拿到函数的地址:
1 | arm-linux-gnueabi-nm vmlinux | grep __cpuinfo_store_cpu |
根据出错的位置计算起始和结束地址
1 | $ echo "obase=16;ibase=10;$((0x80100919c0+0x80))" | bc -l |
根据起始地址和结束地址dump出出错的汇编和源码,结合oops现场进行复盘分析
1 | arm-linux-gnueabi-objdump -dS vmlinux --start-address=0xffffff80100919c0 --stop-address=0xffffff8010091b90 |