• linux内核调试技术之修改内核定时器来定位系统僵死问题


    1.简介

      在内核调试中,会经常出现内核僵死的问题,也就是发生死循环,内核不能产生调度。导致内核失去响应。这种情况下我们可以采用修改系统内核中的系统时钟的中断来定位发生僵死的进程和函数名称。因为内核系统系统时钟采用的是硬件中断的形式存在,所以,软件发生僵死的时候,系统时钟照样会发生中断。

      1.1、我们在命令行输入:# cat /proc/interrupts 
    # cat /proc/interrupts 
               CPU0
     30:       8316         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:       1047     s3c-ext  eth0
     60:          0     s3c-ext  s3c-mci
     70:         16   s3c-uart0  s3c2440-uart
     71:         26   s3c-uart0  s3c2440-uart
     79:          8     s3c-adc  s3c2410_action
     80:       1732     s3c-adc  s3c2410_action
     83:          0           -  s3c2410-wdt
    Err:          0
    # 
       30:       8316         s3c  S3C2410 Timer Tick 这个就是系统时钟,中断号为30
     1.2、在内核代码中
    搜索"
    S3C2410 Timer Tick"字样。
      在
    Time.c (archarmplat-s3c24xx)文件中有如下代码。
    static struct irqaction s3c2410_timer_irq = {
        .name        = "S3C2410 Timer Tick",
        .flags        = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
        .handler    = s3c2410_timer_interrupt,
    };
    
    /*
     * IRQ handler for the timer
     */
    static irqreturn_t
    s3c2410_timer_interrupt(int irq, void *dev_id)
    {
    #if 1
        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
    ",current->pid,current->comm);
            }
        }
    #endif
        
        write_seqlock(&xtime_lock);
        timer_tick();
        write_sequnlock(&xtime_lock);
        return IRQ_HANDLED;
    
    }

      ①、每个进程都有一个结构task_struct用来存储进程的一些状态信息。current是一个宏,表示当前进程的信息,也就是一个task_struct结构体,所以current->pid为当前进程的pid号,current->comm表示当前进程的name。

      ②、HZ也是一个宏定于,表示1s需要多少次中断。10*HZ表示就就是10s需要多少次中断

      

     2、测试

      编译内核:#make uImage

      加载一个带有while(1);的驱动程序,系统发送僵死,系统会打印如下信息:

    # insmod first_drv.ko 
    # ./firstdrvtest on
    s3c2410_timer_interrupt : pid = 770, task_name = firstdrvtest
    s3c2410_timer_interrupt : pid = 770, task_name = firstdrvtest

     根据上述信息可知,发送僵死的进程号为:770,发送僵死的进程名称为:firstdrvtest

    3、继续完善,增加PC值,更加详细的定位僵死的地方

         我们知道,当中断发送的时候,在汇编中会调用asm_do_irq函数,

        .macro    irq_handler
        get_irqnr_preamble r5, lr
    1:    get_irqnr_and_base r0, r6, r5, lr
        movne    r1, sp
        @
        @ routine called with r0 = irq number, r1 = struct pt_regs *
        @
        adrne    lr, 1b
        bne    asm_do_IRQ #调用C语言的函数
    asm_do_IRQ 函数原型:
    asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
    {
        static pid_t pre_pid;
        static int cnt=0;
        
        struct pt_regs *old_regs = set_irq_regs(regs);
        struct irq_desc *desc = irq_desc + irq;
    
        /*
         * Some hardware gives randomly wrong interrupts.  Rather
         * than crashing, do something sensible.
         */
        if (irq >= NR_IRQS)
            desc = &bad_irq_desc;
    
        irq_enter();
    
        desc_handle_irq(irq, desc);
    
        /* AT91 specific workaround */
        irq_finish(irq);
    
        irq_exit();
        set_irq_regs(old_regs);
    
        
    } 
      asm_do_IRQ这个函数,在这个函数里面我们发现了一个结构体:struct pt_regs,这个结构体就用来保存发生中断时的现场,其中PC值就是:ARM_pc
      我们将上面在:s3c2410_timer_interrupt里面加入的信息都删除,并在:asm_do_IRQ函数里面加修改后改函数为:(红色为添加的程序)
    asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
    {
    
    #if 1
        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
    ",current->pid,current->comm);
                printk("pc = %08x
    ",regs->ARM_pc);//打印pc值
            }
        }
    #endif
    
        static pid_t pre_pid;
        static int cnt=0;
        
        struct pt_regs *old_regs = set_irq_regs(regs);
        struct irq_desc *desc = irq_desc + irq;
    
        /*
         * Some hardware gives randomly wrong interrupts.  Rather
         * than crashing, do something sensible.
         */
        if (irq >= NR_IRQS)
            desc = &bad_irq_desc;
    
        irq_enter();
    
        desc_handle_irq(irq, desc);
    
        /* AT91 specific workaround */
        irq_finish(irq);
    
        irq_exit();
        set_irq_regs(old_regs);
    
        
    }

     4、测试:

    # insmod first_drv.ko 
    # ./firstdrvtest on
    s3c2410_timer_interrupt : pid = 771, task_name = firstdrvtest
    pc = bf000084

    4.1、查看内核中内核函数、加载的函数的地址

      #cat /proc/kallsyms > /kallsyms.txt 

      找到pc地址为bf000084附近的函数:

      

    ....................................
    00000000 a first_drv.c    [first_drv]
    bf000088 t first_drv_init    [first_drv]
    bf000140 t first_drv_exit    [first_drv]
    c48761cc ? __mod_license87    [first_drv]
    bf000940 b $d    [first_drv]
    bf000740 d first_drv_fops    [first_drv]
    bf000740 d $d    [first_drv]
    bf00003c t first_drv_write    [first_drv]  #大概就在这个函数里面,可以确定僵死的地方在
    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]
    bf000184 t $d    [first_drv]
    00000000 a first_drv.mod.c    [first_drv]
    c48761d8 ? __module_depends    [first_drv]
    bf0008ac d $d    [first_drv]
    c4876204 ? __mod_vermagic5    [first_drv]
    c01bd44c u class_device_create    [first_drv]
    c008ca94 u register_chrdev    [first_drv]
    c01bd668 u class_device_unregister    [first_drv]
    bf000948 b major    [first_drv]
    bf000944 b gpfcon    [first_drv]
    c0031ad0 u __iounmap    [first_drv]
    c01bc968 u class_create    [first_drv]
    bf0007c0 d __this_module    [first_drv]
    bf000088 t init_module    [first_drv]
    c008c9dc u unregister_chrdev    [first_drv]
    bf000140 t cleanup_module    [first_drv]
    c01bc9dc u class_destroy    [first_drv]
    bf000940 b gpfdat    [first_drv]
    c0031a6c u __arm_ioremap    [first_drv]
    c0172f80 u __copy_from_user    [first_drv]
    c01752e0 u __memzero    [first_drv]

    4.2、查看反汇编

      #arm-linux-objdump -D first_drv.ko > first_drv.dis

      在kallsyms.txt中可以知道,first_drv_write的入口地址为 bf00003c 

      打开first_drv.dis,如何查找真正僵死的位置?
      (1)首先从反汇编文件中找到位置为00000000的函数:00000000 <first_drv_open>:
      (2)在kallsyms.txt中,first_drv_open 实际位置是:bf000000 
      (3)根据上面的信息,可知知道,在反汇编中,发送僵死的位置为00000084 - 4  处
      (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>

       注意:在arm中,中断保存的PC是当前指令加4,所以真正僵死的位置是:bf00000080,也就是:80



  • 相关阅读:
    生成密码
    生成密码
    C#委托
    C#委托
    C#委托
    最近所有博客
    win10 uwp 读写XML
    win10 uwp 读写XML
    win10 uwp 读写XML
    win10 uwp 绑定密码
  • 原文地址:https://www.cnblogs.com/veryStrong/p/6236577.html
Copyright © 2020-2023  润新知