本文转载自:http://blog.chinaunix.net/uid-20671208-id-4940381.html

曾经遇到过一个bug是这样的,通过串口终端和开发板交互时,执行一个程序后,整个系统就挂了,也不接受输入了,只能重启,
后来发现是死在某段代码里了,当时可是费了一番功夫,今天来说一下怎么调试这种系统僵死的程序.

首先说一下linux的时钟中断。
linux的时钟中断也是一种硬件中断,通过计数器产生输出脉冲,送到CPU,触发中断。这个中断比较特殊,它是来记录系统时间的,
每隔固定的一段时间就会触发一次。类似于现实中的钟表,每隔1秒就滴答一次,记录时间,我们的时间概念都是以这个为基准的。
同样,内核当中的时间都是以时钟中断为基准的,一次中断就可以认为是一个时间单位。它是内核的心脏,它不跳了,内核肯定就挂了。
系统利用时钟中断来维持系统时间、促使环境切换和进程调度。

linux用HZ来表示1s产生的时钟中断次数,用jiffies来表示自从系统启动后产生了多少次时钟中断

下面进入正题

首先写一个能够引起系统僵死的测试程序。

system_dead.c

点击(此处)折叠或打开

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/init.h>
  5. #include <linux/delay.h>
  6. #include <asm/uaccess.h>
  7. #include <asm/irq.h>
  8. #include <asm/io.h>
  9. #include <linux/device.h>
  10. static struct class *sysdead_class;
  11. static struct device *sysdead_class_dev;
  12. int major;
  13. static int sysdead_test_open(struct inode *inode, struct file *file)
  14. {
  15. int i = 0;
  16. int j = 0;
  17. int k = 0;
  18. while(1){
  19. i = i + 1;
  20. j = i + 1;
  21. k = j + 1;
  22. if(i > 100)
  23. i = 0;
  24. if(j > 100)
  25. j = 0;
  26. if(k > 100)
  27. k = 0;
  28. }
  29. //printk("sysdead_test_open success!\n");
  30. return 0;
  31. }
  32. static struct file_operations sysdead_test_fops = {
  33. .owner = THIS_MODULE,
  34. .open = sysdead_test_open,
  35. };
  36. static int sysdead_drv_init(void)
  37. {
  38. major = register_chrdev(0, "sysdead_test", &sysdead_test_fops);
  39. sysdead_class = class_create(THIS_MODULE, "sysdead_test");
  40. sysdead_class_dev = device_create(sysdead_class, NULL, MKDEV(major, 0), NULL, "sysdead");
  41. printk("sysdead_drv_init success!\n");
  42. return 0;
  43. }
  44. static void sysdead_drv_exit(void)
  45. {
  46. device_destroy(sysdead_class, MKDEV(major,0));
  47. class_destroy(sysdead_class);
  48. unregister_chrdev(major, "sysdead_test");
  49. }
  50. module_init(sysdead_drv_init);
  51. module_exit(sysdead_drv_exit);
  52. MODULE_LICENSE("GPL")

test.c

点击(此处)折叠或打开

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>
  5. int main(int argc, char **argv)
  6. {
  7. int fd;
  8. int val = 1;
  9. fd = open("/dev/sysdead", O_RDWR);
  10. if (fd < 0)
  11. {
  12. printf("can't open!\n");
  13. return -1;
  14. }
  15. close(fd);
  16. return 0;
  17. }

# insmod system_dead.ko
sysdead_drv_init success!
# ./test

程序会卡死,输入会没有任何反应,因为我们在 sysdead_test_open 函数里引入了一个死循环,当应用程序调用open的
时候,程序就陷入死循环出不来了。

这种调试方法的思想是,如果内核僵死了,我们认为可能是卡在某个程序里出不来了,这个时候内核只有这一个程序在运行。
我们调试方法就是,如果我们判断一个进程连续执行超过10s,我们就认为这个进程陷入了死循环,把进程号(可能是没用的,因为
卡死了要重新启动,在运行时进程号就变了)和PC值打印出来,根据PC值来定位当前执行的代码。

那么怎么去修改内核来实现我们的这种思想呢?

由之前的说明我们知道,无论什么时候内核的脉搏总是有的,就是系统时钟中断。就算卡死的时候也还是有的,因为它是一个
硬件中断,卡死的时候也会响应中断。所以我们在系统中断响应函数里添加一段代码实现我们上面的调试思想。

在linux-2.6.30.4\arch\arm\kernel\irq.c 文件中,找到asm_do_IRQ函数,修改如下:

