1.简介

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

  1.1、我们在命令行输入:# cat /proc/interrupts 
# cat /proc/interrupts
CPU0
30: 8316 s3c S3C2410 Timer Tick -----> 系统时钟
: s3c s3c-mci
: s3c I2SSDI
: s3c I2SSDO
: s3c s3c-mci
: s3c ohci_hcd:usb1
: s3c s3c2440-i2c
: s3c-ext eth0
: s3c-ext s3c-mci
: s3c-uart0 s3c2440-uart
: s3c-uart0 s3c2440-uart
: s3c-adc s3c2410_action
: s3c-adc s3c2410_action
: - s3c2410-wdt
Err:
#
   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 = , task_name = firstdrvtest

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

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

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

    .macro    irq_handler
get_irqnr_preamble r5, lr
: 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=; 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=; 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附近的函数:

  

....................................
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]
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
: e92dd800 stmdb sp!, {fp, ip, lr, pc}
: e24cb004 sub fp, ip, # ; 0x4
: e24dd004 sub sp, sp, # ; 0x4
4c: e3cd3d7f bic r3, sp, # ; 0x1fc0
: e3c3303f bic r3, r3, # ; 0x3f
: e5933008 ldr r3, [r3, #]
: e0910002 adds r0, r1, r2
5c: 30d00003 sbcccs r0, r0, r3
: 33a03000 movcc r3, # ; 0x0
: e3530000 cmp r3, # ; 0x0
: e24b0010 sub r0, fp, # ; 0x10
6c: 1a00001c bne e4 <init_module+0x5c>
: ebfffffe bl <first_drv_write+0x34>
: ea00001f b f8 <init_module+0x70>
: e3520000 cmp r2, # ; 0x0
7c: 11a01002 movne r1, r2
80: 1bfffffe blne 80 <first_drv_write+0x44> #错误在这,死循环!!!!
:  ea00001f b 108 <init_module+0x80>

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


linux内核调试技术之修改内核定时器来定位系统僵死问题的更多相关文章

  1. linux内核调试技术之printk

    原创博客:欢迎转载,转载请注明出处https://i.cnblogs.com/EditPosts.aspx?postid=6218383 1.简介(基于s3c2440 linux) 在内核调试技术之中 ...

  2. Linux内核调试技术——jprobe使用与实现

    前一篇博文介绍了kprobes的原理与kprobe的使用与实现方式,本文介绍kprobes中的另外一种探測技术jprobe.它基于kprobe实现,不能在函数的任何位置插入探測点,仅仅能在函数的入口处 ...

  3. Windows Kernel Way 1:Windows内核调试技术

    掌握Windows内核调试技术是学习与研究Windows内核的基础,调试Windows内核的方式大致分为两种: (1)通过Windbg工具在Windows系统运行之初连接到Windows内核,连接成功 ...

  4. Linux kprobe调试技术使用

    kprobe调试技术是为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术. 利用kprobe技术,可以在内核绝大多数函数中动态插入探测点,收集调试状态所需信息而基本不影响原有执行流程. kpr ...

  5. 嵌入式Linux的调试技术

    本节我们研究嵌入式Linux的调试技术,对于复杂的Linux驱动及HAL等程序库,需要使用各种方法对其进行调试.刚开始讲了打印内核调试信息:printk,这个函数的用法与printf函数类似,只不过p ...

  6. Linux内核调试方法总结之内核通知链

    Linux内核通知链notifier 1.内核通知链表简介(引用网络资料)    大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣.为了满足这个需求,也即是让某个子系统在 ...

  7. 第十章 嵌入式Linux的调试技术

    对调试工具进行简介.Linux中提供了一类工具,通过这些工具可以逐行跟踪程序的代码,用于测试用户空间程序的gdb.gdbserver和调试内核空间程序的kgdb. 用gdb调试用户空间程序:gdb可跟 ...

  8. 第10章 嵌入式Linux 的调试技术

    10.1  打印内核调试信息:printk printk位函数运行在内核空间, printf函数运行在用户空间.也就是说,像Linux 驱动这样的Linux内核程序只能使用printk函数输出调试信息 ...

  9. linux内核调试技术之自构proc

    1.简介 在上一篇中,在内核中使用printk可以讲调试信息保存在log_buf缓冲区中,可以使用命令 #cat /proc/kmsg  将缓冲区的数区的数数据打印出来,今天我们就来研究一下,自己写k ...

随机推荐

  1. ASP.NET Core 之 Identity 入门(一)

    前言 在 ASP.NET Core 中,仍然沿用了 ASP.NET里面的 Identity 组件库,负责对用户的身份进行认证,总体来说的话,没有MVC 5 里面那么复杂,因为在MVC 5里面引入了OW ...

  2. premere cs4绿色版 安装 并且 视频导出 讲解

    最近室友,开始在玩视频剪辑,用的是 premere cs4 绿色版.让他遇到的最大问题也是我之前遇到的最大问题,就是视频导出. 所以我在这里上传一套自己的一点点经验吧. 接下来,我就总结一下 我是怎么 ...

  3. Node.js:进程、子进程与cluster多核处理模块

    1.process对象 process对象就是处理与进程相关信息的全局对象,不需要require引用,且是EventEmitter的实例. 获取进程信息 process对象提供了很多的API来获取当前 ...

  4. TortoiseGit 文件比对工具使用 Beyond Compare 和 DiffMerge

    TortoiseGit 内置的文件比对工具是 TortoiseMerge,用于文件比对和解决冲突合并,TortoiseGit 还支持外部的比对工具使用,比如 Beyond Compare 和 Diff ...

  5. Python(九) Python 操作 MySQL 之 pysql 与 SQLAchemy

    本文针对 Python 操作 MySQL 主要使用的两种方式讲解: 原生模块 pymsql ORM框架 SQLAchemy 本章内容: pymsql 执行 sql 增\删\改\查 语句 pymsql ...

  6. C++内联函数

    在C语言中,我们使用宏定义函数这种借助编译器的优化技术来减少程序的执行时间,那么在C++中有没有相同的技术或者更好的实现方法呢?答案是有的,那就是内联函数.内联函数作为编译器优化手段的一种技术,在降低 ...

  7. 异步 HttpContext.Current 为空null 另一种解决方法

    1.场景 在导入通讯录过程中,把导入的失败.成功的号码数进行统计,然后保存到session中,客户端通过轮询显示状态. 在实现过程中,使用的async调用方法,出现HttpContext.Curren ...

  8. 【JQ基础】DOM操作

    内部插入:append() //向每个匹配的元素内部追加内容,可包含 HTML 标签 $(selector).append(function(index,html)) /*•index - 可选.接收 ...

  9. Mono 3.2.7发布,JIT和GC进一步改进

    Mono 3.2.7已经发布,带来了很多新特性,如改进的JIT.新的面向LINQ的解释器以及使用了64位原生指令等等. 这是一次主要特性发布,累积了大约5个月的开发工作.看上去大部分改进都是底层的性能 ...

  10. Xamarin.Android-用ZXing实现二维码扫描以及连续扫描

    一.前言 本文的内容有两个基础:ZXing.Net和ZXing.Net.Mobile ZXing.Net:ZXing的C#实现,主要封装了各种二维码的编码.解码等跨平台的算法 ZXing.Net.Mo ...