Linux内核的Oops与Linux Kernel Panic报错解决思路

147 篇文章 1 订阅
50 篇文章 2 订阅

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自己的驱动,这个信息就没什么意义了。


Linux虽然没有蓝屏现象,不过Kernel报错有时也会让人头疼。有时重启后正常,linux系统运行一段时间后又down了,总不能出现问题就reboot啊。我从网上搜集一下资料,整理了出来,希望大家能在评论与我交流您的看法与经验。

什么是Kernel Panic?

wiki:

kernel panic is an action taken by an operating system upon detecting an internal fatal error from which it cannot safely recover. The term is largely specific to Unix and Unix-like systems; for Microsoft Windowsoperating systems the equivalent term is “Bug check” (or, colloquially, “Blue Screen of Death“).

The kernel routines that handle panics (in AT&T-derived and BSD Unix source code, a routine known as panic()) are generally designed to output an error message to the console, dump an image of kernel memory to disk for post-mortemdebugging and then either wait for the system to be manually rebooted, or initiate an automatic reboot. The information provided is of highly technical nature and aims to assist a system administrator or software developer in diagnosing the problem.

Attempts by the operating system to read an invalid or non-permitted memory address are a common source of kernel panics. A panic may also occur as a result of a hardware failure or a bug in the operating system. In many cases, the operating system could continue operation after memory violations have occurred. However, the system is in an unstable state and rather than risking security breaches and data corruption, the operating system stops to prevent further damage and facilitate diagnosis of the error.

The kernel panic was introduced in an early version of Unix and demonstrated a major difference between the design philosophies of Unix and its predecessor Multics. Multics developer Tom van Vleck recalls a discussion of this change with Unix developer Dennis Ritchie:

I remarked to Dennis that easily half the code I was writing in Multics was error recovery code. He said, “We left all that stuff out. If there’s an error, we have this routine called panic, and when it is called, the machine crashes, and you holler down the hall, ‘Hey, reboot it.’”[1]

The original panic() function was essentially unchanged from Fifth Edition UNIX to the VAX-based UNIX 32V and output only an error message with no other information, then dropped the system into an endless idle loop. As the Unixcodebase was enhanced, the panic() function was also enhanced to dump various forms of debugging information to the console.

panic是英文中是惊慌的意思,Linux Kernel panic正如其名,linux kernel不知道如何走了,它会尽可能把它此时能获取的全部信息都打印出来。

有两种主要类型kernel panic:

1.hard panic(也就是Aieee信息输出)
2.soft panic (也就是Oops信息输出)

常见Linux Kernel Panic报错内容:

Kernel panic-not syncing fatal exception in interrupt
kernel panic – not syncing: Attempted to kill the idle task!
kernel panic – not syncing: killing interrupt handler!
Kernel Panic – not syncing:Attempted to kill init !

什么会导致Linux Kernel Panic?

只有加载到内核空间的驱动模块才能直接导致kernel panic,你可以在系统正常的情况下,使用lsmod查看当前系统加载了哪些模块。
除此之外,内建在内核里的组件(比如memory map等)也能导致panic。

因为hard panic和soft panic本质上不同,因此我们分别讨论。

hard panic

一般出现下面的情况,就认为是发生了kernel panic:

  1. 机器彻底被锁定,不能使用
  2. 数字键(Num Lock),大写锁定键(Caps Lock),滚动锁定键(Scroll Lock)不停闪烁。
  3. 如果在终端下,应该可以看到内核dump出来的信息(包括一段”Aieee”信息或者”Oops”信息)
  4. 和Windows蓝屏相似

原因:

对于hard panic而言,最大的可能性是驱动模块的中断处理(interrupt handler)导致的,一般是因为驱动模块在中断处理程序中访问一个空指针(null pointre)。一旦发生这种情况,驱动模块就无法处理新的中断请求,最终导致系统崩溃。

