学习笔记 --- LINUX 驱动调试之定位系统僵死

来源:互联网 发布:淘宝快递代理 编辑:程序博客网 时间:2024/05/22 00:05

转自:http://liu1227787871.blog.163.com/blog/static/205363197201261142752997/

在系统空间可能会发生这样一种情况:系统僵死!
此时系统处于僵死状态,进程不再运行!那么有没有办法找到这个僵死的进程呢?答案是肯定的!这里要引入的就是系统时钟中断的概念:
即便是在系统将死的情况下,系统时钟中断依然在以固定的频率发生,那么我们就可以进入系统时钟中断的处理函数中去将当前僵死的进程的一些信息打印出来!
我们在命令行输入:# 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

红色部分就是系统时钟中断,我们可以在内核中查找: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_open里面

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可以找到的是这个段的大概位置,要想发现具体的问题,我们对反汇编要有一定程度的了解!

0 0