Linux Vmlinux反汇编操作和 oops stack 回朔分析

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

你可能感兴趣的:(易百纳)