信息收集
根据panic的状态不同,内核将记录所有在系统锁定之前的信息。因为kenrel panic是一种很严重的错误,不能确定系统能记录多少信息,下面是一些需要收集的关键信息,他们非常重要,因此尽可能收集全,当然如果系统启动的时候就kernel panic,那就无法只知道能收集到多少有用的信息了。

  1. /var/log/messages: 幸运的时候,整个kernel panic栈跟踪信息都能记录在这里。
  2. 应用程序/库 日志: 可能可以从这些日志信息里能看到发生panic之前发生了什么。
  3. 其他发生panic之前的信息,或者知道如何重现panic那一刻的状态
  4. 终端屏幕dump信息,一般OS被锁定后,复制,粘贴肯定是没戏了,因此这类信息,你可以需要借助数码相机或者原始的纸笔工具了。

如果kernel dump信息既没有在/var/log/message里,也没有在屏幕上,那么尝试下面的方法来获取(当然是在还没有死机的情况下):

  1. 如果在图形界面,切换到终端界面,dump信息是不会出现在图形界面的,甚至都不会在图形模式下的虚拟终端里。
  2. 确保屏幕不黑屏,可以使用下面的几个方法:
    • setterm -blank 0
    • setterm -powerdown 0
    • setvesablank off
  3. 从终端,拷贝屏幕信息(方法见上)

完整栈跟踪信息的排查方法

栈跟踪信息(stack trace)是排查kernel panic最重要的信息,该信息如果在/var/log/messages日志里当然最好,因为可以看到全部的信息,如果仅仅只是在屏幕上,那么最上面的信息可能因为滚屏消失了,只剩下栈跟踪信息的一部分。如果你有一个完整栈跟踪信息的话,那么就可能根据这些充分的信息来定位panic的根本原因。要确认是否有一个足够的栈跟踪信息,你只要查找包含”EIP”的一行,它显示了是什么函数和模块调用时导致panic。

使用内核调试工具(kenrel debugger ,aka KDB)

如果跟踪信息只有一部分且不足以用来定位问题的根本原因时,kernel debugger(KDB)就需要请出来了。
KDB编译到内核里,panic发生时,他将内核引导到一个shell环境而不是锁定。这样,我们就可以收集一些与panic相关的信息了,这对我们定位问题的根本原因有很大的帮助。

使用KDB需要注意,内核必须是基本核心版本,比如是2.4.18,而不是2.4.18-5这样子的,因为KDB仅对基本核心有效。

soft panic

症状:

  1. 没有hard panic严重
  2. 通常导致段错误(segmentation fault)
  3. 可以看到一个oops信息,/var/log/messages里可以搜索到’Oops’
  4. 机器稍微还能用(但是收集信息后,应该重启系统)

原因:

凡是非中断处理引发的模块崩溃都将导致soft panic。在这种情况下,驱动本身会崩溃,但是还不至于让系统出现致命性失败,因为它没有锁定中断处理例程。导致hard panic的原因同样对soft panic也有用(比如在运行时访问一个空指针)

信息收集:
当soft panic发生时,内核将产生一个包含内核符号(kernel symbols)信息的dump数据,这个将记录在/var/log/messages里。为了开始排查故障,可以使用ksymoops工具来把内核符号信息转成有意义的数据。

为了生成ksymoops文件,需要:

  • 从/var/log/messages里找到的堆栈跟踪文本信息保存为一个新文件。确保删除了时间戳(timestamp),否则ksymoops会失败。
  • 运行ksymoops程序(如果没有,请安装)
  • 详细的ksymoops执行用法,可以参考ksymoops(8)手册。

Kernel panic实例:

今天就遇到 一个客户机器内核报错:“Kernel panic-not syncing fatal exception”

重启后正常,几个小时后出现同样报错,系统down了,有时重启后可恢复有时重启后仍然报同样的错误。

我先来解释一下什么是fatal exception?

