LINUX内核调试相关--oops信息的定位2 收藏
Ø
1 、掌握printk 的使用、设置及实现原理,理解分级别进行打印log 信息的实现方法
2 、掌握如何分析oops 的方法
3 、掌握strace 工具的移植和使用方法
Ø
1 、请回顾栈的工作原理,尤其是栈帧的作用
2 、请对照printk 的源代码来进行printk 相关实验,并在实验中进一步理解源代码
Ø
一. Printk 实验 。
1 、在内核中编写自己的printk 代码,可利用上次系统调用实验中已有的代码,也可利用之前驱动实验中的模块。
2 、在根文件系统中增加/proc 目录,用来挂载proc 文件系统
3 、重新烧录uImage (如果有所改动的话)和根文件系统,进入控制台之后,输入命令挂载proc 文件系统:mount – t proc none /proc 。
如果挂载成功, /proc 目录应该可以看到文件,比如下面的结果:
# ls proc
1
100
101
102
103
2
3
4
5
6
60
65
71
737
4 、检查并修改printk 的log 级别,比如下面的命令:
# cat /proc/sys/kernel/printk
7
# echo 1 >/proc/sys/kernel/printk
# cat /proc/sys/kernel/printk
1
修改之后,默认的printk 打印(级别为4 )不会显示到串口终端,但仍可以通过“ cat /proc/kmsg ”看到打印结果。
5 、通过代码和 /proc/sys/kernel/printk 分别修改log 级别,并对应printk 的源代码来分析结果。
二.C 语言可变参数实验
1 、在内核代码kernel/printk.c 中的printk 函数用到了C 语言中的可变参数的用法。请参考下面的代码来学习如何使用可变参数。以下代码可直接在x86 环境测试:
#include <stdio.h>
typedef char *va_list;
#define
#define
#define _bnd(X, bnd)
#define va_arg(ap, T)
#define va_end(ap)
#define va_start(ap, A)
int max ( int num, ... )
{
}
int main ( int argc, char* argv[] )
{
}
2 、自己动手 分析上面代码可变参数的用法及实现方式。提示:va_start ( ap, num ) 是为了取得可变参数在栈中的位置,该宏展开执行后,ap 将指向第一个可变参数。可利用GDB 和汇编代码协助分析。
3 、ARM 架构中通常使用寄存器而不是栈来传递参数,那么,上述可变参数的方式能够用于ARM 架构中吗?请想办法找到证据 来支持你的猜测。
三. Oops 实验 。
1 、在上次系统调用实验的代码中,人为的制造产生oops 的条件,比如下面的改动:
asmlinkage long sys_mytest()
{
printk("pid: %d:\tThis is my call.\n",current->pid);
*(int *)0 = 0;
}
2 、从串口终端得到所产生的oops 消息,并进行初步分析
3 、在内核代码中,利用arm-linux-objdump 得到kernel/sys.o 的汇编代码,对照汇编代码进行分析
4 、arm-linux-addr2line 工具可用来寻找代码地址所对应的c 代码(),可尝试:
arm-linux-addr2line – e sys.o 0xnnnn( 你出错的代码地址)
(1) 在kernel/sys.c 文件里
#arm-linux-gcc – c sys.o sys.c( 生成汇编文件)
#vim sys.o
寻找:sys_mytest
找到地址是:000000b0
而通过下面Oops 信息知道:PC is at sys_mytest+0x28/0x34
则0xnnnn( 代码出错的地址)=000000b0+0x28=0xd8
(2) # arm-linux-addr2line – e sys.o 0xd8
结果输出是137 行
则查出出错的地方在sys.c 中的137 行。
5 、下面是我实验中遇到的oops ,请尝试做一些初步分析:
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = c3eb0000
[00000000] *pgd=33ddd031, *pte=00000000, *ppte=00000000
Internal error: Oops: 817 [#1]
Modules linked in:
CPU: 0
PC is at sys_mytest+0x28/0x34 (表示在sys_mytest+0x28 至sys_mytest+0x34 之间,以4 个字节为一个单位)
LR is at 0xc031781c
pc : [<c00561ec>]
sp : c3ec3f98
r10: 00008528
r7 : 00000161
r3 : 80000013
Flags: nZCv
Control: 0000717f
Process syscall_test (pid: 777, stack limit = 0xc3ec2260)
Stack: (0xc3ec3f98 to 0xc3ec4000)
3f80:
3fa0: c002c9e0 c00561d4 00000000 beeced1c 00000161 beecef14 beecef1c 0000000a
3fc0: 0000000a 00000000 beeced1c 00000001 beecef14 000081c4 00008528 beeced18
3fe0: beeced08 beececfc 000081e8 0000f8e0 60000010 00000161 7bf2fafd eff7fbbd
Backtrace:
[<c00561c4>] (sys_mytest+0x0/0x34) from [<c002c9e0>] (ret_fast_syscall+0x0/0x2c)
Code: e59f0010 e59310d8 ebffcf47 e3a00000 (e5800000)
---[ end trace e5388d99d2481600 ]---
四. Strace 实验 。
1 、从www.sourceforge.net 上下载strace 的源代码
2 、配置并编译strace :
./configure --host=arm-linux
make
3 .将strace 工具加入到你的根文件系统中,并测试使用它
(
来自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]
)
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[])
{
}
补充:
为了使汇编代码和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
本文欢迎自由转载,但请标明出处,并保证本文的完整性。
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)由于作者提供的函数代码就一样,过于简单,我这里简单加上一些代码(也就是判断和赋值),如下:
|
(2)编译该模块,并且mknod /dev/faulty
(3)向该模块写入数据:echo 1 > /dev/faulty, 内核OOPS,信息如下:
|
同时,faulty_write+0xe/0x19的后半部分0xe/0x19,说明该函数的大小时0x019,出错位置是在0x0e。这两个值应该值得都是汇编代码的值。
(4)将faulty模块反汇编出汇编代码:
然后,我们打开faulty.s文件。由于我们需要关注的部分正好在文件的前面,因此我这里只贴出文件的前面一部分内容:
|
|
*(int *)0 = 0;
(5)以上是对模块出错信息的分析。不过也有一定的局限。
出自: http://linux.chinaunix.net/bbs/thread-1097586-1-1.html