Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1694882
  • 博文数量: 174
  • 博客积分: 5493
  • 博客等级: 上校
  • 技术积分: 5802
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-05 15:13
个人简介

炼狱,是为追逐光芒

文章分类

全部博文(174)

文章存档

2017年(1)

2016年(3)

2015年(9)

2014年(5)

2013年(23)

2012年(56)

2011年(45)

2010年(32)

分类: LINUX

2014-01-24 14:59:48

驾驭内核调试的能力很大程度上取决于经验和对整个操作系统的把握。

1.重现Bug如果能重新bug,成功的概率会大很多,跟踪Bug的时候,掌握的信息越多越好,许多时候,精确重现,定位一个bug的时候,就已经成功了一半了。

 

2.通过打印来调试

2.1 printk()C库的printf()功能几乎相同,并且printk()是一个健壮性极好的函数,在任何时候,任何地方都可以调用它。中断上下文,进程上下文,在任何持有锁时都可以调用,在多处理器上同时调用,而且调用者连锁都不用加。

唯一的漏洞,在系统启动过程,终端还没初始化之前,某些地方不能使用,这时可以用early_printk()代替,或用其他调试手段。

2.2日志等级

printk()定义了8个消息级别,分为级别07,越低级别(数值越大)的消息越不重要。第0级的优先级最高:

通过/proc/sys/kernel/printfk文件可以调节printk的输出等级,该文件有4个数值

控制台日志级别:优先级高于该值的消息将被打印至控制台。

默认的消息日志级别:用该优先级来打印没有优先级的消息;

最低的控制台日志级别:控制台日志级别可被设置的最小值(最高级别);

默认的控制台日志级别:控制台日志级别的默认值;

上述4个值的默认设置是6417

通过以下命令可以使得Linux内核的任何printk都被打印

# echo 8 > /proc/sys/kernel/printk

printk打印的内核消息被保存在一个LOG_BUF_LEN大小的环形队列中,可以通过CONFIG_LOG_BUF_SHIFT, 默认是16K。环形队列的好处是不会消耗大量内存,缺点是可能丢失消息。

3. /proc

Linux系统中,/proc文件系统用于内核向用户导出信息,硬件信息(内存,中断信息等),都可以在/proc文件系统查到。Linux许多命令本身也是基于分析/proc文件来完成的。

如:ps, top, uptime, free.

4.syslogdklogd

在标准Linux系统上,用户空间的守护进程klogd从记录缓冲区中获取内核消息,再通过syslogd守护进程将它们保存在系统日志文件中。

syslogd守护进程把它接收到的所有消息添加进一个文件中,该文件默认是/var/log/messages.

也可以通过/etc/syslog.conf配置文件重新制定。

klogd启动的时候,可以通过制定-c标志来改变终端的记录等级。

5.Oops

当内核出现段错误(比如访问非法指针),就会抛出一个OopsOops包含的内容包括寄存器上下文和回溯线索,下面举例解析一个Oops

第一行指示出错原因,在虚拟地址0x01处,访问空指针。

Bug回溯跟踪,定位到tuplip_timer()函数的0x128字节偏移处,通过反汇编可以知道偏移0x128对于的C代码。

同理,可以一次回溯到上一层调用函数。

6.引发bug并打印信息

一些内核调用可以用来方便标记Bug,提供assertion和输出信息,最常用的是BUG()BUG_ON()。它们被调用,会引发oops, 导致栈的回溯和错误信息打印。

点击(此处)折叠或打开

  1. if (bad_thing)
  2. BUG();
  3. Or even better
  4. BUG_ON(bad_thing);

BUG_ON()会将其声明作为一个语句放入unlikely()中。

还可以使用编译选项,把BUG_ON()声明在编译时剔除,一遍能在嵌入内核中节约空间。

BULD_BUG_ON()BUG_ON()作用相同,但仅在编译时调用,如果编译阶段已提供的声明为真,那么编译将会因为一个错误而终止。

可以用panic()引发更严重的错误,调用panic()不但会打印错误信息,而且还会挂起整个系统,所以只应该在最糟糕的情况使用它:

点击(此处)折叠或打开

  1. if (terrible_thing)
  2.     panic(“terrible_thing is %ld!\n”, terrible_thing);

有时候只需要在终端上打印一下栈的回溯信息来帮助调试,用dump_stack()更有用。它只在终端上打印寄存器上下文和函数的跟踪线索:

点击(此处)折叠或打开

  1. if (!debug_check) {
  2.     printk(KERN_DEBUG “provide some information...\n”);
  3.     dump_stack();
  4. }


7.
探测系统:

内核调试很有挑战性,即使一点小的暗示或技巧都能给你很大的帮助:

1)用UID作为选择条件

当开发与进程相关的部分时,为了不打破原有代码的可执行性,重写系统调用的时候,非常有用:

点击(此处)折叠或打开

  1. if (current->uid != 7777) {
  2.     /* old algorithm .. */
  3. } else {
  4.     /* new algorithm .. */
  5. }

UID7777时,实现调试,其他进程,功能不做任何改变。

2)使用条件变量和使用统计量

创建一个全局变量作为条件选择开关;

3)重复频率限制

为避免调试信息发送井喷,可以每隔几秒执行一次打印(或者是其他任何你想完成的操作);

8.用二分查找法找出引发罪恶的变更

如果用SVN之类的代码管理工具,那么在一个稳定的版本,和当前有BUG版本之间,用二分查找法,定位引起BUG的版本。

9.使用git进行二分搜索

如果用git管理Linux源码树的副本,那么git将自动运行二分查找进程。Git会在修订版中进行二分搜索,找到具体哪次提交的代码引发了Bug

10.求助社区

当所有努力已经尝试过,任然没有解决问题,求助社区。




阅读(3262) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~