您的位置:首页 > 运维架构 > Linux

linux驱动调试之修改系统时钟中断定位系统僵死问题

2017-06-10 22:37 387 查看
在first_drv.c中故意引入错误,在点灯时引入死循环,当应用程序调用write函数就会在while里面卡住。



重新编译驱动,并拷贝到网络文件系统中,装载驱动并把灯点亮,但是系统卡死了。

可以看测试程序的源代码,看涉及什么系统调用,找到对应的驱动程序并进行分析。



在系统空间可能会发生这样一种情况:系统僵死!

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

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

我们在命令行输入:# 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)  (每过5毫秒,中断处理函数会被调用一次)
{
/*如果10秒钟之内都是同一个进程在运行,就打印
       static pid_t pre_pid;//进程号

       static int cnt=0;

       if(pre_pid==current->pid)//如果之前的PID号和当前的PID号相等
        {
            cnt++;  //计数值增加
        }
       else
        {
            cnt=0;
            pre_pid=current->pid;//修改当前进程
        }
       //如果本进程十秒钟还没有离开的话,就会打印下面的语句
       if(cnt==10*HZ) //这里HZ是100,表示 1秒钟会产生100次中断
        {
            cnt=0;
            printk("s3c2410_timer_interrupt : pid = %d, task_name = %s\n",current->pid,current->comm);//打印进程PID和名称
        }       

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秒钟发生多少次中断!

修改timer.c,重新编译内核并用新的内核启动,系统没有任何应用程序在跑,没有PID为0 的应用进程,PID为0应该表示内核的某个状态。






安装有问题的驱动程序,运行测试程序,系统完全卡死,使系统卡住的进程是753






想知道进程卡在哪里

下面我们测试一下:

我们可以某个驱动程序里面放入语句: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可以找到的是这个段的大概位置,要想发现具体的问题,我们对反汇编要有一定程度的了解
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: