使用空指针和缓冲区溢出是产生oops的两个最常见原因。
1、直接查看oops信息,首先查找源代码发生oops的位置,通过查看指令寄存器EIP的值,可以找到位置。再查找函数调用栈可以得到更多的信息。从函数调用栈可辨别出局部变量,全局变量和函数参数。较为重要的信息就是指令指针(EIP),即出错指令的地址。
例如:在函数faulty_read的oops信息的函数调用栈中,栈顶为ffffffff,栈顶值应为一个小于ffffffff的值,为此值,说明再找不回调用函数地址,说明有可能因缓冲区溢出等原因造成指针错误。
如果oops信息显示触发oops的地址为0xa5a5a5a5,则说明很可能是因为没有初始化动态内存引起的。
2、使用\prebuilts\gcc\linux-x86\arm\arm-eabi-4.7\bin\arm-eabi-addr2line命令找到地址对应的程序位置,显示对应的程序文件名和行号。
1.arm-eabi-addr2line 将类似libxxx.so 0x00012345的调用栈16进制值翻译成文件名和函数名
arm-eabi-addr2line -e libxxx.so 0x00012345
2.arm-eabi-nm 列出文件的符号信息
arm-eabi-nm -l -C -n -S libdvm.so > dvm.data
3.arm-eabi-objdump 列出文件的详细信息
arm-eabi-objdump -C -d libc.so > libc.s
通过以上工具的分析 ,我们可以得到较完整的调用栈以及调用逻辑的汇编码。
addr2line -e -f libc.so 0001173c
objdump -S -D libc.so > deassmble_libc.txt
打开这个反汇编过后的重定向文件,在查询的时候输入1173c这个偏移地址,你会看到在茫茫人海中
00011684 <pthread_create>:
11684: e92d4ff0 push {r4, r5, r6, r7, r8, r9, sl, fp, lr}
11688: e24dd01c sub sp, sp, #28 ; 0x1c
1168c: e1a06001 mov r6, r1
11690: e1a08002 mov r8, r2
11694: e1a09003 mov r9, r3
11698: e3a04001 mov r4, #1 ; 0x1
1169c: e59f521c ldr r5, [pc, #540] ; 118c0 <pthread_create+0x23c>
116a0: e58d000c str r0, [sp, #12]
116a4: eb009a35 bl 37f80 <strncmp+0x20>
116a8: e59f2214 ldr r2, [pc, #532] ; 118c4 <pthread_create+0x240>
116ac: e1a03000 mov r3, r0
116b0: e1a01004 mov r1, r4
116b4: e593c000 ldr ip, [r3]
116b8: e3a0003c mov r0, #60 ; 0x3c
116bc: e08f3005 add r3, pc, r5
116c0: e7933002 ldr r3, [r3, r2]
116c4: e5834000 str r4, [r3]
116c8: e58dc010 str ip, [sp, #16]
116cc: eb009a3b bl 37fc0 <strncmp+0x60>
...
1173c: ebffec2b bl c7f0 <__pthread_clone>-->就是他了,对你成功了。
...
3、在分析转储映像之前,用户应重启动进入一个稳定的内核。用户可以用GDB对拷贝出的转储进行有限分析。编译vmlinux时应加上-g选项,才能生成调试用的符号,然后,用下面的命令调试vmlinux:
gdb vmlinux <dump-file>
首先 objdump -D vmlinx 反汇编你的内核
然后 你可以通过以下几个寄存器来判断:
1. epc 挂在哪个函数里
2. ra 函数的返回地址,
3. Cause 通过这个寄存器可以分析是什么类型的异常.
4、通过cat /proc/modules获得模块内核链接基地址,用死机地址减去链接基地址得到模块内偏移,再反汇编,找该偏移对应的函数就找到了死机函数。
用objdump -d test.ko > test.asm的话。可以将模块文件反汇编。但是因为模块是目标文件,所以会看到,text段的地址是从0开始的。而该模块在内核在运行的时候,该模块的代码段显然不是在0地址。另外,该代码段中你会看到函数间调用,以及函数内部跳转都是用的b开头的分支指令(仅限于mips和arm的体系结构的讨论),分支指令跳转是以pc为基准值前后跳转的。除非,跳转符号不属于这个模块,则需要32位的跳转。实际上,模块链接到内核的时候,模块的代码段是作为一个整体链接到内核,所以我们只需要知道模块链接到的基地址,再用死机的地址减去这个基地址这就得到了,该地址在模块中代码段的偏移,再通过刚才的反汇编文件就找到了是死在那个函数中。而要得到各个模块的链接地址就很简单了,直接cat /proc/modules就得到了。需要注意的是用__init修饰了的模块初始化函数,是放在单独的段的,通常叫.init.text 。该段会在模块初始化完成后内存就释放掉了
5、google提供了一个python脚本,可以从 http://code.google.com/p/android-ndk-stacktrace-analyzer/ 下载这个python脚本,然后使用
adb logcat -d > logfile 导出 crash 的log,
使用 arm-eabi-objdump (位于build/prebuilt/linux-x86/arm-eabi-4.2.1/bin下面)把so或exe转换成汇编代码,如:
arm-eabi-objdump -S mylib.so > mylib.asm,
然后使用脚本
python parse_stack.py <asm-file> <logcat-file>
如上崩溃信息,可知发生崩溃的函数为rb_init_debugfs,崩溃的地址为0x804386f8
在linux下,到工程的如下目录下:kernel/linux,找到文件vmlinux,执行命令gdb vmlinux:
在gdb命令下执行如下命令即可查找到出错函数所在的文件与行数
0x804386f8
2>如果不确定崩溃的地址是否是0x804386f8,可以在文件System.map中
查找函数rb_init_debugfs获取该函数的地址,然后加上偏移地址(本例中偏移地址为0x14 rb_init_debugfs+0x14/0x70)即可。
直接函数名加偏移量也可以
(gdb) b *rb_init_debugfs+0x14
上面是出错模块是编译进内核的,对于编译进内核的模块可以通过gdb vmlinux来确定出错函数所在的文件与行数。
那如果出错模块是动态加载进内核的该怎么办呢?
这就需要使用objdump进行反汇编操作了,使用如下命令,就会将C语言与汇编语言同时显示(需要加-g命令)
#objdump -S **.o -g
如果使用上面命令,还是只显示汇编,而没有c语言的话,不用担心,在你编译驱动模块的Makefile中,编译成.o文件时,增加-g选项,
对于新生成的.o文件再使用上面的命令就 可以看到汇编与c语言共存了。然后根据kernel panic提示中显示的函数名加上偏移量就能找到出错行了。
1. 为了测试GDB操作,故意在kernel/linux/fs/ioctl.c文件的do_vfs_ioctl方法中加入空指针操作代码,然后编译image烧入单板,启动单板,内核crash,部分log如下:
CPU 0 Unable to handle kernel paging request at virtual address 00000000, epc == 800a73b8, ra == 800a793c
Oops[#1]:
Cpu 0
$ 0 : 00000000 10008d00 00000000 ffffffea
$ 4 : fffffdfd 10008d01 00000001 00000000
$ 8 : 00000000 7fed2e40 00001cb2 00000b3b
$12 : 00031c7f 2ab5ead7 2aaac7c9 15010000
$16 : 7fed2e18 878ca5a0 00000000 00000001
$20 : 2ab84980 00000000 00000007 00000000
$24 : 00000000 2ab62090
$28 : 8782c000 8782fe98 7fed2fc8 800a793c
Hi : 0000002a
Lo : 000311fc
epc : 800a73b8 do_vfs_ioctl+0x88/0x5c8
Not tainted
ra : 800a793c sys_ioctl+0x44/0x98
Status: 10008d03 KERNEL EXL IE
Cause : 00000008
BadVA : 00000000
PrId : 0002a080 (Broadcom4350)
Modules linked in:
Process init (pid: 1, threadinfo=8782c000, task=8782bb68, tls=00000000)
Stack : 878ca1a0 00000004 00000000 10008d00 00000603 2aabcfff 87b0bea8 00000001
87beeaf0 2aabc000 2aabd000 87aa9cb0 2aabd000 2aabd000 8782ff08 fffffff8
00000001 7fed3258 00000007 00000000 878ca5a0 0000540d 00000000 00000001
2ab84980 800a793c 08100871 00000001 87bea41c 0000fff2 00000000 2abbdff0
7fed2e18 00000001 7fed2e60 2abbdff0 00000120 8001ba7c 00000000 00000000
...
Call Trace:(--Raw--
[<800a793c>] sys_ioctl+0x44/0x98
[<8001ba7c>] stack_done+0x20/0x3c
Call Trace:
[<800a73b8>] do_vfs_ioctl+0x88/0x5c8
[<800a793c>] sys_ioctl+0x44/0x98
[<8001ba7c>] stack_done+0x20/0x3c
Code: 0c029c9f 02003021 8fbf0064 <8c020000> 8fb40060 8fb3005c 8fb20058 8fb10054 8fb00050
Disabling lock debugging due to kernel taint
Kernel panic - not syncing: Attempted to kill init!
Rebooting in 1 seconds..<6>
stopping CPU 1
kerSysMipsSoftReset: called on cpu 0
2. 启动GDB, 直接在主机(开发机)控制台运行gdb工具(或./mips-linux-uclibc-gdb),然后敲入 file ...../vmlinux启动带调试信息的内核,注意此时要配置内核debug开关重新编译内核生成文件vmlinux(比之前的文件大10倍左右,50M以上),配置内核debug开关如下,
Kernel hacking --->
[*] Kernel debugging
[*] Compile the kernel with debug info
编译内核命令:make kernelbuild,要在project的根目录下运行该命令进行编译。
启动GDB命令如下:
3. GDB调试定位
Checked call trace log, the most important part in log is “epc” (exception program counter), it is where the crash happened. In this example, the “epc” is 0xc0d1d488. For X1000 and X3500, the address like 0x8XXXXXXXX is in kernel, and the address like 0xcXXXXXXX is in some module.
地址0x800a73b8就是对应于地址(do_vfs_ioctl+0x88),后者表示位于函数do_vfs_ioctl的偏移地址0x88处。
调试过程如下:
(gdb) list *(0x800a73b8)
0x800a73b8 is in do_vfs_ioctl (fs/ioctl.c:569).
564 error = vfs_ioctl(filp, cmd, arg);
565 break;
566 }
567 error = *test;
568 return error;
569 }
570
571 SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
572 {
573 struct file *filp;
(gdb) list*(do_vfs_ioctl+0x88)
0x800a73b8 is in do_vfs_ioctl (fs/ioctl.c:569).
564 error = vfs_ioctl(filp, cmd, arg);
565 break;
566 }
567 error = *test;
568 return error;
569 }
570
571 SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
572 {
573 struct file *filp;
(gdb) list*(sys_ioctl+0x44)
可以分析该函数附近代码发现错误位置在line567,test为空指针(故意在前面赋值NULL)。
注:如果list*(0x80xxxxxx)命令提示如下信息:No source file for address 0x80xxxxxx. 原因是没有在make menuconfig中打开对应的调试开关进行编译。