1. Linux Vmlinux反汇编操作和代码分析
1.1 对vmlinx反汇编
arm-linux-gnueabi-objdump -d vmlinux > debug.s
1.2 debug.s代码段分析
vmlinux: file format elf32-littlearm
Disassembly of section .head.text:
内核地址 汇编对应的机器码 汇编指令
c0008000 :
//ARM在链接脚本里面,指定了内核的入口是stext
//在vmlinux.lds指定了ENTRY(stext) ,然后. = 0xC0000000 +0x00008000;
// .head.text :...,所以执行地址是内核地址0xc0008000
c0008000: e10f9000 mrs r9, CPSR
c0008004: e229901a eor r9, r9, #26
c0008008: e319001f tst r9, #31
c000800c: e3c9901f bic r9, r9, #31
c0008010: e38990d3 orr r9, r9, #211 ; 0xd3
c0008014: 1a000004 bne c000802c
c0008018: e3899c01 orr r9, r9, #256 ; 0x100
c000801c: e28fe00c add lr, pc, #12
下面这段是函数代码段,当我们把一个函数编译进内核(有系统)的时候,系统加载器会自动帮我们分配执行地址,如下面的c0008084,当内核调用这个函数的时候,根据函数名称就能找到对应的地址并执行
c0008084 <__create_page_tables>:
c0008084: e2884901 add r4, r8, #16384 ; 0x4000
c0008088: e1a00004 mov r0, r4
c000808c: e3a03000 mov r3, #0
c0008090: e2806901 add r6, r0, #16384 ; 0x4000
c0008094: e4803004 str r3, [r0], #4
c0008098: e4803004 str r3, [r0], #4
c000809c: e4803004 str r3, [r0], #4
说明:那么我们得到这个反汇编的文件后有什么用处呢?下面结合oops栈回溯来介绍
2. oops 栈回朔分析
2.1 oops是什么?
这里,我们要理解一下oops是什么?oops语义上类似于惊讶,也类似于拟声词,像”哎呦”,所以对于程序来说,就如“哎呦,出错了!”,然后把出错的地址,各个寄存器的值等等打印出来。 首先,我们根据内核打印信息的段错误信息来分析,例子如下:
Unable to handle kernel paging request at virtual address 56000050
//内核使用56000050来访问时发生了错误
pgd = c3eb0000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
//5表示错误代码,#1表示这个错误发生一次
Modules linked in: first_drv
// 在哪个模块出错
CPU: 0 Not tainted (2.6.22.6 #1)
// 发生错误的CPU序号
PC is at first_drv_open+0x18(该指令的偏移)/0x3c(该函数的总大小) [first_drv]
// 出错函函数,PC就是发生错误的指令的地址
LR is at chrdev_open+0x14c/0x164
// LR寄存器的值,错误函数的栈返回地址
pc = 0xbf000018
// 出错时各个寄存器的值
pc : [] lr : [] psr: a0000013
sp : c3c7be88 ip : c3c7be98 fp : c3c7be94
r10: 00000000 r9 : c3c7a000 r8 : c049abc0
r7 : 00000000 r6 : 00000000 r5 : c3e740c0 r4 : c06d41e0
r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000
// 执行这条导致错误的指令时各个寄存器的值
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 33eb0000 DAC: 00000015
Process firstdrvtest (pid: 777, stack limit = 0xc3c7a258)
//发生错误时当前进程的名称是firstdrvtest
栈
Stack: (0xc3c7be88 to 0xc3c7c000)
be80: c3c7bebc c3c7be98 c008d888 bf000010 00000000 c049abc0
bea0:c3e740c0 c008d73c c0474e20 c3e766a8 c3c7bee4 c3c7bec0 c0089e48 c008d74c
bec0: c049abc0 c3c7bf04 00000003 ffffff9c c002c044 c3d10000 c3c7befc c3c7bee8
bee0: c0089f64 c0089d58 00000000 00000002 c3c7bf68 c3c7bf00 c0089fb8 c0089f40
bf00: c3c7bf04 c3e766a8 c0474e20 00000000 00000000 c3eb1000 00000101 00000001
bf20: 00000000 c3c7a000 c04a7468 c04a7460 ffffffe8 c3d10000 c3c7bf68 c3c7bf48
bf40: c008a16c c009fc70 00000003 00000000 c049abc0 00000002 bec1fee0 c3c7bf94
bf60: c3c7bf6c c008a2f4 c0089f88 00008520 bec1fed4 0000860c 00008670 00000005
bf80: c002c044 4013365c c3c7bfa4 c3c7bf98 c008a3a8 c008a2b0 00000000 c3c7bfa8
bfa0: c002bea0 c008a394 bec1fed4 0000860c 00008720 00000002 bec1fee0 00000001
bfc0: bec1fed4 0000860c 00008670 00000002 00008520 00000000 4013365c bec1fea8
bfe0: 00000000 bec1fe84 0000266c 400c98e0 60000010 00008720 00000000 00000000
Backtrace: (回溯) // 在内核里面,选中FRAME_POINTER ,出错时才会打印出这些消息
[](first_drv_open+0x0/0x3c[first_drv])from[] (chrdev_open+0x14c/0x164)
[] (chrdev_open+0x0/0x164) from [] (__dentry_open+0x100/0x1e8)
r8:c3e766a8 r7:c0474e20 r6:c008d73c r5:c3e740c0 r4:c049abc0
[] (__dentry_open+0x0/0x1e8) from [] (nameidata_to_filp+0x34/0x48)
[] (nameidata_to_filp+0x0/0x48) from [] (do_filp_open+0x40/0x48)
r4:00000002
[] (do_filp_open+0x0/0x48) from [] (do_sys_open+0x54/0xe4)
r5:bec1fee0 r4:00000002
[] (do_sys_open+0x0/0xe4) from [] (sys_open+0x24/0x28)
[] (sys_open+0x0/0x28) from [] (ret_fast_syscall+0x0/0x2c)
Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000)
Segmentation fault
2.2 oops调试步骤
根据上面的回朔信息,我们可以如下调试: (1) 根据pc值,查看出错的地址是在内核还是在insmod 加载的驱动在内核里面,执行 cat System.map 查看内核的地址范围
(2) 查看内核函数,加载函数的地址 cat /proc/kallsyms > xx.text 这个xx.text里面,现在就有各个函数里面的地址根据pc值,找出一个相近的地址,这个地址<= pc值,找到 bf000000 t first_drv_open [first_drv] 出错的就是first_drv_open函数,t代是static函数
(3)反汇编
如果是在insmod加载的驱动的错误 arm-linux-objdump -D xxx.ko > xxx.dis 如果是在内核中的错误 arm-linux-objdump -D vmlinux > vmlinux.s
(4)在反汇编文件(.s)里面搜索first_drv_open
如果是内核,在内核的.dis里面查找pc值,例如
c014e6a8 :
c014e6a8: e1a0c00d mov ip, sp
c014e6ac: e92dd800 stmdb sp!, {fp, ip, lr, pc}
c014e6b0: e24cb004 sub fp, ip, #4 ; 0x4
c014e6b4: e59f1024 ldr r1, [pc, #36] ; c014e6e0 <.text+0x1276e0>
c014e6b8: e3a00000 mov r0, #0 ; 0x0
c014e6bc: e5912000 ldr r2, [r1]
c014e6c0: e5923000 ldr r3, [r2] // 在此出错 r2=56000050
如果是insmod加载的函数,找个first_drv_open,然后确定是哪个寄存器的值出错了。first_drv.s文件insmod后
00000000 :
bf000000 t first_drv_open [first_drv]
00000018
pc = bf000018
这里的00000018 就是.s出错的地址,后面有汇编代码,可以确定是哪个寄存器,根据上面寄存器的值,就可以得出哪里出错。
文章来源:海思 https://www.ebaina.com/articles/140000004860