分类: 嵌入式
2011-07-23 01:04:05
如何定位错误对分析解问题十分重要,下面这个例子讲如何在内核中定位panic/oops的位置:
-----------------------------------------------------------------------------------------------------------------------
Dec 20 02:40:58 AS-4 kernel: Unable to handle kernel NULL pointer dereference at virtual address 0000000c
Dec 20 02:40:58 AS-4 kernel: printing eip:
Dec 20 02:40:58 AS-4 kernel: f890c2c0
Dec 20 02:40:58 AS-4 kernel: *pde = 00000000
Dec 20 02:40:58 AS-4 kernel: Oops: 0002
Dec 20 02:40:58 AS-4 kernel: CPU: 3
Dec 20 02:40:58 AS-4 kernel: EIP: 0010:[
Dec 20 02:40:58 AS-4 kernel: EFLAGS: 00010282
Dec 20 02:40:58 AS-4 kernel: eax: 00000000 ebx: ef27a5e8 ecx: 00000000 edx: c3036000
Dec 20 02:40:58 AS-4 kernel: esi: f5f31054 edi: f6109800 ebp: f5f31054 esp: c3037de4
Dec 20 02:40:58 AS-4 kernel: ds: 0018 es: 0018 ss: 0018
Dec 20 02:40:58 AS-4 kernel: Process swapper (pid: 0, stackpage=c3037000)
Dec 20 02:40:58 AS-4 kernel: Stack: f89269f2 f6109800 00000000 f5f31054 f5f31054 f6109800 f8926525 f6109800
Dec 20 02:40:58 AS-4 kernel: ef27a5e8 f5f31054 f5f31054 00000001 f8925c42 f5f31054 f6109800 f8925b42
Dec 20 02:40:58 AS-4 kernel: f5f31054 00000001 ef27a5e8 f89263ea f5f31054 f5f31054 f5f31054 f6f2c03c
Dec 20 02:40:58 AS-4 kernel: Call Trace: [
Dec 20 02:40:58 AS-4 kernel: [
Dec 20 02:40:58 AS-4 kernel: [
Dec 20 02:40:58 AS-4 kernel: [
Dec 20 02:40:58 AS-4 kernel:
Dec 20 02:40:58 AS-4 kernel: Code: f0 ff 40 0c c3 8d 76 00 53 8b 54 24 0c f0 ff 4a 0c 0f 94 c0
Dec 20 02:40:58 AS-4 kernel: <0>Kernel panic: Aiee, killing interrupt handler!
-----------------------------------------------------------------------------------------------------------------------
找到原模块文件,比如hello.o
insmod -m hello.o > ~/kernel-module.symbol
在输出的文件中我们可以知道hello.o被插入到kernel时的符号表地址。
根据eip我们就可以知道kernel oops发生的大体位置,即是在哪一个函数中出现错误的。
然后根据eip的地址去反汇编代码中寻找错误发生函数的具体位置,这里就是f890c2c0。
Eip减去kernel-module.symbol中的函数起始地址,得到错误发生的偏移量。根据这个偏移量,我们就可以在objdump得到的反汇编代码中查到函数出错时的具体位置。
注:objdump -D *.o得到反汇编代码。 objdump -S *.o得到含有c源码的汇编,这要求之前的编译包含了debug信息(-g)。
如果还不能精确定位函数错误的具体位置。还有一种方法:从panic/oops的信息中我们看到有Dec 20
02:40:58 AS-4 kernel: Code: f0 ff 40 0c c3 8d 76 00 53 8b 54 24 0c f0 ff
4a 0c 0f 94 c0
这是出错地方对应的二进制代码。它们可以帮助我们定位到错误发生处的汇编语言。
Kernel oops后内核是否需要重启,取决于我们的参数配置。
# sysctl -a|grep oops
kernel.panic_on_oops = 1
panic_on_oops的缺省设置是"0",即在Oops发生时不会进行panic()操作,即oops时不panic,系统仍然继续运行。
这样的一个Oops:
Oops: 0000 [#1] PREEMPT SMP
Modules linked in: capidrv kernelcapi isdn slhc ipv6
loop dm_multipath snd_ens1371 gameport snd_rawmidi snd_ac97_codec
ac97_bus snd_seq_dummy snd_seq_oss snd_seq_midi_event snd_seq
snd_seq_device snd_pcm_oss snd_mixer_oss snd_pcm snd_timer snd
parport_pc floppy parport pcnet32 soundcore mii pcspkr snd_page_alloc ac
i2c_piix4 i2c_core button power_supply sr_mod sg cdrom ata_piix libata
dm_snapshot dm_zero dm_mirror dm_mod BusLogic sd_mod scsi_mod ext3 jbd
mbcache uhci_hcd ohci_hcd ehci_hcd
Pid: 1726, comm: kstopmachine Not tainted (2.6.24-rc3-module #2)
EIP: 0060:[
EIP is at list_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
Process kstopmachine (pid: 1726, ti=dec62000 task=df8d2d40 task.ti=dec62000)
Stack: 000006bf dec62fb4 c04276c7 00000020 dec62fbc c044ab4c dec62fd0 c045336c
df6e8f08 c04532b4 00000000 dec62fe0 c043deb0 c043de75 00000000 00000000
c0405cdf df6e8eb4 00000000 00000000 00000000 00000000 00000000
Call Trace:
[
[
[
[
[
[
[
[
[
[
=======================
Code: 6b c0 e8 2e 7e f6 ff e8 d1 16 f2 ff b8 01 00 00
00 e8 aa 1c f4 ff 89 d8 83 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 44 24 04 c7 04 24 be
32 6b c0
EIP: [
note: kstopmachine[1726] exited with preempt_count 1
1, 有自己编译的vmlinux: 使用gdb
编译时打开complie with debug info选项。
注意这行:
EIP is at list_del+0xa/0x61
这告诉我们,list_del函数有0x61这么大,而Oops发生在0xa处。 那么我们先看一下list_del从哪里开始:
# grep list_del /boot/System.map-2.6.24-rc3-module
c10e5234 T plist_del
c10e53cc T list_del
c120feb6 T klist_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
Breakpoint 1 at 0xc10e53d6: file /usr/src/linux-2.6.24-rc3/lib/list_debug.c, line 64.
看,gdb直接就告诉你在哪个文件、哪一行了。
gdb中还可以这样:
# gdb Sources/linux-2.6.24/vmlinux
(gdb) l *do_fork+0x1f
0xc102b7ac is in do_fork (kernel/fork.c:1385).
1380
1381 static int fork_traceflag(unsigned clone_flags)
1382 {
1383 if (clone_flags & CLONE_UNTRACED)
1384 return 0;
1385 else if (clone_flags & CLONE_VFORK) {
1386 if (current->ptrace & PT_TRACE_VFORK)
1387 return PTRACE_EVENT_VFORK;
1388 } else if ((clone_flags & CSIGNAL) != SIGCHLD) {
1389 if (current->ptrace & PT_TRACE_CLONE)
(gdb)
也可以直接知道line number。
或者:
(gdb) l *(0xffffffff8023eaf0 + 0xff) /* 出错函数的地址加上偏移 */
2, 没有自己编译的vmlinux: TIPS
如果在lkml或bugzilla上看到一个Oops,而自己不能重现,那就只能反汇编以"Code:"开始的行。 这样可以尝试定位到
源代码中。
注意,Oops中的Code:行,会把导致Oops的第一条指令,也就是EIP的值的第一个字节, 用尖括号<>括起来。 但是,有些
体系结构(例如常见的x86)指令是不等长的(不一样的指令可能有不一样的长度),所以要不断的尝试(trial-and-error)。
Linus通常使用一个小程序,类似这样:
const char array[] = "\xnn\xnn\xnn...";
int main(int argc, char *argv[])
{
printf("%p\n", array);
*(int *)0 = 0;
}
e.g. /*{{{*/ /* 注意, array一共有从array[0]到array[64]这65个元素, 其中出错的那个操作码<8b>
== arry[43] */
#include
#include
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 gdb Fedora (6.8-1.fc9)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <[url][/url]>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu"...
(no debugging symbols found)
(gdb) r
Starting program: /home/arc/hello
0x80484e0
Program received signal SIGSEGV, Segmentation fault.
注意,这时候就可以反汇编0x80484e0这个地址:
(gdb) disassemble 0x80484e0
Dump of assembler code for function array:
0x080484e0
0x080484e3
0x080484e6
0x080484e8
0x080484ea
0x080484ec
0x080484f1
0x080484f6
0x080484f8
0x080484fb
0x080484fc
0x080484fd
0x080484fe
0x080484ff
0x08048500
0x08048501
0x08048502
0x08048504
0x08048505
0x08048508
0x0804850b
0x0804850d
0x0804850f
0x08048511
0x08048515
0x08048519
0x08048520
End of assembler dump.
(gdb)
OK, 现在你知道出错的那条指令是array[43],也就是mov
(%ecx),%edx,也就是说,(%ecx)指向了一个错误内存地址。
补充:
为了使汇编代码和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
另外, 内核源代码目录的./scripts/decodecode文件是用来解码Oops的:
./scripts/decodecode < Oops.txt
|
(2)编译该模块,并且mknod /dev/faulty
(3)向该模块写入数据:echo 1 > /dev/faulty, 内核OOPS,信息如下:
|
|
|