• 反汇编调试内核驱动 Oops提示【转】


    以下部分内容转自:https://blog.csdn.net/jiatingqiang/article/details/7481497

    反汇编调试内核驱动

    arm-none-linux-gnueabi-objdump -S kmod-demo1.o  > a.txt

    什么是Oops?从语言学的角度说,Oops应该是一个拟声词。当出了点小事故,或者做了比较尴尬的事之后,你可以说"Oops",翻译成中国话就叫做“哎呦”。“哎呦,对不起,对不起,我真不是故意打碎您的杯子的”。看,Oops就是这个意思。

    在Linux内核开发中的Oops是什么呢?其实,它和上面的解释也没什么本质的差别,只不过说话的主角变成了Linux。当某些比较致命的问题出现时,我们的Linux内核也会抱歉的对我们说:“哎呦(Oops),对不起,我把事情搞砸了”。Linux内核在发生kernel panic时会打印出Oops信息,把目前的寄存器状态、堆栈内容、以及完整的Call trace都show给我们看,这样就可以帮助我们定位错误。

    下面,我们来看一个实例。为了突出本文的主角--Oops,这个例子唯一的作用就是造一个空指针引用错误。

    #include <linux/kernel.h>
    #include <linux/module.h>
     
    static int __init hello_init(void)
    {
        int *p = 0;
         
        *p = 1;
        return 0;
    }
     
    static void __exit hello_exit(void)
    {
        return;
    }
     
    module_init(hello_init);
    module_exit(hello_exit);
     
    MODULE_LICENSE("GPL");

    很明显,错误的地方就是第8行。

    接下来,我们把这个模块编译出来,再用insmod来插入到内核空间,正如我们预期的那样,Oops出现了。

    [  100.243737] BUG: unable to handle kernel NULL pointer dereference at (null)

    [  100.244985] IP: [<f82d2005>] hello_init+0x5/0x11 [hello]

    [  100.262266] *pde = 00000000 

    [  100.288395] Oops: 0002 [#1] SMP 

    [  100.305468] last sysfs file: /sys/devices/virtual/sound/timer/uevent

    [  100.325955] Modules linked in: hello(+) vmblock vsock vmmemctl vmhgfs acpiphp snd_ens1371 gameport snd_ac97_codec ac97_bus snd_pcm_oss snd_mixer_oss snd_pcm snd_seq_dummy snd_seq_oss snd_seq_midi snd_rawmidi snd_seq_midi_event snd_seq snd_timer snd_seq_device ppdev psmouse serio_raw fbcon tileblit font bitblit softcursor snd parport_pc soundcore snd_page_alloc vmci i2c_piix4 vga16fb vgastate intel_agp agpgart shpchp lp parport floppy pcnet32 mii mptspi mptscsih mptbase scsi_transport_spi vmxnet

    [  100.472178] [  100.494931] Pid: 1586, comm: insmod Not tainted (2.6.32-21-generic #32-Ubuntu) VMware Virtual Platform

    [  100.540018] EIP: 0060:[<f82d2005>] EFLAGS: 00010246 CPU: 0

    [  100.562844] EIP is at hello_init+0x5/0x11 [hello]

    [  100.584351] EAX: 00000000 EBX: fffffffc ECX: f82cf040 EDX: 00000001

    [  100.609358] ESI: f82cf040 EDI: 00000000 EBP: f1b9ff5c ESP: f1b9ff5c

    [  100.631467]  DS: 007b ES: 007b FS: 00d8 GS: 00e0 SS: 0068

    [  100.657664] Process insmod (pid: 1586, ti=f1b9e000 task=f137b340 task.ti=f1b9e000)

    [  100.706083] Stack:

    [  100.731783]  f1b9ff88 c0101131 f82cf040 c076d240 fffffffc f82cf040 0072cff4 f82d2000

    [  100.759324] <0> fffffffc f82cf040 0072cff4 f1b9ffac c0182340 f19638f8 f137b340 f19638c0

    [  100.811396] <0> 00000004 09cc9018 09cc9018 00020000 f1b9e000 c01033ec 09cc9018 00015324

    [  100.891922] Call Trace:

    [  100.916257]  [<c0101131>] ? do_one_initcall+0x31/0x190

    [  100.943670]  [<f82d2000>] ? hello_init+0x0/0x11 [hello]

    [  100.970905]  [<c0182340>] ? sys_init_module+0xb0/0x210

    [  100.995542]  [<c01033ec>] ? syscall_call+0x7/0xb

    [  101.024087] Code: <c7> 05 00 00 00 00 01 00 00 00 5d c3 00 00 00 00 00 00 00 00 00 00 

    [  101.079592] EIP: [<f82d2005>] hello_init+0x5/0x11 [hello] SS:ESP 0068:f1b9ff5c

    [  101.134682] CR2: 0000000000000000

    [  101.158929] ---[ end trace e294b69a66d752cb ]---

    Oops首先描述了这是一个什么样的bug,然后指出了发生bug的位置,即“IP: [<f82d2005>] hello_init+0x5/0x11 [hello]”。

    在这里,我们需要用到一个辅助工具objdump来帮助分析问题。objdump可以用来反汇编,命令格式如下:

    objdump -S  hello.o

    下面是hello.o反汇编的结果,而且是和C代码混排的,非常的直观。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    hello.o:     file format elf32-i386
     
     
    Disassembly of section .init.text:
     
    00000000 <init_module>:
    #include <linux/kernel.h>
    #include <linux/module.h>
     
    static int __init hello_init(void)
    {
       0:   55                      push   %ebp
        int *p = 0;
         
        *p = 1;
         
        return 0;
    }
       1:   31 c0                   xor    %eax,%eax
    #include <linux/kernel.h>
    #include <linux/module.h>
     
    static int __init hello_init(void)
    {
       3:   89 e5                   mov    %esp,%ebp
        int *p = 0;
         
        *p = 1;
       5:   c7 05 00 00 00 00 01    movl   $0x1,0x0
       c:   00 00 00
         
        return 0;
    }
       f:   5d                      pop    %ebp
      10:   c3                      ret   
     
    Disassembly of section .exit.text:
     
    00000000 <cleanup_module>:
     
    static void __exit hello_exit(void)
    {
       0:   55                      push   %ebp
       1:   89 e5                   mov    %esp,%ebp
       3:   e8 fc ff ff ff          call   4 <cleanup_module+0x4>
        return;
    }
       8:   5d                      pop    %ebp
       9:   c3                      ret   

    对照Oops的提示,我们可以很清楚的看到,出错的位置hello_init+0x5的汇编代码是:

    1
    5:c7 05 00 00 00 00 01 movl   $0x1,0x0

    这句代码的作用是把数值1存入0这个地址,这个操作当然是非法的。

    我们还能看到它对应的c代码是:

    1
    *p = 1;

    Bingo!在Oops的帮助下我们很快就解决了问题。

    我们再回过头来检查一下上面的Oops,看看Linux内核还有没有给我们留下其他的有用信息。

    Oops: 0002 [#1]

    这里面,0002表示Oops的错误代码(写错误,发生在内核空间),#1表示这个错误发生一次。

    Oops的错误代码根据错误的原因会有不同的定义,本文中的例子可以参考下面的定义(如果发现自己遇到的Oops和下面无法对应的话,最好去内核代码里查找):

     * error_code:
     *      bit 0 == 0 means no page found, 1 means protection fault
     *      bit 1 == 0 means read, 1 means write
     *      bit 2 == 0 means kernel, 1 means user-mode
     *      bit 3 == 0 means data, 1 means instruction

    有时候,Oops还会打印出Tainted信息。这个信息用来指出内核是因何种原因被tainted(直译为“玷污”)。具体的定义如下:

      1: 'G' if all modules loaded have a GPL or compatible license, 'P' if any proprietary module has been loaded.  Modules without a MODULE_LICENSE or with a MODULE_LICENSE that is not recognised by insmod as GPL compatible are assumed to be proprietary.
      2: 'F' if any module was force loaded by "insmod -f", ' ' if all modules were loaded normally.
      3: 'S' if the oops occurred on an SMP kernel running on hardware that hasn't been certified as safe to run multiprocessor. Currently this occurs only on various Athlons that are not SMP capable.
      4: 'R' if a module was force unloaded by "rmmod -f", ' ' if all modules were unloaded normally.
      5: 'M' if any processor has reported a Machine Check Exception, ' ' if no Machine Check Exceptions have occurred.
      6: 'B' if a page-release function has found a bad page reference or some unexpected page flags.
      7: 'U' if a user or user application specifically requested that the Tainted flag be set, ' ' otherwise.
      8: 'D' if the kernel has died recently, i.e. there was an OOPS or BUG.
      9: 'A' if the ACPI table has been overridden.
     10: 'W' if a warning has previously been issued by the kernel. (Though some warnings may set more specific taint flags.)
     11: 'C' if a staging driver has been loaded.
     12: 'I' if the kernel is working around a severe bug in the platform firmware (BIOS or similar).

    基本上,这个Tainted信息是留给内核开发者看的。用户在使用Linux的过程中如果遇到Oops,可以把Oops的内容发送给内核开发者去debug,内核开发者根据这个Tainted信息大概可以判断出kernel panic时内核运行的环境。如果我们只是debug自己的驱动,这个信息就没什么意义了。

    本文的这个例子非常简单,Oops发生以后没有造成宕机,这样我们就可以从dmesg中查看到完整的信息。但更多的情况是Oops发生的同时系统也会宕机,此时这些出错信息是来不及存入文件中的,关掉电源后就无法再看到了。我们只能通过其他的方式来记录:手抄或者拍照。

    还有更坏的情况,如果Oops信息过多的话,一页屏幕显示不全,我们怎么来查看完整的内容呢?第一种方法,在grub里用vga参数指定更高的分辨率以使屏幕可以显示更多的内容。很明显,这个方法其实解决不了太多的问题;第二种方法,使用两台机器,把调试机的Oops信息通过串口打印到宿主机的屏幕上。但现在大部分的笔记本电脑是没有串口的,这个解决方法也有很大的局限性;第三种方法,使用内核转储工具kdump把发生Oops时的内存和CPU寄存器的内容dump到一个文件里,之后我们再用gdb来分析问题。

    开发内核驱动的过程中可能遇到的问题是千奇百怪的,调试的方法也是多种多样,Oops是Linux内核给我们的提示,我们要用好它。

    #################################

    修订:

    2010-11-19: Oops的error code

    objdump命令的使用

    objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它还有其他作用,下面以ELF格式可执行文件test为例详细介绍:

    objdump -f test

    显示test的文件头信息

    objdump -d test

    反汇编test中的需要执行指令的那些section

    objdump -D test

    与-d类似,但反汇编test中的所有section

    objdump -h test

    显示test的Section Header信息

    objdump -x test

    显示test的全部Header信息

    objdump -s test

    除了显示test的全部Header信息,还显示他们对应的十六进制文件代码

    举例:

    将C源代码和反汇编出来的指令对照:

    1.

    编译成目标文件(要加-g选项)

    gcc -g -o test.c

    2.

    输出C源代码和反汇编出来的指令对照的格式

    objdump -S test.o

    如下:

    如何对任意一个二进制文件进行反汇编?

    我们可以这样做:

    objdump -D -b binary -m i386 a.bin

    -D表示对全部文件进行反汇编,-b表示二进制,-m表示指令集架构,a.bin就是我们要反汇编的二进制文件

    objdump -m可以查看更多支持的指令集架构,如i386:x86-64,i8086等

    另外上面的所有objdump命令的参数同样适用于arm-linux-objdump。

    同时我们也可以指定big-endian或little-endian(-EB或-EL),我们可以指定从某一个位置开始反汇编等。所以objdump命令是非常强大的!

    转自:http://blog.csdn.net/muyuyuzhong/article/details/7755291

    arm-linux-gcc -wall -O2 -c -o $@ $<
     
    -o 只激活预处理,编译,和汇编,也就是他只把程序做成obj文件 
    -Wall 指定产生全部的警告信息 
    -O2 编译器对程序提供的编译优化选项,在编译的时候使用该选项,可以使生成的执行文件的执行效率提高 
    -c 表示只要求编译器进行编译,而不要进行链接,生成以源文件的文件名命名但把其后缀由 .c 或 .cc 变成 .o 的目标文件 
    -S 只激活预处理和编译,就是指把文件编译成为汇编代码
     
    arm-linux-ld 
    直接指定代码段,数据段,BSS段的起始地址
    -Ttest startaddr
    -Tdata startaddr
    -Tbss startaddr
    示例:
    Arm-linux-ld –Ttext 0x0000000 –g led.o –o led_elf
     
    使用连接脚本设置地址:
    Arm-linux-ld –Ttimer.lds –o timer_elf  $^
    其中timer.lds 为连接脚本
    完整的连接脚本格式:
    SECTIONS{

    Secname start ALING(aling) (NOLOAD):AT(ldaddr)
    {contents} > region:phdr=fill
    …..
    }
    arm-linux-objcopy被用来复制一个目标文件的内容到另一个文件中,可用于不同源文件的之间的格式转换
    示例:
    Arm-linux-objcopy –o binary –S elf_file bin_file
    常用的选项:
    input-file , outflie
    输入和输出文件,如果没有outfile,则输出文件名为输入文件名
    2.-l bfdname或—input-target=bfdname
    用来指明源文件的格式,bfdname是BFD库中描述的标准格式名,如果没指明,则arm-linux-objcopy自己分析
    3.-O bfdname 输出的格式
    4.-F bfdname 同时指明源文件,目的文件的格式
    5.-R sectionname 从输出文件中删除掉所有名为sectionname的段
    6.-S 不从源文件中复制重定位信息和符号信息到目标文件中
    7.-g 不从源文件中复制调试符号到目标文件中
     
    arm-linux-objdump
    查看目标文件(.o文件)和库文件(.a文件)信息
    arm-linux-objdump -D -m arm led_elf > led.dis
    -D 显示文件中所有汇编信息
    -m machine
    指定反汇编目标文件时使用的架构,当待反汇编文件本身没有描述架构信息的时候(比如S-records),这个选项很有用。可以用-i选项列出这里能够指定的架构.

    常用选项:

    1.-b bfdname 指定目标码格式
    2.—disassemble或者-d 反汇编可执行段
    3.—dissassemble-all或者-D 反汇编所有段
    4.-EB,-EL指定字节序
    5.—file-headers或者-f 显示文件的整体头部摘要信息
    6.—section-headers,--headers或者-h 显示目标文件中各个段的头部摘要信息
    7.—info 或者-I 显示支持的目标文件格式和CPU架构
    8.—section=name或者-j name显示指定section 的信息
    9.—architecture=machine或者-m machine 指定反汇编目标文件时使用的架构

    1. 修改源代码的顶层  Makefile
       CC =$(CROSSCOM_PILE)gcc            -->
       CC =$(CROSSCOM_PILE)gcc   -g
       使成生的vmlinux中含有debug信息
    2. 所有生成.o的rule中再加一条    
       CC   -E  -dD -C $< > /preprocessing/$(shell pwd)/$<
       生成预处理文件从这个文件里面能很容易找到c源文件的宏定义
    3. objdump -h vmlinux > vmlinux.txt
       显示  linux 内核段信息,如段的开始虚拟地址,段的长度
    4. objdump -S -l -z vmlinux > vmlinux.txt
       反汇编vmlinux到vmlinux.txt,vmlinux.txt含有汇编和c源文件的混合代码,看起来很方便。而且能一步步看linux怎么一步步运行的。
    5. objdump -S -l -z -j xxxx(section name) vmlinux > vmlinux.txt
       反汇编linux内核段xxxx到文件vmlinux.txt中。
    6. objdump -x vmlinux > x.txt
       vmliux中所有段的头信息,其中包口vmlinux的入口地址等
    7. objdump --debugging vmlinux > debugging.txt
       很多有用的debug信息,如函数名,结构体定义等
       我觉的用根据以上信息,ultraedit看很方便。尤其在vmlinux.txt中选中文件名,
       用ultraedit右键的open能马上打开文件,很方便。


    objdump -j .text -S vmlinux > vmlinux.txt
    -S尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,效果比较明显。隐含了-d参数。

    -l用文件名和行号标注相应的目标代码,仅仅和-d、-D或者-r一起使用使用-ld和使用-d的区别不是很大,在源码级调试的时候有用,要求编译时使用了-g之类的调试编译选项。

    [-l | --line-numbers]
    [-S | --source]    混合汇编
    [-z | --disassemble-zeroes]
    [-j section | --section=section]
    [--prefix-addresses]

    驱动程序的调试
    一. 打印: prink, 自制proc文件
    UBOOT传入console=ttySAC0 console=tty1
    1. 内核处理UBOOT传入的参数
    console_setup
        add_preferred_console // 我想用名为"ttySAC0"的控制台,先记录下来
    
    2. 硬件驱动的入口函数里:
        drivers/serial/s3c2410.c
            register_console(&s3c24xx_serial_console);        
    
    3. printk
            vprintk
                /* Emit the output into the temporary buffer */
                // 先把输出信息放入临时BUFFER
                vscnprintf
                
                // Copy the output into log_buf.
                // 把临时BUFFER里的数据稍作处理,再写入log_buf
                // 比如printk("abc")会得到"<4>abc", 再写入log_buf
                // 可以用dmesg命令把log_buf里的数据打印出来重现内核的输出信息
                
                
                // 调用硬件的write函数输出
                release_console_sem();
                    call_console_drivers(_con_start, _log_end);
                        // 从log_buf得到数据,算出打印级别
                        _call_console_drivers(start_print, cur_index, msg_level);            
                            // 如果可以级别够格打印
                            if ((msg_log_level < console_loglevel
                                __call_console_drivers
                                    con->write(con, &LOG_BUF(start), end - start);
    
    
    
    
    二. 根据内核打印的段错误信息分析
    a. 作为模块:
    1. 根据pc值确定该指令属于内核还是外加的模块
    pc=0xbf000018 它属于什么的地址?是内核还是通过insmod加载的驱动程序?
    先判断是否属于内核的地址: 看System.map确定内核的函数的地址范围:c0004000~c03265a4
    
    如果不属于System.map里的范围,则它属于insmod加载的驱动程序
    
    2. 假设它是加载的驱动程序引入的错误,怎么确定是哪一个驱动程序?
    先看看加载的驱动程序的函数的地址范围
    cat /proc/kallsyms  (内核函数、加载的函数的地址)
    从这些信息里找到一个相近的地址, 这个地址<=0xbf000018
    比如找到了:
    bf000000 t first_drv_open    [first_drv]
    
    3. 找到了first_drv.ko
    在PC上反汇编它: arm-linux-objdump -D first_drv.ko > frist_drv.dis
    在dis文件里找到first_drv_open
    
        first_drv.dis文件里              insmod后
    00000000 <first_drv_open>:       bf000000 t first_drv_open    [first_drv]
    00000018                         pc = bf000018
                                     
    
    
    
    ./firstdrvtest on
    Unable to handle kernel paging request at virtual address 56000050
    内核使用56000050来访问时发生了错误
    
    pgd = c3eb0000
    [56000050] *pgd=00000000
    Internal error: Oops: 5 [#1]
    Modules linked in: first_drv
    CPU: 0    Not tainted  (2.6.22.6 #1)
    PC is at first_drv_open+0x18(该指令的偏移)/0x3c(该函数的总大小) [first_drv]
    PC就是发生错误的指令的地址
    大多时候,PC值只会给出一个地址,不到指示说是在哪个函数里
    
    LR is at chrdev_open+0x14c/0x164
    LR寄存器的值
    
    pc = 0xbf000018
    
    pc : [<bf000018>]    lr : [<c008d888>]    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: (回溯)
    [<bf000000>] (first_drv_open+0x0/0x3c [first_drv]) from [<c008d888>] (chrdev_open+0x14c/0x164)
    [<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
     r8:c3e766a8 r7:c0474e20 r6:c008d73c r5:c3e740c0 r4:c049abc0
    [<c0089d48>] (__dentry_open+0x0/0x1e8) from [<c0089f64>] (nameidata_to_filp+0x34/0x48)
    [<c0089f30>] (nameidata_to_filp+0x0/0x48) from [<c0089fb8>] (do_filp_open+0x40/0x48)
     r4:00000002
    [<c0089f78>] (do_filp_open+0x0/0x48) from [<c008a2f4>] (do_sys_open+0x54/0xe4)
     r5:bec1fee0 r4:00000002
    [<c008a2a0>] (do_sys_open+0x0/0xe4) from [<c008a3a8>] (sys_open+0x24/0x28)
    [<c008a384>] (sys_open+0x0/0x28) from [<c002bea0>] (ret_fast_syscall+0x0/0x2c)
    Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000) 
    Segmentation fault
    # 
    
    b. 编入内核
    Modules linked in:
    CPU: 0    Not tainted  (2.6.22.6 #2)
    PC is at first_drv_open+0x18/0x3c
    LR is at chrdev_open+0x14c/0x164
    pc : [<c014e6c0>]    lr : [<c008638c>]    psr: a0000013
    sp : c3a03e88  ip : c3a03e98  fp : c3a03e94
    r10: 00000000  r9 : c3a02000  r8 : c03f3c60
    r7 : 00000000  r6 : 00000000  r5 : c38a0c50  r4 : c3c1e780
    r3 : c014e6a8  r2 : 56000050  r1 : c031a47c  r0 : 00000000
    Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user
    Control: c000717f  Table: 339f0000  DAC: 00000015
    Process firstdrvtest (pid: 750, stack limit = 0xc3a02258)
    
    1. 根据pc值确定该指令属于内核还是外加的模块
    pc=c014e6c0 属于内核(看System.map)
    
    2. 反汇编内核: arm-linux-objdump -D vmlinux > vmlinux.dis
    在dis文件里搜c014e6c0
    c014e6a8 <first_drv_open>:
    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
    
    
    
    3. 根据栈信息分析函数调用过程
    # ./firstdrvtest on
    Unable to handle kernel paging request at virtual address 56000050
    pgd = c3e78000
    [56000050] *pgd=00000000
    Internal error: Oops: 5 [#1]
    Modules linked in: first_drv
    CPU: 0    Not tainted  (2.6.22.6 #48)
    PC is at first_drv_open+0x18/0x3c [first_drv]
    LR is at chrdev_open+0x14c/0x164
    pc : [<bf000018>]    lr : [<c008c888>]    psr: a0000013
    3.1 根据PC确定出错位置
    bf000018 属于 insmod的模块
    bf000000 t first_drv_open       [first_drv]
    
    3.2 确定它属于哪个函数
    反汇编first_drv.ko
    
    
    
    
    
    
    sp : c3e69e88  ip : c3e69e98  fp : c3e69e94
    r10: 00000000  r9 : c3e68000  r8 : c0490620
    r7 : 00000000  r6 : 00000000  r5 : c3e320a0  r4 : c06a8300
    r3 : bf000000  r2 : 56000050  r1 : bf000964  r0 : 00000000
    Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user
    Control: c000717f  Table: 33e78000  DAC: 00000015
    Process firstdrvtest (pid: 752, stack limit = 0xc3e68258)
    Stack: (0xc3e69e88 to 0xc3e6a000)
    9e80:                   c3e69ebc c3e69e98 c008c888 bf000010 00000000 c0490620 
                            first_drv_open'sp    lr             chrdev_open'sp
    
    9ea0: c3e320a0 c008c73c c0465e20 c3e36cb4 c3e69ee4 c3e69ec0 c0088e48 c008c74c 
                                                                lr    
                                                                                     
    9ec0: c0490620 c3e69f04 00000003 ffffff9c c002b044 c06e0000 c3e69efc c3e69ee8 
          __dentry_open'sp
    
    9ee0: c0088f64 c0088d58 00000000 00000002 c3e69f68 c3e69f00 c0088fb8 c0088f40 
          lr                nameidata_to_filp'sp                lr
          
    9f00: c3e69f04 c3e36cb4 c0465e20 00000000 00000000 c3e79000 00000101 00000001 
          do_filp_open'sp
    
    9f20: 00000000 c3e68000 c04c1468 c04c1460 ffffffe8 c06e0000 c3e69f68 c3e69f48 
    9f40: c008916c c009ec70 00000003 00000000 c0490620 00000002 be94eee0 c3e69f94 
    9f60: c3e69f6c c00892f4 c0088f88 00008520 be94eed4 0000860c 00008670 00000005 
                   lr                do_sys_open'sp
    
    9f80: c002b044 4013365c c3e69fa4 c3e69f98 c00893a8 c00892b0 00000000 c3e69fa8 
                                              lr                sys_open'sp
    
    9fa0: c002aea0 c0089394 be94eed4 0000860c 00008720 00000002 be94eee0 00000001 
          lr                ret_fast_syscall'sp
                            
    9fc0: be94eed4 0000860c 00008670 00000002 00008520 00000000 4013365c be94eea8 
    9fe0: 00000000 be94ee84 0000266c 400c98e0 60000010 00008720 00000000 00000000 
    
    
    三. 自制工具
    寄存器编辑器
    
    
    四. 修改内核来定位系统僵死问题
    ./firstdrvtest on 
    asm_do_IRQ => s3c2410_timer_interrupt : pid = 752, task name = firstdrvtest
    pc = bf000084
    asm_do_IRQ => s3c2410_timer_interrupt : pid = 752, task name = firstdrvtest
    pc = bf000084   // 对于中断, pc-4才是发生中断瞬间的地址
    /proc/kallsyms                                
    first_drv.dis
    00000000 <first_drv_open>:                     bf000000 t first_drv_open    [first_drv]         
    0000003c <first_drv_write>:
      3c:    e1a0c00d     mov    ip, sp
      40:    e92dd800     stmdb    sp!, {fp, ip, lr, pc}
      44:    e24cb004     sub    fp, ip, #4    ; 0x4
      48:    e24dd004     sub    sp, sp, #4    ; 0x4
      4c:    e3cd3d7f     bic    r3, sp, #8128    ; 0x1fc0
      50:    e3c3303f     bic    r3, r3, #63    ; 0x3f
      54:    e5933008     ldr    r3, [r3, #8]
      58:    e0910002     adds    r0, r1, r2
      5c:    30d00003     sbcccs    r0, r0, r3
      60:    33a03000     movcc    r3, #0    ; 0x0
      64:    e3530000     cmp    r3, #0    ; 0x0
      68:    e24b0010     sub    r0, fp, #16    ; 0x10
      6c:    1a00001c     bne    e4 <init_module+0x5c>
      70:    ebfffffe     bl    70 <first_drv_write+0x34>
      74:    ea00001f     b    f8 <init_module+0x70>
      78:    e3520000     cmp    r2, #0    ; 0x0
      7c:    11a01002     movne    r1, r2
      80:    1bfffffe     blne    80 <first_drv_write+0x44>       // 卡死的地方
      84:    ea00001f     b    108 <init_module+0x80>
  • 相关阅读:
    Mac 安装brew的正确姿势
    Jmeter分布式压测及踩坑记录
    Jmeter录制脚本
    mitmproxy使用详解
    Python单元测试框架pytest常用测试报告类型
    基于Java+Spring Boot开源项目JeeSite的Jenkins持续交付介绍
    Maven入门
    Linux Shell 编程基础详解——吐血整理,墙裂推荐!
    使用Jenkins+Blue Ocean 持续构建自动化部署之安卓源码打包、测试、邮件通知
    使用Jenkins+Pipline 持续构建自动化部署之安卓源码打包、测试、邮件通知
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/4765751.html
Copyright © 2020-2023  润新知