点击(此处)折叠或打开

  1. asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
  2. {
  3. struct pt_regs *old_regs = set_irq_regs(regs);
  4. //add by llz in 2015.4.1
  5. static pid_t pre_pid = 0;
  6. static int cnt = 0;
  7. if(30 == irq)
  8. {
  9. if(pre_pid == current->pid)
  10. cnt++;
  11. else
  12. {
  13. cnt = 0;
  14. pre_pid = current->pid;
  15. }
  16. if(cnt == 10*HZ)
  17. {
  18. cnt = 0;
  19. printk("asm_do_IRQ -> s3c2410_timer_irq : pid = %d , task_name = %s , ", current->pid, current->comm);
  20. printk("pc = %08x\n", (unsigned int )regs->ARM_pc);
  21. }
  22. }
  23. //addition ends here
  24. irq_enter();
  25. /*
  26. * Some hardware gives randomly wrong interrupts. Rather
  27. * than crashing, do something sensible.
  28. */
  29. if (irq >= NR_IRQS)
  30. handle_bad_irq(irq, &bad_irq_desc);
  31. else
  32. generic_handle_irq(irq);
  33. /* AT91 specific workaround */
  34. irq_finish(irq);
  35. irq_exit();
  36. set_irq_regs(old_regs);
  37. }

添加的部分为红色的字体,注意第6、7行的pre_pid和cnt变量定义要用static修饰,这样初值只会赋一次,不然会判断错误。
具体原因请百度static的作用。30号中断是代表时钟中断,见linux-2.6.30.4\arch\arm\mach-s3c2410\include\mach\irqs.h。
current是结构体struct task_struct,表示当前进程。还有struct pt_regs,请百度。
通过current->pid, current->comm,regs->ARM_pc分别打印当前进程号、进程名、PC值。

接下来编译内核:make uImage
然后启动开发板,进入uboot模式,通过tftp下载uImage镜像,并启动:
> tftp 0x30007fc0 uImage
> bootm 0x30007fc0

