系统僵死是Linux系统中一种严重的错误,它发生在系统无法响应任何输入或输出的情况下,导致系统无法正常工作。系统僵死的原因可能有很多,如内存泄漏,死锁,中断屏蔽等。系统僵死的调试是Linux内核调试技术中最困难的一种,它涉及到内核定时器,内核打印,内核调试器等概念。在本文中,我们将介绍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 (arch\arm\plat-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\n",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\n",current->pid,current->comm);
printk("pc = %08x\n",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
(2)在kallsyms.txt中,first_drv_open 实际位置是:bf000000
(3)根据上面的信息,可知知道,在反汇编中,发送僵死的位置为00000084 – 4 处
(4)查找00000084处代码在函数:first_drv_write中
0000003c :
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
70: ebfffffe bl 70
74: ea00001f b f8
78: e3520000 cmp r2, #0 ; 0x0
7c: 11a01002 movne r1, r2
80: 1bfffffe blne 80 #错误在这,死循环!!!!
84: ea00001f b 108
注意:在arm中,中断保存的PC是当前指令加4,所以真正僵死的位置是:bf00000080,也就是:80
通过本文,我们了解了Linux内核调试技术之修改内核定时器来定位系统僵死问题的原理和方法,它们可以用来实现对系统僵死问题的定位和分析。我们应该根据实际需求选择合适的方法,并遵循一些基本原则,如使用正确的定时器函数,使用正确的打印函数,使用正确的调试器命令等。系统僵死是Linux系统中最严重的错误之一,它可以反映系统中的问题和缺陷,也可以提升系统的质量和稳定性。希望本文能够对你有所帮助和启发。
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !