首页 / 操作系统 / Linux / Linux内核调试技术之修改内核定时器来定位系统僵死问题
1.简介
在内核调试中,会经常出现内核僵死的问题,也就是发生死循环,内核不能产生调度。导致内核失去响应。这种情况下我们可以采用修改系统内核中的系统时钟的中断来定位发生僵死的进程和函数名称。因为内核系统系统时钟采用的是硬件中断的形式存在,所以,软件发生僵死的时候,系统时钟照样会发生中断。 1.1、我们在命令行输入:# cat /proc/interrupts # cat /proc/interruptsCPU0 30: 8316 s3cS3C2410 Timer Tick -----> 系统时钟 33:0 s3cs3c-mci 34:0 s3cI2SSDI 35:0 s3cI2SSDO 37: 12 s3cs3c-mci 42:0 s3cohci_hcd:usb1 43:0 s3cs3c2440-i2c 51: 1047 s3c-exteth0 60:0 s3c-exts3c-mci 70: 16 s3c-uart0s3c2440-uart 71: 26 s3c-uart0s3c2440-uart 79:8 s3c-adcs3c2410_action 80: 1732 s3c-adcs3c2410_action 83:0 -s3c2410-wdtErr:0# 30: 8316 s3cS3C2410 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_ts3c2410_timer_interrupt(int irq, void *dev_id){#if 1static pid_t pre_pid;static int cnt=0;//时钟中断的中断号是30if(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);}}#endifwrite_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 ons3c2410_timer_interrupt : pid = 770, task_name = firstdrvtests3c2410_timer_interrupt : pid = 770, task_name = firstdrvtest 根据上述信息可知,发送僵死的进程号为:770,发送僵死的进程名称为:firstdrvtest3、继续完善,增加PC值,更加详细的定位僵死的地方
我们知道,当中断发送的时候,在汇编中会调用asm_do_irq函数,.macroirq_handlerget_irqnr_preamble r5, lr1:get_irqnr_and_base r0, r6, r5, lrmovner1, sp@@ routine called with r0 = irq number, r1 = struct pt_regs *@adrnelr, 1bbneasm_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 1static pid_t pre_pid;static int cnt=0;//时钟中断的中断号是30if(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值}}#endifstatic 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 ons3c2410_timer_interrupt : pid = 771, task_name = firstdrvtestpc = bf0000844.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 movip, sp40:e92dd800 stmdbsp!, {fp, ip, lr, pc}44:e24cb004 subfp, ip, #4; 0x448:e24dd004 subsp, sp, #4; 0x44c:e3cd3d7f bicr3, sp, #8128 ; 0x1fc050:e3c3303f bicr3, r3, #63 ; 0x3f54:e5933008 ldrr3, [r3, #8]58:e0910002 adds r0, r1, r25c:30d00003 sbcccsr0, r0, r360:33a03000 movccr3, #0 ; 0x064:e3530000 cmpr3, #0 ; 0x068:e24b0010 subr0, fp, #16; 0x106c:1a00001c bnee4 <init_module+0x5c>70:ebfffffe bl 70 <first_drv_write+0x34>74:ea00001f bf8 <init_module+0x70>78:e3520000 cmp r2, #0; 0x07c:11a01002 movner1, r280:1bfffffe blne80 <first_drv_write+0x44> #错误在这,死循环!!!!84: ea00001fb 108 <init_module+0x80> 注意:在arm中,中断保存的PC是当前指令加4,所以真正僵死的位置是:bf00000080,也就是:80本文永久更新链接地址:http://www.linuxidc.com/Linux/2017-01/139029.htm