(这个内核是用来调试的,每一次编译烧到nand flash太麻烦。关于tftp下载内核启动请参见另一篇博文:
http://blog.chinaunix.net/uid-29401328-id-4930747.html

下面再来测试一次:
# insmod system_dead.ko
sysdead_drv_init success!
# ./test             // 卡死了,等10s会打印如下信息
asm_do_IRQ -> s3c2410_timer_irq : pid = 635 , task_name = test , pc = bf0d700c
asm_do_IRQ -> s3c2410_timer_irq : pid = 635 , task_name = test , pc = bf0d700c

我们一眼就看出来了,问题出现在test这个程序上,但具体出在哪不清楚,就要根据PC值去分析了,分析方法和之前博文讲的一样。
下面再唠叨一遍:

1. 找到bf0d700c所在的函数。
现在的系统僵死了,我们没办法继续下去,只有重启系统。这里注意一个问题,重启系统使用的内核要和僵死时使用的内核是同一个。
因为如果内核变了,我们就很难还原僵死前的状态了,新内核pc = bf0d700c可能代表不同的代码。

用tftp启动刚才的内核,插入模块system_dead.ko。
先去查看内核源码下的 System.map 文件,看PC地址是否属于其中,这里不属于(那里面是内核函数,地址都是以C开头)。
然后查看开发板的模块地址:cat cat /proc/kallsyms > kall.txt
打开kall.txt,在里面查找PC值相近的地址(有可能直接查到,也有可能PC位于某段地址之间),这里查到:

00000000 a system_dead.c        [system_dead]
bf0d7000 t $a   [system_dead]
bf0d7000 t sysdead_test_open    [system_dead]
bf0d7010 t sysdead_drv_exit     [system_dead]

可知pc = bf0d700c位于sysdead_test_open函数中。接下来去分析这个函数

2. 分析发生错误的函数
因为我们这里的代码很短,所以可以很快的定位出问题,但当代码很长时,可能就需要看汇编了。这里给出方法

反汇编sysdead_test_open函数位于的模块system_dead.ko:
arm-none-linux-gnueabi-objdump system_dead.ko -D > system_dead.dis

打开system_dead.dis:(贴出对我们有用的那段)
00000000 <sysdead_test_open>:
   0:   e1a0c00d        mov     ip, sp
   4:   e92dd800        push    {fp, ip, lr, pc}
   8:   e24cb004        sub     fp, ip, #4      ; 0x4
   c:   eafffffe        b       c <sysdead_test_open+0xc>

到这里就需要用汇编去分析了,我们这里的错误比较明显,就是一直跳转到自己这个函数里,调不出去了。

注:有可能两次发生僵死时PC值不一样,就算僵死在同一段代码,PC值也可能不一样。因为如果死循环是一段代码,
那么僵死时,程序可能正在执行这段代码当中的任意一句。

linux驱动调试--修改系统时钟终端来定位僵死问题【转】的更多相关文章

  1. 38.Linux驱动调试-根据系统时钟定位出错位置

    当内核或驱动出现僵死bug,导致系统无法正常运行,怎么找到是哪个函数的位置导致的? 答,通过内核的系统时钟,因为它是由定时器中断产生的,每隔一定时间便会触发一次,所以当CPU一直在某个进程中时,我们便 ...

  2. Linux驱动设计—— 中断与时钟

    中断和时钟技术可以提升驱动程序的效率 中断 中断在Linux中的实现 通常情况下,一个驱动程序只需要申请中断,并添加中断处理函数就可以了,中断的到达和中断函数的调用都是内核实现框架完成的.所以程序员只 ...

  3. 41.Linux应用调试-修改内核来打印用户态的oops

    1.在之前第36章里,我们学习了通过驱动的oops定位错误代码行 第36章的oops代码如下所示: Unable to handle kernel paging request at //无法处理内核 ...

  4. linux驱动调试--段错误之oops信息分析

    linux驱动调试--段错误之oops信息分析 http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29401328&id= ...

  5. linux驱动调试记录

    linux驱动调试 linux 目录 /proc 下面可以配置驱动的调试信息,比如给proc目录的自己定制的驱动的一文件设置一个变量,然后驱动程序跟了proc的参数值来配置调试级别.类似于内核调试的级 ...

  6. 转载:Linux下查看/修改系统时区、时间

    一.查看和修改Linux的时区 1. 查看当前时区 命令 : "date -R" 2. 修改设置Linux服务器时区 方法 A 命令 : "tzselect" ...

  7. Linux xclock打不开时钟终端

    一般执行该操作的都是在安装oracle数据库或其他应用时,需要测试是否可以正常弹层执行的: 网络关于这个的描述和处理大片片的,但是符合自己实际情况的,还是需要直接去确认: 两步处理: 第一步: 使用r ...

  8. Linux驱动设计—— 中断与时钟@request_irq参数详解

    request_irq函数定义 /*include <linux/interrupt.h>*/ int request_irq(unsigned int irq, irq_handler_ ...

  9. 36.Linux驱动调试-根据oops定位错误代码行

    1.当驱动有误时,比如,访问的内存地址是非法的,便会打印一大串的oops出来 1.1以LED驱动为例 将open()函数里的ioremap()屏蔽掉,直接使用物理地址的GPIOF,如下图所示: 1.2 ...

随机推荐

  1. 自定义字体TextView

    /** * 备注: * 作者:王莹 * 时间:2017/5/4. * ~_~想睡觉了!! * (-o-)~zZ我想睡啦- * π_π?打瞌睡 */ public class FontsTextView ...

  2. jquery的jsonp相关

    <!DOCTYPE html><html><head ><meta charset="utf-8"><script src=& ...

  3. MySQL中的注释(有三种)

    MysQL支持三种注释: .#... (推荐这种,具有通性) ."-- ..." (注意--后面有一个空格) ./*...*/

  4. 修改SQL Server 数据库的编码

    ALTER DATABASE [dbnam] collate SQL_Latin1_General_CP1_CI_AS 查询编码号:SELECT COLLATIONPROPERTY('SQL_Lati ...

  5. python基于yield实现协程

    def f1(): print(11) yield print(22) yield print(33) def f2(): print(55) yield print(66) yield print( ...

  6. 我的Android进阶之旅------>Android自定义View来实现解析lrc歌词并同步滚动、上下拖动、缩放歌词的功能

    前言 一LRC歌词文件简介 1什么是LRC歌词文件 2LRC歌词文件的格式 LRC歌词文件的标签类型 1标识标签 2时间标签 二解析LRC歌词 1读取出歌词文件 2解析得到的歌词内容 1表示每行歌词内 ...

  7. Perceptual Generative Adversarial Networks for Small Object Detection

    Perceptual Generative Adversarial Networks for Small Object Detection 感知生成对抗网络用于目标检测 论文链接:https://ar ...

  8. Java中对Clone的理解

    面试中经常遇到Clone的相关知识,今天总算是把Clone理解的比较透彻了!Java中Clone的概念大家应该都很熟悉了,它可以让我们很方便的“制造”出一个对象的副本来,下面来具体看看java中的Cl ...

  9. HTML5之WEB Storage

    什么是HTML5 web storage? 使用HTML5,web页面能够使用用户的浏览器本地保存数据. 在曾经,通常我们使用cookie来保存用户数据.然而使用web存储更加安全和高速.数据不再包括 ...

  10. 批量处理任务进度条控制—基于BackgroundWorker

    今天要做一个批量处理图层数据的功能,希望在处理任务过程中,各个任务都能在进度条中显示自己的当前进度,决定继续使用强大易用的BackgroundWorker组件.通过在RunWorkerComplete ...