“致命异常(fatal exception)表示一种例外情况,这种情况要求导致其发生的程序关闭。通常,异常(exception)可能是任何意想不到的情况(它不仅仅包括程序错误)。致命异常简单地说就是异常不能被妥善处理以至于程序不能继续运行。

软件应用程序通过几个不同的代码层与操作系统及其他应用程序相联系。当异常(exception)在某个代码层发生时,为了查找所有异常处理的代码,各个代码层都会将这个异常发送给下一层,这样就能够处理这种异常。如果在所有层都没有这种异常处理的代码,致命异常(fatal exception)错误信息就会由操作系统显示出来。这个信息可能还包含一些关于该致命异常错误发生位置的秘密信息(比如在程序存储范围中的十六进制的位置)。这些额外的信息对用户而言没有什么价值,但是可以帮助技术支持人员或开发人员调试程序。

当致命异常(fatal exception)发生时,操作系统没有其他的求助方式只能关闭应用程序,并且在有些情况下是关闭操作系统本身。当使用一种特殊的应用程序时,如果反复出现致命异常错误的话,应将这个问题报告给软件供应商。 ”

而且此时键盘无任何反应,必然使用reset键硬重启。

panic.c源文件有个方法,当panic挂起后,指定超时时间,可以重新启动机器

方法:

#vi /etc/sysctl.conf  添加

kernel.panic = 20 #panic error中自动重启,等待timeout为20秒
kernel.sysrq=1 #激活Magic SysRq  否则,键盘鼠标没有响应

按住 [ALT]+[SysRq]+[COMMAND], 这里SysRq是Print SCR键,而COMMAND按以下来解释!

b – 立即重启
e – 发送SIGTERM给init之外的系统进程
o – 关机
s – sync同步所有的文件系统
u – 试图重新挂载文件系统

配置一下以防万一。

很多网友安装linux出现“Kernel panic-not syncing fatal exception in interrupt”是由于网卡驱动原因。

解决方法:将选项“Onboard Lan”的选项“Disabled”,重启从光驱启动即可。

等安装完系统之后,再进入BIOS将“Onboard Lan”的选项给“enable”,下载相应的网卡驱动安装。

如出现以下报错:

init() r8168 … 

          … …

         … :Kernel panic: Fatal exception

r8168是网卡型号。

在BIOS中禁用网卡,从光驱启动安装系统。再从网上下载网卡驱动安装。

#tar  vjxf  r8168-8.014.00.tar.bz2

# make  clean  modules       (as root or with sudo)

      # make  install

      # depmod  -a

      # modprobe  r8168

安装好系统后reboot进入BIOS把网卡打开。

另有网友在Kernel panic出错信息中看到“alc880”,这是个声卡类型。尝试着将声卡关闭,重启系统,搞定。

安装linux系统遇到安装完成之后,无法启动系统出现Kernel panic-not syncing fatal exception。很多情况是由于板载声卡、网卡、或是cpu 超线程功能(Hyper-Threading )引起的。这类问题的解决办法就是先查看错误代码中的信息,找到错误所指向的硬件,将其禁用。系统启动后,安装好相应的驱动,再启用该硬件即可。
另外出现“Kernel Panic — not syncing: attempted to kill init”和“Kernel Panic — not syncing: attempted to kill idle task”有时把内存互相换下位置或重新插拔下可以解决问题。

