? 实验目的与意义
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 74 devices kpagecount slabinfo
100 765 diskstats kpageflags stat
101 773 driver loadavg swaps
102 783 execdomains locks sys
103 784 fb meminfo sysrq-trigger
2 786 filesystems misc sysvipc
3 790 fs modules timer_list
4 804 ide mounts tty
5 806 interrupts mtd uptime
6 buddyinfo iomem net version
60 bus ioports pagetypeinfo vmstat
65 cmdline irq partitions yaffs
71 cpu kallsyms sched_debug zoneinfo
737 cpuinfo kmsg self
4 、检查并修改printk 的log 级别,比如下面的命令:
# cat /proc/sys/kernel/printk7 4 1 7# echo 1 >/proc/sys/kernel/printk# cat /proc/sys/kernel/printk1 4 1 7
修改之后,默认的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;
/*
* * Storage alignment properties
* */
#define _AUPBND (sizeof (signed int) – 1)
#define _ADNBND (sizeof (signed int) – 1)
/*
* * Variable argument list macro definitions
* */
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) – (_bnd (T,_ADNBND))))
#define va_end(ap) (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
int max ( int num, … )
{
int m = -0x7FFFFFFF;
int i;
va_list ap;
va_start ( ap, num );
for ( i= 0; i< num; i++ )
{
int t = va_arg (ap, int);
if ( t > m )
{
m = t;
}
}
va_end (ap);
return m;
}
int main ( int argc, char* argv[] )
{
int n = max ( 5, 2, 6 ,3 ,8 ,1);
printf("n=%d./n", n);
return 0;
}
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;
return 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 Not tainted (2.6.25.8 #7)
PC is at sys_mytest+0x28/0x34 (表示在sys_mytest+0x28 至sys_mytest+0x34 之间,以4 个字节为一个单位)
LR is at 0xc031781c
pc : [<c00561ec>] lr : [<c031781c>] psr: 60000013
sp : c3ec3f98 ip : c031781c fp : c3ec3fa4
r10: 00008528 r9 : c3ec2000 r8 : c002cb84
r7 : 00000161 r6 : beeced1c r5 : 00000000 r4 : 0000000a
r3 : 80000013 r2 : 00000001 r1 : 00000001 r0 : 00000000
Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
Control: 0000717f Table: 33eb0000 DAC: 00000015
Process syscall_test (pid: 777, stack limit = 0xc3ec2260)
Stack: (0xc3ec3f98 to 0xc3ec4000)
3f80: 00000000 c3ec3fa8
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 工具加入到你的根文件系统中,并测试使用它
ssize_t faulty_write ( struct file * filp, const char __user * buf, size_t count , loff_t * pos) { /* make a simple fault by dereferencing a NULL pointer */ if ( count > 0x100) count = 0x100; * ( int * ) 0 = 0; return count ; }
(2)编译该模块,并且mknod /dev/faulty(3)向该模块写入数据:echo 1 > /dev/faulty, 内核OOPS,信息如下:
< 1> BUG: unable to handle kernel NULL pointer dereference at virtual address 00000000printing eip: f8e8000e* pde = 00000000Oops: 0002 [ # 3] SMPModules linked in: faulty autofs4 hidp rfcomm l2cap . . . . . . //此处省略若干字符CPU: 1EIP: 0060: [ < f8e8000e> ] Not tainted VLIEFLAGS: 00010283 ( 2. 6. 18. 3 # 2) EIP is at faulty_write+ 0xe/ 0x19 [ faulty] eax: 00000001 ebx: f4f6ca40 ecx: 00000001 edx: b7c2d000esi: f8e80000 edi: b7c2d000 ebp: 00000001 esp: f4dc5f84ds: 007b es: 007b ss: 0068Process bash ( pid: 6084, ti= f4dc5000 task= f7c8d4d0 task. ti= f4dc5000) Stack : c1065914 f4dc5fa4 f4f6ca40 fffffff7 b7c2d000 f4dc5000 c1065f06 f4dc5fa400000000 00000000 00000000 00000001 00000001 c1003d0b 00000001 b7c2d00000000001 00000001 b7c2d000 bfd40aa8 ffffffda 0000007b c100007b 00000004Call Trace: [ < c1065914> ] vfs_write+ 0xa1/ 0x143[ < c1065f06> ] sys_write+ 0x3c/ 0x63[ < c1003d0b> ] syscall_call+ 0x7/ 0xbCode: Bad EIP value. EIP: [ < f8e8000e> ] faulty_write+ 0xe/ 0x19 [ faulty] SS: ESP 0068: f4dc5f84
其中,我们应该关注的信息是第一行红色标出部分:告诉我们操作了NULL指针。其次,就是第二行红色部分:EIP is at faulty_write+0xe/0x19。这个出错信息告诉我们EIP指针出现问题的地方时faulty_write函数,而且指出了是faulty 模块。同时,faulty_write+0xe/0x19的后半部分0xe/0x19,说明该函数的大小时0x019,出错位置是在0x0e。这两个值应该值得都是汇编代码的值。(4)将faulty模块反汇编出汇编代码: objdump -d faulty.ko > faulty.s 或 objdump -d faulty.o > faulty.s然后,我们打开faulty.s文件。由于我们需要关注的部分正好在文件的前面,因此我这里只贴出文件的前面一部分内容:
faulty. o: file format elf32- i386Disassembly of section . text: 00000000 < faulty_write> : 0: 81 f9 00 01 00 00 cmp $0x100, % ecx6: b8 00 01 00 00 mov $0x100, % eaxb: 0f 46 c1 cmovbe % ecx, % eaxe: c7 05 00 00 00 00 00 movl $0x0, 0x015: 00 00 0018: c3 ret 00000019 < cleanup_module> : 19: a1 00 00 00 00 mov 0x0, % eax1e: ba 00 00 00 00 mov $0x0, % edx23: e9 fc ff ff ff jmp 24 < cleanup_module+ 0xb> 00000028 < faulty_init> : 28: a1 00 00 00 00 mov 0x0, % eax2d: b9 00 00 00 00 mov $0x0, % ecx32: ba 00 00 00 00 mov $0x0, % edx37: e8 fc ff ff ff call 38 < faulty_init+ 0x10> 3c: 85 c0 test % eax, % eax3e: 78 13 js 53 < faulty_init+ 0x2b> 40: 83 3d 00 00 00 00 00 cmpl $0x0, 0x047: 74 03 je 4c < faulty_init+ 0x24> 49: 31 c0 xor % eax, % eax4b: c3 ret 4c: a3 00 00 00 00 mov % eax, 0x051: 31 c0 xor % eax, % eax53: 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 慢慢学会了长大。流转的时光,