LUPA首页
|
资讯
|
教程
|
下载
|
求职
|
方案
|
博客
|
交易
|
英文版
游客:
注册
|
登录
|
会员
|
统计
|
帮助
LUPA论坛
»
内核问题答疑
» 内核调试(四)
‹‹ 上一主题
|
下一主题 ››
投票
交易
悬赏
活动
打印
|
推荐
|
订阅
|
收藏
|
开通个人空间
|
加入资讯
标题: 内核调试(四)
陈莉君
版主
UID 26540
精华
1
积分 2168
帖子 145
LUPA币 2093 点
阅读权限 100
注册 2006-11-9
#1
发表于 2008-5-12 13:06
资料
个人空间
短消息
内核调试(四)
内核调试器的传奇
很多内核开发者一直以来都希望能拥有一个用于内核的调试器。不幸的是,
Linus
不愿意在他的内核源代码树中加入一个调试器。他认为调试器会误导开发者,从而导致引入不良的修正。没有人能对他的逻辑提出异议——从真正理解代码出发,确实更能保证修正的正确性。然而,许多内核开发者们还是希望有一个官方发布的,用于内核的调试器。因为这个要求看起来不会马上被满足,所以许多补丁应运而生了,它们为标准内核附加上了内核调试的支持。虽然这都是一些不被官方认可的附加布丁,但它们确实功能完善,十分强大。在我们深入这些解决方案之前,让我们先看看标准的
Linux
调试器
gdb
能够给我们一些什么帮助。
gdb
你可以使用标准的
GNU
调试器对正在运行的内核进行察看。针对内核启动调试器的方法与针对进程的方法大致相同:
gdb vmlinux /proc/kcore
其中
vmlinux
文件是未经压缩的内核映像,不是压缩过的
zImage
或
bzImage
,它存放在源代码树的根目录上。
/proc/kcore
作为一个参数选项,是作为
core
文件来用的,通过它你能够访问到内核驻留的高端内存。只有超级用户才能读取此文件的数据。
你可以使用
gdb
的所有命令来获取信息。举个例子,为了打印一个变量的值,你可以:
p gloable_variable
反汇编一个函数
disassemble function
如果你编译内核的时候使用了
-g
参数(在内核的
Makefile
文件的
CFLAGS
变量中加入
-g
),
gdb
还可以提供更多的信息。比如,你可以打印出结构体中存放的信息或是跟踪一个指针。当然,你编译出的内核会大很多,所以不要把编译带调试信息的内核当作一种习惯。
下来,就要说不幸的那一面了,
gdb
还是有很多局限的。它没有任何办法修改内核数据。它也不能单步执行内核代码,不能加断点。不能修改内核数据是个非常大的缺陷。尽管在必要时反汇编函数无疑是个非常有用的功能,但是能够修改数据的却更为有用。
kgdb
kgdb
是一个补丁,它可以让我们在远端主机上通过串口利用
gdb
的所有功能对内核进行调试。这需要两台计算机。第一台运行带有
kgdb
补丁的内核。第二台通过串行线(不通过
modem
,直接连接两台机器的电缆)使用
gdb
对第一台进行调试。通过
kgdb
,
gdb
的所用功能都能使用:读取或修改变量值,设置断点,设置关注变量,单步执行等等。某些版本的
gdb
甚至允许执行函数。
设置
kgdb
和连接串行线比较麻烦,但是一旦做完了,调试就变得很简单了。该补丁会在
Documentation/
目录下安装很多说明文件,你可以把它们挑出来研究一下。
不同体系结构、不同内核版本使用的
kgdb
由不同的人员维护,为了给需要调试的内核找到合适的补丁,还是在网上搜索一下比较好。
kdb
kdb
是
kgdb
的一种替代品。不象
kgdb
,它不是一个远端调试器。
kdb
这个补丁对内核源代码进行了很多修改,使调试内核在本地主机上就可以进行。它提供了变量修改、设置断点、单步执行等等许多功能。运行调试器非常简单,在终端上敲一下
break
键就可以了。内核执行
oops
的时候,它也会自动投入运行。安装
kdb
之后,可以在
Documentation/kdb
中找到有关它的详细文档。
在
http://oss.sgi.com/
可以找到
kdb
补丁。
在系统中钻营打探
伴随着你调试内核的经验越来越丰富,你获得的在内核中钻营打探以获取答案的技巧也会越来越多。由于调试内核是一种复杂度非常高的考验,所以掌握好任何技巧和窍门都会有所帮助。下面就有一些:
用
UID
作为选择条件
如果你开发的是进程相关的部分,有些时候,你可以在提供替代物的同时不打破原有代码的可执行性。这在你开发重要系统调用的时候,或者在你希望进行调试时系统功能依旧健全的情况下非常有用。
举个例子,假设为了加入一个激动人心的新特性,你重写了
fork()
系统调用。除非你第一次的尝试就完美无缺,否则系统调试就是一场噩梦。如果
fork()
系统调用不正常的话,你压根就不用指望整个系统还能正常工作。当然,和任何时候一样,希望总是存在的。
一般情况下,只要保留原有的算法而把你的新算法加入到其它位置上,基本就能保证安全。你可以利用把用户
id
(
UID
)作为选择条件来实现这种功能,通过这种选择条件,你可以安排到底执行哪种算法:
if (current->uid != 7777) {
/*
老算法
.. */
} else {
/*
新算法
.. */
}
除了
7777
以外,其它所有的用户都用的是老算法。你可以创建一个
UID
为
7777
的用户,专门来测试新算法。对于要求很严格的进程相关部分的代码来说,这种方法使得测试变得容易了许多。
使用条件变量
如果代码与进程无关,或者你希望有一个针对所有情况都能使用的机制来控制某个特性,你可以使用条件变量。这比使用
UID
还来得简单。你只需要创建一个全局变量作为一个条件选择开关。如果该变量为零,你就是用一个分支上的代码。如果它不为零,你就选择另外一个分支。你可以通过某种界面提供对这个变量的操控,也可以直接通过调试器进行操控。
使用统计量
有些时候你需要掌握某个特定事件的发生规律。有些时候你需要比较多个事件并从中得出规律。通过创建统计量并提供某种机制访问其统计结果,很容易就能满足这种需求。
举个例子,假设我们希望得到
foo
和
bar
的发生频率,那么在某个文件中,当然最好是在事件所发生的那个文件里,定义两个全局变量:
unsigned long foo_stat = 0;
unsigned long bar_stat = 0;
每当事件发生的时候,就让相应的变量加
1
。然后按照你喜欢的方式提供这两个变量的访问方法。比如,你可以在
/proc
目录中创建一个文件,还可以新创建一个系统调用。最简单的办法当然还是通过调试器直接访问它们。
注意,这种实现并非是
SMP
安全的。理想的办法是通过原子操作进行实现。但是仅仅对于一个简单的每次加
1
的调试统计量,一般无需搞得这么麻烦。
重复频率限制
为了发现一个错误,开发者们往往在代码的某个部分加入很多错误检查语句(多数对应的都是一些
print
语句)。在内核中,有些函数每秒都要被调用很多次。如果你在这样的函数中加入了
prink()
,那么系统马上就会被显示调试信息这一个任务压得喘不过气来,很快就什么也干不成了。
有两种相关的技巧可以用来防止此类问题的发生。第一种是
重复频率限制
,如果某种事件发生的非常频繁,而你又需要观察它的整体进展情况,你就可以让这种技巧施展身手了。为了避免调试信息发生井喷,你可以每隔几秒执行一次打印(或者是其它任何你想完成的操作)。
举个例子:
static unsigned long prev_jiffy = jiffies; /*
频率限制
*/
if (time_after(jiffies, prev_jiffy + 2*HZ)) {
prev_jiffy = jiffies;
printk(KERN_ERR “blah blah blah\n”);
}
此例中,调试信息最多两秒打印一次。这可以让你的终端不至于被汹涌而至的调试信息洪流充塞,也保证你的系统依旧能用。你完全可以根据自己的需要,或低或高的调整这种重复频率。
在你观察某种频繁发生的时间时,还有另一种棘手的问题。与前面的例子不同,你想观察的不是整个事件在内核中进展过程,你只是想在某个事件发生时得到一个通知。可能只要得到一次或是两次就足够了。如果这种通知在被触发一次之后依旧不停的到来,那就比较麻烦了。下面这种技巧针对的就不再是如何限制重复频率了,它要实现的是
发生次数限制
。
s
tatic unsigned long limit = 0;
i
f (limit < 5) {
l
imit ++;
p
rintk(KERN_ERR “blah blah blah\n”);
}
此例中,调试信息输出五次就封顶了。五次之后,打印条件总是不能成立。
不管是哪种技巧,用到的变量都应该是静态的(
static
),并且应该限制在函数的局部以内。像例子中展示的那样,这样才能保证在函数的多次调用中变量的值能够保留。
这些例子的代码都不是
SMP
或抢占来安全的,不过,只需要用原子操作改造一下就没问题了。可是,这只不过是在调试中才会用到的代码,没有必要搞得那么复杂吧?
二分法查找引发罪恶的变更
知道
bug
是什么时候被引入内核源代码的通常都是很有用的。如果你知道
2.4.18
中出现了一个
bug
,而能肯定
2.4.17
中没有,那么你就能够很容易的对引发这个
bug
的代码变更进行定位。消灭
bug
变得唾手可得,要么取消这个变更,要么对其进行修正。
可是,很多时候你并不知道到底是哪个内核版本引入了
bug
。你知道
当前
版本里
bug
是确确实实存在的,不过,它好像就是存在于当前版本中。这就需要做一些调查取证了,不过只需要花一点点力气,你就能找出引发问题的代码变更了。元凶在手,消灭
bug
就指日可待了。
一开始,你需要一个可靠的可复制的错误。最好是一个系统一启动你就能查证的
bug
。下来,你需要一个能确保没问题的内核。你应该能够找到。举个例子,你知道几个月前的内核没有这种错误,那么就从那时使用的内核中选取一个。如果发现问题那时就存在了,那么就找更早的。这不会太难——除非
bug
是
Linux
与生俱来的——一定要找到不含
bug
的内核。
下来需要一个肯定有问题的内核。为了简单起见,你应该从已知最早出现该问题的内核开始。
现在,你就可以在问题内核和良好的内核之间使用二分法了。举个例子,假定确保没有问题的内核版本是
2.4.11
,有问题的内核版本是
2.4.20
。从二者的正中选取一个内核版本,像
2.4.15
。检查
2.4.15
是否包含此
bug
。如果
2.4.15
没有问题,那么你就知道错误是发生在此版本之后了。所以,再从
2.4.15
开始,在它和
2.4.20
正中选取下一个版本,比如说
2.4.17
进行检查。如果
2.4.15
有问题,那么错误就可能发生在此版本之前了,那么你就该选
2.4.13
作为下一个待查目标了。就这样重复筛选。
最终你肯定能把问题局限在两个版本之间——一个包含错误而另外一个不包含。你就能够很容易的对引发这个
bug
的代码变更进行定位了。
这种方式比依次对每个版本的内核进行核查要好得多了。
当所有的努力都失败时:社区
或许你已经做完了所有你能想到的尝试。你在键盘上呕心沥血了几个小时——实际上,可能是无数日子——答案依旧没有眷顾你。此时,如果
bug
是在
Linux
内核的主流部分中,你可以在内核开发社区中寻求其他开发者的帮助。
你应该向内核邮件列表发送一份电子邮件,对
bug
进行完整而又简洁的描述,你的发现可能会对找到最终的答案起到帮助作用。毕竟,没人希望
bug
存在。
在后续,“补丁,开发和社区”会重点推荐社区和它最重要的论坛,
Linux
内核邮件列表(
LKML
)。
[
本帖最后由 陈莉君 于 2008-5-12 14:35 编辑
]
透析真谛,手中满握春风;共享智慧,如春风沐浴
http://www.kerneltravel.net
[广告]
推荐个超酷的web2.0相册
投票
交易
悬赏
活动
LUPA论坛
专题指导
> 内核问题答疑
> FreeBSD专版
LUPA论坛
> 开源思想交流
> 人物专栏
> LUPA足迹
> 推进员之家
> 网页标准化
> 投稿区
> 技术交流
> 初级问答[新手区]
> 有奖评书专区
> 社区茶馆
> 美景美图
> 创业就业
> 游戏专版
> IT界评论
> 技术文档
> Linux基础
> 跨平台应用
> ErLang专区
> 软件应用
> LAMP专区
> Shell编程
> JAVA
> 高级应用
> PHP
> 邮件服务器
> 嵌入式开发
> 数据库
> FTP技术
> 网络安全
> Solaris专区
> 其他Unix系列
> windows平台开源软件介绍
> 其他编程语言
> 高校教学认证专版
> 认证公告和教学指导
> 技术支持
> 操作员认证专题
> 网管员认证专题
> LAMP工程师认证专题
> 社区管理
> 社区活动
> LUPA基金会
> 开源社区广告同盟
> 人员调整公告
> 回收垃圾
Linux平台开发专版
> C/C++语言基础
> 开发工具使用
> GTK/QT图形库
> 开发包调用
> 软件包制作
合作专区
> 开源项目合作建设
> X-Vake威客系统
> Serious Game底层引擎
> 蓝迪游戏
> ExtMail
> WiseReal教育软件
> Works.lt信息化平台
> LGsearch桌面搜索
> FireFox插件开发
> LUPA考试系统
> Linuxer电子杂志
> Easyjf专版(简易JAVA框架)
> 恩信ERP
> 希瑞CRM
> Zen Cart购物车
> Klinux 发行版定制
当前时区 GMT+8, 现在时间是 2008-7-24 16:00
浙ICP备06002895号
Powered by
Discuz!
5.0.0
© 2001-2006
Comsenz Inc.
Processed in 5.164771 second(s), 6 queries , Gzip enabled
TOP
清除 Cookies
-
联系我们
-
LUPA开源社区
-
Archiver
-
WAP
控制面板首页
编辑个人资料
积分交易
公众用户组
好友列表
基本概况
流量统计
客户软件
发帖量记录
论坛排行
主题排行
发帖排行
积分排行
在线时间
管理团队
管理统计