Oops 信息来源及格式
Oops 这个单词含义为“惊讶”
,当内核出错时(比如访问非法地址)打印出来的信息被
称为 Oops 信息。
Oops 信息包含以下几部分内容。
1 一段文本描述信息。
比如类似“Unable to handle kernel NULL pointer dereference at virtual address 00000000”
的信息,它说明了发生的是哪类错误。
2 Oops 信息的序号。
比如是第 1 次、第 2 次等。这些信息与下面类似,中括号内的数据表示序号。
Internal error: Oops: 805 [#1]
3 内核中加载的模块名称,也可能没有,以下面字样开头。
Modules linked in:
4 发生错误的 CPU 的序号,对于单处理器的系统,序号为 0,比如:
CPU: 0
Not tainted (2.6.22.6 #36)
5 发生错误时 CPU 的各个寄存器值。
6 当前进程的名字及进程 ID,比如:
Process swapper (pid: 1, stack limit = 0xc0480258)
这并不是说发生错误的是这个进程,而是表示发生错误时,当前进程是它。错误可能发
生在内核代码、驱动程序,也可能就是这个进程的错误。
7 栈信息。
8 栈回溯信息,可以从中看出函数调用关系,形式如下:
Backtrace:
[<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_
probe+0x20/0x24)
...
9 出错指令附近的指令的机器码,比如(出错指令在小括号里)
:
Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)

配置内核使 Oops 信息的栈回溯信息更直观
Linux 2.6.22 自身具备的调试功能,可以使得打印出的 Oops 信息更直观。通过 Oops 信
息中 PC 寄存器的值可以知道出错指令的地址,通过栈回溯信息可以知道出错时的函数调用
关系,根据这两点可以很快定位错误。
要让内核出错时能够打印栈回溯信息,编译内核时要增加“-fno-omit-frame-pointer”选
项,这可以通过配置 CONFIG_FRAME_POINTER 来实现。查看内核目录下的配置文件.config,
确保 CONFIG_FRAME_POINTER 已经被定义,如果没有,执行“make menuconfig”命令重
新配置内核。CONFIG_FRAME_POINTER 有可能被其他配置项自动选上。
18.3.3
使用 Oops 信息调试内核的实例
1.获得 Oops 信息
本小节故意修改 LCD 驱动程序 drivers/video/s3c2410fb.c,加入错误代码:在 s3c2410fb_
probe 函数的开头增加下面两条代码:
int *ptest = NULL;
*ptest = 0x1234;
重新编译内核,启动后会出错并打印出如下 Oops 信息:
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = c0004000
[00000000] *pgd=00000000
Internal error: Oops: 805 [#1]
Modules linked in:
CPU: 0
Not tainted (2.6.22.6 #36)
PC is at s3c2410fb_probe+0x18/0x560
LR is at platform_drv_probe+0x20/0x24
pc : [<c001a70c>]
lr : [<c01bf4e8>]
psr: a0000013
sp : c0481e64 ip : c0481ea0 fp : c0481e9c
r10: 00000000 r9 : c0024864 r8 : c03c420c
r7 : 00000000 r6 : c0389a3c r5 : 00000000 r4 : c036256c
r3 : 00001234 r2 : 00000001 r1 : c04c0fc4 r0 : c0362564
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment kernel
Control: c000717f Table: 30004000 DAC: 00000017
Process swapper (pid: 1, stack limit = 0xc0480258)
Stack: (0xc0481e64 to 0xc0482000)
1e60:c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c
1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a704
1ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8 c0362644 c036256c c01bd708 c0389a3c
1ec0: 00000000 c0481ee8 c0481ed4 c01bd788 c01bd4d0 00000000 c0481eec c0481f14
1ee0: c0481eec c01bc5a8 c01bd718 c038dac8 c038dac8 c03625b4 00000000 c0389a3c
1f00: c0389a44 c038d9dc c0481f24 c0481f18 c01bd808 c01bc568 c0481f4c c0481f28
1f20: c01bcd78 c01bd7f8 c0389a3c 00000000 00000000 c0480000 c0023ac8 00000000
1f40: c0481f60 c0481f50 c01bdc84 c01bcd0c 00000000 c0481f70 c0481f64 c01bf5fc
1f60: c01bdc14 c0481f80 c0481f74 c019479c c01bf5a0 c0481ff4 c0481f84 c0008c14
1f80: c0194798 e3c338ff e0222423 00000000 00000001 e2844004 00000000 00000000
1fa0: 00000000 c0481fb0 c002bf24 c0041328 00000000 00000000 c0008b40 c00476ec
1fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
1fe0: 00000000 00000000 00000000 c0481ff8 c00476ec c0008b50 c03cdf50 c0344178
Backtrace:
[<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_
probe+0x20/0x24)
[<c01bf4c8>] (platform_drv_probe+0x0/0x24) from [<c01bd5a8>] (driver_probe_
device+0xe8/0x18c)
[<c01bd4c0>] (driver_probe_device+0x0/0x18c) from [<c01bd788>] (__driver_
attach+0x80/0xe0)
r8:00000000 r7:c0389a3c r6:c01bd708 r5:c036256c r4:c0362644
[<c01bd708>] (_ _driver_attach+0x0/0xe0) from [<c01bc5a8>] (bus_for_each_
dev+0x50/0x84)
r5:c0481eec r4:00000000
[<c01bc558>] (bus_for_each_dev+0x0/0x84) from [<c01bd808>] (driver_attach+
0x20/0x28)
r7:c038d9dc r6:c0389a44 r5:c0389a3c r4:00000000
[<c01bd7e8>] (driver_attach+0x0/0x28) from [<c01bcd78>] (bus_add_driver+
0x7c/0x1b4)
[<c01bccfc>] (bus_add_driver+0x0/0x1b4) from [<c01bdc84>] (driver_register+
0x80/0x88)
[<c01bdc04>] (driver_register+0x0/0x88) from [<c01bf5fc>] (platform_driver_
register+0x6c/0x88)
r4:00000000
[<c01bf590>] (platform_driver_register+0x0/0x88) from [<c019479c>] (s3c2410fb_
init+0x14/0x1c)
[<c0194788>] (s3c2410fb_init+0x0/0x1c) from [<c0008c14>] (kernel_init+0xd4/
0x28c)
[<c0008b40>] (kernel_init+0x0/0x28c) from [<c00476ec>] (do_exit+0x0/0x760)
Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)
Kernel panic - not syncing: Attempted to kill init!
分析 Oops 信息
(1)明确出错原因。
由出错信息“Unable to handle kernel NULL pointer dereference at virtual address 00000000”
可知内核是因为非法地址访问出错,使用了空指针。
(2)根据栈回溯信息找出函数调用关系。
内核崩溃时,可以从 pc 寄存器得知崩溃发生时的函数、出错指令。但是很多情况下,错
误有可能是它的调用者引入的,所以找出函数的调用关系也很重要。
部分栈回溯信息如下:
[<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_
probe+0x20/0x24)
这行信息分为两部分,
表示后面的 platform_drv_probe 函数调用了前面的 s3c2410fb_probe
函数。
前半部含义为:
“c001a6f4”是 s3c2410fb_probe 函数首地址偏移 0 的地址,这个函数大
小为 0x560。
后半部含义为:
“c01bf4e8”是 platform_drv_probe 函数首地址偏移 0x20 的地址,这个函
数大小为 0x24。
另外,后半部的“[<c01bf4e8>]”表示 s3c2410fb_probe 执行后的返回地址。
对于类似下面的栈回溯信息,其中是 r8~r4 表示 driver_probe_device 函数刚被调用时这
些寄存器的值。
[<c01bd4c0>] (driver_probe_device+0x0/0x18c) from [<c01bd788>] (__driver_
attach+0x80/0xe0)
r8:00000000 r7:c0389a3c r6:c01bd708 r5:c036256c r4:c0362644
从上面的栈回溯信息可以知道内核出错时的函数调用关系如下,
最后在 s3c2410fb_probe
函数内部崩溃。
do_exit ->
kernel_init ->
s3c2410fb_init ->
platform_driver_register ->
driver_register ->
bus_add_driver ->
driver_attach ->
bus_for_each_dev ->
__driver_attach ->
driver_probe_device ->
platform_drv_probe ->
s3c2410fb_probe
(3)根据 pc 寄存器的值确定出错位置。
上述 Oops 信息中出错时的寄存器值如下:
PC is at s3c2410fb_probe+0x18/0x560
LR is at platform_drv_probe+0x20/0x24
pc : [<c001a70c>]
lr : [<c01bf4e8>]
psr: a0000013
...
“PC is at s3c2410fb_probe+0x18/0x560”表示出错指令为 s3c2410fb_probe 函数中偏移为
0x18 的指令。
“pc : [<c001a70c>]”表示出错指令的地址为 c001a70c(十六进制)。
(4)结合内核源代码和反汇编代码定位问题。
先生成内核的反汇编代码 vmlinux.dis,执行以下命令:
$ cd /work/system/linux-2.6.22.6
$ arm-linux-objdump -D vmlinux > vmlinux.dis
出错地址 c001a70c 附近的部分汇编代码如下:
c001a6f4 <s3c2410fb_probe>:
c001a6f4: e1a0c00d mov ip, sp
c001a6f8: e92ddff0 stmdb
c001a6fc: e24cb004 sub fp, ip, #4 ; 0x4
c001a700: e24dd010 sub sp, sp, #16 ; 0x10
c001a704: e59f34e0 ldr r3, [pc, #1248] ; c001abec <.init+0x1284c>
c001a708: e3a07000 mov r7, #0
c001a70c: e5873000 str r3, [r7]
c001a710: e59030fc ldr r3, [r0, #252]
sp!, {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
; 0x0
<===========出错指令
出错指令为“str r3, [r7]”
,它把 r3 寄存器的值放到内存中,内存地址为 r7 寄存器的值。
根据 Oops 信息中的寄存器值可知:r3 为 0x00001234,r7 为 0。0 地址不可访问,所以出错。
s3c2410fb_probe 函数的部分 C 代码如下:
static int __init s3c2410fb_probe(struct platform_device *pdev)
{
struct s3c2410fb_info *info;
struct fb_info
*fbinfo;
struct s3c2410fb_hw *mregs;
int ret;
int irq;
int i;
u32 lcdcon1;
int *ptest = NULL;
*ptest = 0x1234;
mach_info = pdev->dev.platform_data;
结合反汇编代码,很容易知道是“*ptest = 0x1234;”导致错误,其中的 ptest 为空。
对于大多数情况,从反汇编代码定位到 C 代码并不会如此容易,这需要较强的阅读汇编
程序的能力。通过栈回溯信息知道函数的调用关系,这已经可以帮助定位很多问题了。

使用 Oops 的栈信息手工进行栈回溯
前面说过,从 Oops 信息的 pc 寄存器值可知得知崩溃发生时的函数、出错指令。但是错
误有可能是它的调用者引入的,所以还要找出函数的调用关系。
由于内核配置了 CONFIG_FRAME_POINTER,当出现 Oops 信息时,会打印栈回溯信息。如
果内核没有配置 CONFIG_FRAME_POINTER,这时可以自己分析栈信息,找到函数的调用关系。
1.栈的作用
一个程序包含代码段、数据段、BSS 段、堆、栈;其中数据段用来中存储初始值不为 0
的全局数据,BSS 段用来存储初始值为 0 的全局数据,堆用于动态内存分配,栈用于实现函
数调用、存储局部变量。
被调用函数在执行之前,它会将一些寄存器的值保存在栈中,其中包括返回地址寄存器
lr。如果知道了所保存的 lr 寄存的值,那么就可以知道它的调用者是谁。在栈信息中,一个
函数一个函数地往上找出所有保存的 lr 值,
就可以知道各个调用函数,
这就是栈回溯的原理。
2.栈回溯实例分析
仍以前面的 LCD 驱动程序为例,
使用上面的 Oops 信息的栈信息进行分析,
栈信息如下:
Stack: (0xc0481e64 to 0xc0482000)
1e60:c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c
1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a704
1ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8 c0362644 c036256c c01bd708 c0389a3c
1ec0: 00000000 c0481ee8 c0481ed4 c01bd788 c01bd4d0 00000000 c0481eec c0481f14
1ee0: c0481eec c01bc5a8 c01bd718 c038dac8 c038dac8 c03625b4 00000000 c0389a3c
...
1 根据 pc 寄存器值找到第一个函数,确定它的栈大小,确定调用函数。
从 Oops 信息可知 pc 值为 c001a70c,
使用它在内核反汇编程序 vmlinux.dis 中可以知道它
位于 s3c2410fb_probe 函数内。
根据这个函数开始部分的汇编代码可以知道栈的大小、lr 返回值在栈中保存的位置,代
码如下:
c001a6f4 <s3c2410fb_probe>:
c001a6f4:
e1a0c00d
mov ip, sp
c001a6f8: e92ddff0 stmdb
c001a6fc: e24cb004 sub fp, ip, #4 ; 0x4
sp!, {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
c001a700: e24dd010 sub sp, sp, #16 ; 0x10
e5873000 str r3, [r7]
...
c001a70c:
// pc 值 c001a70c 对应的指令
...
{r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}这 11 个寄存器都保存在栈中,指令“sub sp, sp, #16”
又使得栈向下扩展了 16 字节,所以本函数的栈大小为(11 × 4+16)字节,即 15 个双字。
栈信息开始部分的 15 个数据就是本函数的栈内容,下面列出了它们所保存的寄存器。
1e60:
c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c
r4
r5
r6
1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a704
r7
r8
r9
sl
fp
ip
lr
pc
其中 lr 值为 c01bf4e8,表示函数 s3c2410fb_probe 执行完后的返回地址,它是调用函数
中的地址。下面使用 lr 值再次重复本步骤的回溯过程。
2 根据 lr 寄存器值找到调用函数,确定它的栈大小,确定上一级调用函数。
根据上步得到的 lr 值(c01bf4e8)在内核反汇编程序 vmlinux.dis 中可以知道它位于
platform_drv_probe 函数内。
根据这个函数开始部分的反汇编代码可以知道栈的大小、lr 返回值在栈中保存的位置。
代码如下:
c01bf4c8 <platform_drv_probe>:
c01bf4c8: e1a0c00d mov ip, sp
c01bf4cc: e92dd800 stmdb sp!, {fp, ip, lr, pc}
e89da800 ldmia sp, {fp, sp, pc}
...
c01bf4e8:
// lr 值(c01bf4e8)对应的指令
{fp, ip, lr, pc}这 4 寄存器都保存在栈中,本函数的栈大小为 4 个双字。Oops 栈信息中,
前一个函数 s3c2410fb_probe 的栈下面的 4 个数据就是函数 platform_drv_probe 的栈内容,如
下所示:
1ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8
fp
ip
lr
pc
其中 lr 值为 c01bd5a8,表示函数 platform_drv_probe 执行完后的返回地址,它是上一级
调用函数中的地址。使用 lr 值,重复本步骤的查找过程,直到栈信息分析完毕或者再也无法
分析,这样就可以找出所有的函数调用关系。
有些函数很简单,没有使用栈(sp 值在这个函数中没有变化)
,或者没有在栈中保存 lr
值。这些情况需要读者灵活处理,较强的汇编程序阅读能力是关键。

linux内核调试工具: oops && ksymoops && objdump

利用coredump定位出错的代码行

http://www.linuxforu.com/2011/01/understanding-a-kernel-oops/

http://www.kerneloops.org/


linux内核打印"BUG: scheduling while atomic"和"bad: scheduling from the idle thread"错误的时候,通常是在中断处理函数(包括定时器中断处理函数)中调用了导致休眠的函数,如sleep,kmalloc,ioremap,semaphore,mutex之类的函数,而 linux内核要求在中断处理的时候,不允许抢占,不允许休眠,要等到中断处理完成才能做其他事情


  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值