关于定位linux OOPS的几篇文章

第一篇定位Oops的具体代码行 作者: albcamus (百無一用書生)
(
来自Linus Torvalds的讨论:
[url]https://groups.google.com/group/linux.kernel/browse_thread/thread/b70bffe9015a8c41/ed9c0a0cfcd31111[/url]
又,[url]http://kerneltrap.org/Linux/Further_Oops_Insights[/url]
)
       
            例如这样的一个Oops:
                    Oops: 0000[#1] PREEMPT SMP   
                    Moduleslinked in: capidrv kernelcapi isdn slhc ipv6 loop dm_multipathsnd_ens1371 gameport snd_rawmidi snd_ac97_codec ac97_bussnd_seq_dummy snd_seq_oss snd_seq_midi_event snd_seq snd_seq_devicesnd_pcm_oss snd_mixer_oss snd_pcm snd_timer snd parport_pc floppyparport pcnet32 soundcore mii pcspkr snd_page_alloc ac i2c_piix4i2c_core button power_supply sr_mod sg cdrom ata_piix libatadm_snapshot dm_zero dm_mirror dm_mod BusLogic sd_mod scsi_mod ext3jbd mbcache uhci_hcd ohci_hcd ehci_hcd

                    Pid: 1726,comm: kstopmachine Not tainted (2.6.24-rc3-module #2)
                    EIP:0060:[<c04e53d6>] EFLAGS: 00010092CPU: 0
                    EIP is atlist_del+0xa/0x61
                    EAX:e0c3cc04 EBX: 00000020 ECX: 0000000e EDX: dec62000
                    ESI:df6e8f08 EDI: 000006bf EBP: dec62fb4 ESP: dec62fa4
                        DS: 007b ES:007b FS: 00d8 GS: 0000 SS: 0068
                    Processkstopmachine (pid: 1726, ti=dec62000 task=df8d2d40task.ti=dec62000)
                    Stack:000006bf dec62fb4 c04276c7 00000020 dec62fbc c044ab4c dec62fd0c045336c
                                df6e8f08c04532b4 00000000 dec62fe0 c043deb0 c043de75 0000000000000000
                                c0405cdfdf6e8eb4 00000000 00000000 00000000 00000000 00000000
                    CallTrace:
                        [<c0406081>]show_trace_log_lvl+0x1a/0x2f
                        [<c0406131>]show_stack_log_lvl+0x9b/0xa3
                        [<c04061dc>]show_registers+0xa3/0x1df
                        [<c0406437>]die+0x11f/0x200
                        [<c0613cba>]do_page_fault+0x533/0x61a
                        [<c06123ea>]error_code+0x72/0x78
                        [<c044ab4c>]__unlink_module+0xb/0xf
                        [<c045336c>]do_stop+0xb8/0x108
                        [<c043deb0>]kthread+0x3b/0x63
                        [<c0405cdf>]kernel_thread_helper+0x7/0x10
                        =======================
                    Code: 6b c0e8 2e 7e f6 ff e8 d1 16 f2 ff b8 01 00 00 00 e8 aa 1c f4 ff 89 d883 c4 10 5b 5d c3 90 90 90 55 89 e5 53 83 ec 0c 8b 48 04<8b> 11 39 c2 74 18 89 54 24 08 89 4424 04 c7 04 24 be 32 6bc0   
                    EIP:[<c04e53d6>] list_del+0xa/0x61 SS:ESP0068:dec62fa4
                    note:kstopmachine[1726] exited with preempt_count 1
       
            1,有自己编译的vmlinux: 使用gdb
       
                编译时打开compliewith debug info选项。

                注意这行:
       
                    EIP is atlist_del+0xa/0x61
       
                这告诉我们,list_del函数有0x61这么大,而Oops发生在0xa处。那么我们先看一下list_del从哪里开始:

                    # greplist_del /boot/System.map-2.6.24-rc3-module
                    c10e5234 Tplist_del
                    c10e53cc Tlist_del
                    c120feb6 Tklist_del
                    c12d6d34 r__ksymtab_list_del
                    c12dadfc r__ksymtab_klist_del
                    c12e1abd r__kstrtab_list_del
                    c12e9d03 r__kstrtab_klist_del

                于是我们知道,发生Oops时的EIP值是:

                    c10e53cc +0xa    == c10e53d6

                然后用gdb查看:

                    # gdb/home/arc/build/linux-2.6/vmlinux
                    (gdb) b*0xc10e53d6
                    Breakpoint1 at 0xc10e53d6: file /usr/src/linux-2.6.24-rc3/lib/list_debug.c,line 64.

                看,gdb直接就告诉你在哪个文件、哪一行了。

                gdb中还可以这样:

                    # gdbSources/linux-2.6.24/vmlinux
                    (gdb) l*do_fork+0x1f
                    0xc102b7acis in do_fork (kernel/fork.c:1385).
                    1380
                    1381    static intfork_traceflag(unsigned clone_flags)
                    1382    {
                    1383                if(clone_flags & CLONE_UNTRACED)
                    1384                            return0;
                    1385                else if(clone_flags & CLONE_VFORK) {
                    1386                            if(current->ptrace &PT_TRACE_VFORK)
                    1387                                    returnPTRACE_EVENT_VFORK;
                    1388                } else if((clone_flags & CSIGNAL) != SIGCHLD) {
                    1389                            if(current->ptrace &PT_TRACE_CLONE)
                    (gdb)

                也可以直接知道linenumber。

                或者:

                    (gdb) l*(0xffffffff8023eaf0 +0xff)   



            2,没有自己编译的vmlinux: TIPS

                如果在lkml或bugzilla上看到一个Oops,而自己不能重现,那就只能反汇编以"Code:"开始的行。这样可以尝试定位到
                源代码中。

                注意,Oops中的Code:行,会把导致Oops的第一条指令,也就是EIP的值的第一个字节,用尖括号<>括起来。 但是,有些
                体系结构(例如常见的x86)指令是不等长的(不一样的指令可能有不一样的长度),所以要不断的尝试(trial-and-error)。

                Linus通常使用一个小程序,类似这样:

                    const chararray[] = "\xnn\xnn\xnn...";
                    intmain(int argc, char *argv[])
                    {
                                printf("%p\n", array);
                                *(int *)0 =0;
                    }

e.g.
#include <stdio.h>
#include <stdlib.h>


const char array[]="\x6b\xc0\xe8\x2e\x7e\xf6\xff\xe8\xd1\x16\xf2\xff\xb8\x01\x00\x00\x00\xe8\xaa\x1c\xf4\xff\x89\xd8\x83\xc4\x10\x5b\x5d\xc3\x90\x90\x90\x55\x89\xe5\x53\x83\xec\x0c\x8b\x48\x04\x8b\x11\x39\xc2\x74\x18\x89\x54\x24\x08\x89\x44\x24\x04\xc7\x04\x24\xbe\x32\x6b\xc0";
int main(int argc, char *argv[])
{
            printf("%p\n",array);
            *(int *)0 =0;
}




                用gcc-g编译,在gdb里运行它:

                    [arc@dhcp-cbjs05-218-251 ~]$ gdb hello
                    GNU gdbFedora (6.8-1.fc9)
                    Copyright(C) 2008 Free Software Foundation, Inc.
                    LicenseGPLv3+: GNU GPL version 3 or later<[url]http://gnu.org/licenses/gpl.html[/url]>
                    This isfree software: you are free to change and redistribute it.
                    There is NOWARRANTY, to the extent permitted bylaw.    Type "show copying"
                    and "showwarranty" for details.
                    This GDBwas configured as "x86_64-redhat-linux-gnu"...
                    (nodebugging symbols found)
                    (gdb)r
                    Startingprogram: /home/arc/hello
                    0x80484e0

                    Programreceived signal SIGSEGV, Segmentation fault.

                注意,这时候就可以反汇编0x80484e0这个地址:

                    (gdb)disassemble 0x80484e0
                    Dump ofassembler code for function array:
                    0x080484e0<array+0>:    imul    $0xffffffe8,�x,�x
                    0x080484e3<array+3>:    jle,pn 0x80484dc<__dso_handle+20>
                    0x080484e6<array+6>:    ljmp    *<internal disassemblererror>
                    0x080484e8<array+8>:    rcll    (%esi)
                    0x080484ea<array+10>:    repnz(bad)
                    0x080484ec<array+12>:    mov    $0x1,�x
                    0x080484f1<array+17>:    call    0x7f8a1a0
                    0x080484f6<array+22>:    mov    �x,�x
                    0x080484f8<array+24>:    add    $0x10,%esp
                    0x080484fb<array+27>:    pop    �x
                    0x080484fc<array+28>:    pop    �p
                    0x080484fd<array+29>:    ret
                    0x080484fe<array+30>:    nop
                    0x080484ff<array+31>:    nop
                    0x08048500<array+32>:    nop
                    0x08048501<array+33>:    push    �p
                    0x08048502<array+34>:    mov    %esp,�p
                    0x08048504<array+36>:    push    �x
                    0x08048505<array+37>:    sub    $0xc,%esp
                    0x08048508<array+40>:    mov    0x4(�x),�x
                    0x0804850b<array+43>:    mov    (�x),�x
                    0x0804850d<array+45>:    cmp    �x,�x
                    0x0804850f<array+47>:    je        0x8048529
                    0x08048511<array+49>:    mov    �x,0x8(%esp)
                    0x08048515<array+53>:    mov    �x,0x4(%esp)
                    0x08048519<array+57>:    movl    $0xc06b32be,(%esp)
                    0x08048520<array+64>:    add    %ah,0xa70
                    End ofassembler dump.
                    (gdb)

            OK,现在你知道出错的那条指令是array[43],也就是mov    (�x),�x,也就是说,(�x)指向了一个错误内存地址。

补充:

为了使汇编代码和C代码更好的对应起来, Linux内核的Kbuild子系统提供了这样一个功能:任何一个C文件都可以单独编译成汇编文件,例如:

make path/to/the/sourcefile.s

例如我想把kernel/sched.c编译成汇编,那么:

make kernel/sched.s V=1

或者:

make kernel/sched.lst V=1

            编译出*.s文件
           
            有时侯需要对*.s文件进行分析,以确定BUG所在的位置。 对任何一个内核build目录下的*.c文件,都可以
            直接编译出*.s文件。

                    # make drivers/net/e100.s V=1
           
            而对于自己写的module,就需要在Makefile中有一个灵活的target写法:
                   
                # cat Makefile
                obj-m := usb-skel.o
                KDIR := /lib/modules/`uname-r`/build
                traget := modules

                default:
                        make -C$(KDIR) M=$(shell pwd) $(target)
                clean:
                        rm -f *.o*.ko .*.cmd *.symvers *.mod.c
                        rm -rf.tmp_versions


                # make target=usb-skel.sV=1
           
            这样,kbuild系统才知道你要make的目标不是modules,而是usb-skel.s。




另外, 内核源代码目录的./scripts/decodecode文件是用来解码Oops的:

./scripts/decodecode < Oops.txt
 
第二篇: 定位可动态加载的内核模块的OOPS代码行作者:Godbach (To be 千里马!)
最近又仔细学习了albcamus版主提供的《定位Oops的具体代码行》(链接:http://linux.chinaunix.net/bbs/viewthread.php?tid=1008573),并且进行了实践。因此这里简单总结一下,并且以实例的方式给出定位可动态加载模块Oops信息的方法。

本文欢迎自由转载,但请标明出处,并保证本文的完整性。
Godbach
Apr 19, 2009

1. 从vmlinux获取具体的代码行

文章中albcamus版主也提到了,需要有自己编译的vmlinux,而且编译时打开compile with debug info.这个选项打开之后会使vmlinux文件比不加调试信息大一些。我这里代调试信息的是49M。建议如果学习的时候,想使用gdb的方式获取出错代码行的话,就加上这个编译条件。
然后就可以按照具体的方法去操作,可以定位到具体的C 代码行。

2. 从自己编译的内核模块出错信息中获取代码行
以ldd3中提供的misc-modules/faulty.c为例。主要以faulty_write函数作分析。
(1)由于作者提供的函数代码就一样,过于简单,我这里简单加上一些代码(也就是判断和赋值),如下:

ssize_t faulty_write(struct file *filp,const char __user *buf,size_t count,
                loff_t*pos)
{
        
        if(count>0x100)
                count= 0x100;
        *(int*)0 =0;
        returncount;
}

(2)编译该模块,并且mknod /dev/faulty
(3)向该模块写入数据:echo 1 > /dev/faulty, 内核OOPS,信息如下:

 

<1>BUG: unable to handle kernel NULL pointer dereference at virtual address 00000000
printing eip:
f8e8000e
*pde = 00000000
Oops: 0002 [#3]
SMP
Modules linked in: faultyautofs4 hidp rfcomm l2cap ......
//此处省略若干字符

CPU: 1
EIP: 0060:[<f8e8000e>] Nottainted VLI
EFLAGS: 00010283 (2.6.18.3#2)
EIP is at faulty_write+0xe/0x19 [faulty]
eax: 00000001 ebx: f4f6ca40 ecx: 00000001 edx: b7c2d000
esi: f8e80000 edi: b7c2d000 ebp: 00000001 esp: f4dc5f84
ds: 007b es: 007b ss: 0068
Process bash (pid: 6084,ti=f4dc5000 task=f7c8d4d0 task.ti=f4dc5000)
Stack: c1065914 f4dc5fa4 f4f6ca40 fffffff7b7c2d000 f4dc5000 c1065f06 f4dc5fa4
       0000000000000000 00000000 00000001 00000001 c1003d0b 00000001b7c2d000
       0000000100000001 b7c2d000 bfd40aa8 ffffffda 0000007b c100007b00000004
Call Trace:
[<c1065914>] vfs_write+0xa1/0x143
[<c1065f06>] sys_write+0x3c/0x63
[<c1003d0b>] syscall_call+0x7/0xb
Code: Bad EIP value.
EIP: [<f8e8000e>] faulty_write+0xe/0x19 [faulty] SS:ESP 0068:f4dc5f84

其中,我们应该关注的信息是第一行红色标出部分:告诉我们操作了NULL指针。其次,就是第二行红色部分:EIP is atfaulty_write+0xe/0x19。这个出错信息告诉我们EIP指针出现问题的地方时faulty_write函数,而且指出了是faulty模块。
同时,faulty_write+0xe/0x19的后半部分0xe/0x19,说明该函数的大小时0x019,出错位置是在0x0e。这两个值应该值得都是汇编代码的值。
(4)将faulty模块反汇编出汇编代码:
            objdump -dfaulty.ko > faulty.s
            或
            objdump -dfaulty.o > faulty.s
然后,我们打开faulty.s文件。由于我们需要关注的部分正好在文件的前面,因此我这里只贴出文件的前面一部分内容:

faulty.o:file format elf32-i386

Disassembly of section .text:

00000000 <faulty_write>:
   0: 81 f9 00 01 00 00 cmp $0x100,%ecx
   6: b8 00 01 00 00 mov $0x100,%eax
   b: 0f 46 c1 cmovbe %ecx,%eax
   e: c7 05 00 00 00 00 00 movl$0x0,0x0
  15: 00 00 00
  18: c3 ret

00000019 <cleanup_module>:
  19: a1 00 00 00 00 mov 0x0,%eax
  1e: ba 00 00 00 00 mov $0x0,%edx
  23: e9 fc ff ff ff jmp 24 <cleanup_module+0xb>

00000028 <faulty_init>:
  28: a1 00 00 00 00 mov 0x0,%eax
  2d: b9 00 00 00 00 mov $0x0,%ecx
  32: ba 00 00 00 00 mov $0x0,%edx
  37: e8 fc ff ff ff call 38 <faulty_init+0x10>
  3c: 85 c0 test %eax,%eax
  3e: 78 13 js 53 <faulty_init+0x2b>
  40: 83 3d 00 00 00 00 00 cmpl$0x0,0x0
  47: 74 03 je 4c <faulty_init+0x24>
  49: 31 c0 xor %eax,%eax
  4b: c3 ret
  4c: a3 00 00 00 00 mov %eax,0x0
  51: 31 c0 xor %eax,%eax
  53: c3 ret

由以上汇编代码可以看出,faulty_write函数的大小确实是0x18 -0x00 +1 = 0x19.那么EIP指针出问题的地方是0x0e处,代码为:

e: c7 05 00 00 00 00 00 movl$0x0,0x0

这行汇编代码就是将0值保存到0地址的位置。那么很显然是非法的。这一行对应的C 代码应该就是:
*(int *)0 = 0;

(5)以上是对模块出错信息的分析。不过也有一定的局限。
    首先就是EIP出错的位置正好在本模块内部,这样可以在本模块定位问题;
    其次,要求一定的汇编基础,特别是当一个函数的代码比较多时,对应的汇编代码也比较大,如何准确定位到C代码行需要一定的经验和时间。

    实际运用中,可以将内核代码行的定位和可动态加载的内核模块代码行的定位结合起来使用,应该可以较快的定位问题。

    分析中有纰漏或者不妥的地方希望大家指出,也希望有网友分享更有效的方法。
出自: http://linux.chinaunix.net/bbs/thread-1097586-1-1.html

你可能感兴趣的:(linux,OOPS)