您的位置:首页 > 其它

修改内核来定位系统僵死问题

2014-07-30 08:55 274 查看
在系统空间可能会发生这样一种情况:系统僵死!

此时系统处于僵死状态,进程不再运行!那么有没有办法找到这个僵死的进程呢?答案是肯定的!这里要引入的就是系统时钟中断的概念:

即便是在系统将死的情况下,系统时钟中断依然在以固定的频率发生,那么我们就可以进入系统时钟中断的处理函数中去将当前僵死的进程的一些信息打印出来!

我们在命令行输入:# cat /proc/interrupts

打印出如下信息:

CPU0
30: 85713 s3c S3C2410 Timer Tick //这个就是系统时钟中断
33: 0 s3c s3c-mci
34: 0 s3c I2SSDI
35: 0 s3c I2SSDO
37: 12 s3c s3c-mci
42: 0 s3c ohci_hcd:usb1
43: 0 s3c s3c2440-i2c
51: 3509 s3c-ext eth0
60: 0 s3c-ext s3c-mci
70: 96 s3c-uart0 s3c2440-uart
71: 92 s3c-uart0 s3c2440-uart
83: 0 - s3c2410-wdt
Err: 0

30: 85713 s3c S3C2410 Timer Tick :这个就是系统时钟中断,我们可以再内核中查找:S3C2410 Timer Tick,会找到这样一个结构体:

static struct irqaction s3c2410_timer_irq = {
.name= "S3C2410 Timer Tick",
.flags= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
.handler= s3c2410_timer_interrupt,
};

其中:s3c2410_timer_interrupt就是中断处理函数!我们在其中加入一些信息:

s3c2410_timer_interrupt(int irq, void *dev_id)
{
static pid_t pre_pid;

static int cnt=0;

if(pre_pid==current->pid)
{
cnt++;
}
else
{
cnt=0;
pre_pid=current->pid;
}
//如果本进程十秒钟还没有离开的话,就会打印下面的语句
if(cnt==10*HZ)
{
cnt=0;
printk("s3c2410_timer_interrupt : pid = %d, task_name = %s\n",current->pid,current->comm);
}

write_seqlock(&xtime_lock);
timer_tick();
write_sequnlock(&xtime_lock);
return IRQ_HANDLED;
}

关于我们加入的代码有两点需要说一下:

第一:每一个进程都需要用一个结构体来表示:task_struct,这里面保存着与进程的一些状态信息,而current是一个宏,它代表当前的进程,也是一个task_struct结构体。所以current->pid就代表本进程的id,而current->comm就代表本进程的名字!

第二:HZ是一个宏定义,它表示1秒钟发生多少次中断,10*HZ就代表10秒钟发生多少次中断!

下面我们测试一下:

我们可以某个驱动程序里面放入语句:while(1);这样的话,当程序执行到这里的时候就会僵死掉,在之前没有加入上述信息之前,没有任何打印信息,我们根本不知道是哪一个进程发生了僵死,现在的话,没过10秒就会打印相关信息,告诉我们现在是什么进程正在发生僵死!我的测试打印信息如下:

s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest

不过这样还不够详细,有没有办法知道是在哪里发生了僵死呢?这也是有办法的!
先来说一下原理:在应用程序的执行的时候,会一直以固定的频率发生时钟中断,那么发生中断的时候肯定会保存现场吧,那么这个保存现场的时候就要保存发生中断处的PC值,这样才能返回,那么如果把这个PC值打印出来不就知道在哪里发生中断了吗!

我们之前分析过,发生中断的时候经过一些前期处理之后会调用:asm_do_IRQ这个函数

在这个函数里面我们发现了一个结构体:struct pt_regs,这个结构体就用来保存发生中断时的现场,其中PC值就是:ARM_pc

我们将上面在:s3c2410_timer_interrupt里面加入的信息都删除,并在:asm_do_IRQ函数里面加入如下信息:

static pid_t pre_pid;
static int cnt=0;
//时钟中断的中断号是30
if(irq==30)
{
if(pre_pid==current->pid)
{
cnt++;
}
else
{
cnt=0;
pre_pid=current->pid;
}

if(cnt==10*HZ)
{
cnt=0;
printk("s3c2410_timer_interrupt : pid = %d, task_name = %s\n",current->pid,current->comm);
printk("pc = %08x\n",regs->ARM_pc);//打印pc值
}
}

我们在次测试的话,会打印出如下信息:

s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
pc = bf000084
s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
pc = bf000084
s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
pc = bf000084
s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest
pc = bf000084

这说明是在bf000084处发生了中断,这样就好办了,我们按照上一节的内容来找到找到这个位置:
1、重新启动开发板,要用同一个内核启动

2、加载驱动程序

3、 cat /proc/kallsyms > /kallsyms.txt

vi /kallsyms.txt

找到地址与bf000084相近的函数:

bf000000 t first_drv_open [first_drv]
bf000000 t $a [first_drv]
bf000038 t $d [first_drv]
bf00003c t $a [first_drv]
bf000114 t $d [first_drv]
bf00094c b firstdrv_class [first_drv]
bf000950 b firstdrv_class_dev [first_drv]
bf000140 t $a [first_drv] n
bf000184 t $d [first_drv]
00000000 a first_drv.mod.c [first_drv]
c486e1d8 ? __module_depends [first_drv

根据种种信息我们推断出出错位置在first_drv这个加载模块里面

4、反汇编first_drv:arm-linux-objdump -D first_drv.ko > first_drv.dis

打开first_drv.dis,在这里面找我们的需要的位置,那么该如何找呢?这是很有讲究的:

(1)首先从反汇编文件中找到位置为00000000的函数:00000000 <first_drv_open>:

(2)查看我们上面贴出的信息,得知:first_drv_open 实际位置是:bf000000

(3)于是我们就可以推断出来,出错位置在:00000084处

(4)经查找00000084处代码在函数:first_drv_write中

0000003c <first_drv_write>:
3c: e1a0c00d mov ip, sp
40: e92dd800 stmdb sp!, {fp, ip, lr, pc}
44: e24cb004 sub fp, ip, #4 ; 0x4
48: e24dd004 sub sp, sp, #4 ; 0x4
4c: e3cd3d7f bic r3, sp, #8128 ; 0x1fc0
50: e3c3303f bic r3, r3, #63 ; 0x3f
54: e5933008 ldr r3, [r3, #8]
58: e0910002 adds r0, r1, r2
5c: 30d00003 sbcccs r0, r0, r3
60: 33a03000 movcc r3, #0 ; 0x0
64: e3530000 cmp r3, #0 ; 0x0
68: e24b0010 sub r0, fp, #16 ; 0x10
6c: 1a00001c bne e4 <init_module+0x5c>
70: ebfffffe bl 70 <first_drv_write+0x34>
74: ea00001f b f8 <init_module+0x70>
78: e3520000 cmp r2, #0 ; 0x0
7c: 11a01002 movne r1, r2
80: 1bfffffe blne 80 <first_drv_write+0x44> //看下这条代码,不就是个死循环嘛,哈哈,找到了!!!
84: ea00001f b 108 <init_module+0x80>

还是有一点我们要知道,中断保存的PC是当前指令加4,所以真正僵死的位置是:bf00000080,也就是:80

虽然这里我们找到了僵死位置,但是实际上僵死往往发生在某一段代码上,所以根据中断时保存的PC可以找到的是这个段的大概位置,要想发现具体的问题,我们对反汇编要有一定程度的了解!

http://liu1227787871.blog.163.com/blog/static/205363197201261142752997/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: