转载:Linux内核调试方法
转载文章请注明作者和二维码及全文信息。
转自:http://blog.csdn.net/swingwang/article/details/72331196
不会编程的程序员,不是好的架构师,编程和内核调试也是出色架构师的必修课。谈起编程人员的数量,基于Linux平台的软件工程师肯定是最多的,没有之一。那今天我们就以Linux为例,深入讲一下内核模块和内核的调试技术和调试工具KGDB。
1 KGDB背景
KGDB是在内核2.6.26版本中正式支持的,对应发行版即SLES11及以上、RHEL6及以上,在此之前的内核版本由Linsyssoft Technologies公司提供补丁以支持KGDB,但并不是所有内核版本都有补丁可用,同时打补丁操作也比较繁琐且问题多多,因此可用性不高。
2 调试环境搭建
注:以下称 “被调试的主机”为目标机,运行gdb进行调试的主机为开发机
2.1 目标机配置
2.1.1 配置串口
物理机串口根据实际环境要求配置,虚拟机按如下方式配置,pipe名字可以修改,但要保证和开发机一致:
2.1.2 更新内核以支持kgdb
注:本文以SLES11SP1作为目标机为例,内核源码直接安装RPM包就可以使用,RHEL要稍微麻烦一些,需要下载源码包,进行编译后进行安装。
更新内核前准备
加入调试信息后内核及各个ko的体积会增大数倍,因此编译内核前一定要确认磁盘有7G以上剩余空间(保险起见建议预留10G),执行make后源码目录空间占用超过5G。
执行make modules_install后/lib/modules目录还要占用1.4G
SLES系列默认内核源码目录是/usr/src/linux-xxx/,但由于试验用的虚拟机创建时磁盘选择默认大小只有8G,因此额外创建了一块20G的磁盘挂载到/home目录作为内核编译目录,可直接将目录usr/src/linux-xxx/拷贝到/home/linux-xxx/不影响编译。
更新内核步骤
1、执行uname –r确认当前运行内核的类型,拷贝/boot/目录下对应内核类型的config文件到内核源码目录并重命名为.config;大多数情况下编译内核后启动失败都是因为内核配置不当,因此最好在系统原有配置文件基础上修改。
2、在内核源码目录执行make menuconfig进行内核配置;
进入Kernel hacking子选项,确认激活以下项目:
[*]Compile the kernel with debug info
[*]Compile the kernel with frame pointers
[*]KGDB: kernel debugging with remote gdb
清除 Write protect kernel read-only data structures选项;此项默认是激活的,会导致后续使用gdb调试时无法加断点;
在SLES11SP1上去掉Write protect kernel read-only data structures后编译会出错,原因是函数mark_rodata_ro在init/main.c和cacheflush.h中重复定义了
解决办法是注掉main.c中的定义:
3、执行make all编译内核;(耗时约1小时,可使用make –j x all加快编译速度,x表示线程数)
4、安装模块,编译完成后,新生成的模块ko还在源码目录,并未更新到/lib/module/对应目录:
注意,在安装模块前强烈建议备份原模块目录,以便调试完成后或新编译模块有问题时恢复环境,如下。
执行make modules_install(注意:不是make modules install)将拷贝ko到/lib/module/
5、创建启动内核及initrd
注:依然强烈建议先备份/boot/目录下的原vmlinuz和initrd文件,因为虽然内核install脚本会自动备份,但如果install执行两次或以上,则之前的备份会被新备份覆盖。
设置/etc/modprobe.d/unsupported-modules中allow_unsupported_modules为1,否则新编译生成的模块ko可能无法加载:
执行make install,将会拷贝源码目录下的vmlinux到/boot/目录并压缩为vmlinuz,并创建initrd:
6、为KGDB内核创建新的启动项
注:继续强烈建议先备份原始启动项,将原始启动项使用的内核和initrd文件指定为之前备份的文件:
新增的KGDB启动项,与原始启动项相比只增加了一个参数:kgdboc=ttyS0,115200
如果需要目标机一启动就断住(比如要调试启动阶段的代码),则再增加一个参数kgdbwait
7、重启目标机,以KGDB选项启动
2.2 开发机配置
开发机不需要和目标机硬件或内核相同,只要上面装的gdb版本满足kgdb的要求就可以。本文使用一个SLES10SP4的32位虚拟机作为开发机。
2.2.1 配置串口
物理机串口根据实际环境要求配置,虚拟机按如下方式配置:
检查参数,确认串口配置正确:
1、 在目标机执行cat /dev/ttyS0;
2、 在开发机执行echo test > /dev/ttyS0
3、 观察目标机是否打印test字样;
2.2.2 准备调试代码和目标二进制文件
调试代码
由于gdb调试需要源码文件,因此需要把内核源码拷贝到开发机。建议在目标机编译前把整个源码目录拷贝到开发机,否则编译后整个源码目录体积太大。
目标二进制文件
目标二进制文件就是要调试的文件,如vmlinux或xxx.ko,直接把目标机上编译好的文件拷贝到开发机,建议放在内核源码目录下。
3 调试步骤
3.1调试内核vmlinux
以调试函数block层的函数get_request_wait为例
1、 在目标机执行echo g > /proc/sysrq-trigger,会触发目标机挂起以等待开发机输入;
2、 在开发机启动gdb:
3、 设置启动远程调试
在gdb界面输入以下两条命令,成功的话会显示断在kgdb_breakpoint函数:
set remotebaud 115200
target remote /dev/ttyS0
4、 输入b get_request_wait为我们想调试的函数设置断点(b表示breakpoint),然后执行c(continue)让目标机继续运行直到断点;
5、 查看调用栈(bt)和单步调试(n)都是比较有用的手段;
查看函数get_request_wait的调用栈:
单步调试:
下图例子中代码执行到rq = get_request(q, rw_flags, bio, GFP_NOIO);这行前;
执行p rq打印指针变量rq的地址显示value optimized out表示为空;
执行p *rq打印指针变量rq的内容显示无法访问0x0地址;
执行n让rq = get_request(q, rw_flags, bio, GFP_NOIO);执行完;
再次执行p rq成功打印出指针变量rq的地址;
执行p *rq成功打印出指针变量rq的内容;
6、 调试完成后清除断点让目标机恢复正常运行;
执行info b查看当前断点;
执行d breakpoint 1清除断点1;
执行c让目标机恢复运行;
目标机之前挂起后网络就中断了,此时恢复后又可重新登录:
3.2 调试模块KO
以调试模块scsi_mod.ko为例:
1、先在目标机上查看模块在内核中的偏移地址,然后挂起目标机:
2、在开发机启动gdb,并执行add-symbol-file [模块ko] [内核地址]加载模块ko文件:
之后的步骤同调试内核vmlinux一样:启动远程调试、设置断点…
4 总结
使用KGDB,一方面可以帮助阅读内核代码,实际观察代码执行的流程;另一方面可以帮助非自研模块相关流程的问题定位,不需要反复添加打印重编内核,提高问题定位效率。本文重点描述了KGDB环境搭建及启动调试的步骤,更多gdb调试技巧请参考gdb手册。
环境搭建重点在于更新内核,这块也是整个过程中最耗时和容易出错的,项目组可以组织分工进行各个版本、类型内核的KGDB更新(如SLES11 32位/64位、RHEL等等)并保存,后续使用时可以直接拷贝。请搜索“ICT_Architect”加入微信公众号“架构师技术联盟”获取更多精彩内容。
转载:Linux内核调试方法的更多相关文章
- Linux内核调试方法总结
Linux内核调试方法总结 一 调试前的准备 二 内核中的bug 三 内核调试配置选项 1 内核配置 2 调试原子操作 四 引发bug并打印信息 1 BUG()和BUG_ON() 2 ...
- Linux内核调试方法总结【转】
转自:http://my.oschina.net/fgq611/blog/113249 内核开发比用户空间开发更难的一个因素就是内核调试艰难.内核错误往往会导致系统宕机,很难保留出错时的现场.调试内核 ...
- 【转】Linux内核调试方法总结
目录[-] 一 调试前的准备 二 内核中的bug 三 内核调试配置选项 1 内核配置 2 调试原子操作 四 引发bug并打印信息 1 BUG()和BUG_ON() 2 dump_sta ...
- Linux内核调试方法【转】
转自:http://www.cnblogs.com/shineshqw/articles/2359114.html kdb:只能在汇编代码级进行调试: 优点是不需要两台机器进行调试. gdb:在调试模 ...
- Linux内核调试方法总结之反汇编
Linux反汇编调试方法 Linux内核模块或者应用程序经常因为各种各样的原因而崩溃,一般情况下都会打印函数调用栈信息,那么,这种情况下,我们怎么去定位问题呢?本文档介绍了一种反汇编的方法辅助定位此类 ...
- Linux内核调试方法总结之栈帧
栈帧 栈帧和指针可以说是C语言的精髓.栈帧是一种特殊的数据结构,在C语言函数调用时,栈帧用来保存当前函数的父一级函数的栈底指针,当前函数的局部变量以及被调用函数返回后下一条汇编指令的地址.如下图所示: ...
- Linux内核调试方法总结之序言
本系列主要介绍Linux内核死机.异常重启类稳定性问题的调试方法. 在Linux系统中,一切皆为文件,而系统运行的载体,是一类特殊的文件,即进程.因此,我尝试从进程的角度分析Linux内核的死机.异常 ...
- Linux内核调试方法总结之ddebug
[用途] Linux内核动态调试特性,适用于驱动和内核各子系统调试.动态调试的主要功能就是允许你动态的打开或者关闭内核代码中的各种提示信息.适用于驱动和内核线程功能调试. [使用方法] 依赖于CONF ...
- Linux内核调试方法总结之调试宏
本文介绍的内核调试宏属于静态调试方法,通过调试宏主动触发oops从而打印出函数调用栈信息. 1) BUG_ON 查看bug处堆栈内容,主动制造oops Linux中BUG_ON,WARN_ON用于调试 ...
随机推荐
- iftop、ifstat详解
ifstat 介绍 ifstat工具是个网络接口监测工具,比较简单看网络流量 实例 默认使用 #ifstat eth0 eth1 KB/s in KB/s out KB/s in KB/s out 0 ...
- v$Session详解
从Oracle10gR1开始,Oracle在V$SESSION中增加关于等待事件的字段,实际上也就是把原来V$SESSION_WAIT视图中的所有字段全部整合到了V$SESSION视图中,开始的时候我 ...
- VC++:制作一个控件注册的小工具
在平时的工作中,时常需要注册与反注册ActiveX控件,有时需要判断控件是否已经注册. 所以通过查找资料编写了一个控件注册的小工具,欢迎学习交流,不当之处请多多交流. 先直接上图: 主要代码: ...
- VMWare虚拟机提示:打不开磁盘…或它所依赖的某个快照磁盘,开启模块DiskEarly的操作失败,未能启动虚拟机
将电脑上存在的虚拟机复制一份后打开运行,弹出错误提示: 打不开磁盘…或它所依赖的某个快照磁盘,开启模块DiskEarly的操作失败,未能启动虚拟机. 解决方法如下: 打开存放虚拟机系统硬盘的所在文件夹 ...
- CentOS下yum安装PHP,配置php-fpm服务
yum list installed | grep php 先删除已有的php版本 ,执行下面的命令删除php yum remove php-common 然后像安装那样问你是否继续的,输入yes即可 ...
- mysql中,root用户密码被遗忘,该如何进行重置?
需求描述: 在mysql的测试环境中,有时候会遇到一段时间之后root用户的密码被遗忘的情况, 这个时候,就是需要对root密码进行重置,不过,在生产环境中,这种情况还是很少见. 环境描述: 操作系统 ...
- 【java】 java 中stop方法终止线程的不良后果
stop()方法属于暴力终止线程的方法,有诸多隐患已经被废弃了. 下面演示的是stop方法会释放锁,造成数据不一致的问题. package com.xwolf.java.thread; /** * C ...
- MFC 小知识总结五
1 移动无标题对话框 响应WM_NCHITTEST 消息 [cpp] view plaincopy LRESULT CTimeJishiDlg::OnNcHitTest(CPoint point) ...
- laravel 使用验证码
1)php.ini需要开两个扩展 extension=php_fileinfo.dllextension=php_gd2.dll 2)使用composer安装类包 composer require m ...
- 【渗透测试学习平台】 web for pentester -8.XML
example1: http://192.168.91.139/xml/example1.php?xml=%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%2 ...