使用 KGDB 调试 Kernel On Red Hat Linux
1. KGDB 简介
KGDB 提供了一种使用 GDB 调试 Linux 内核的机制。使用 KGDB 可以象调试普通的应用程序那样,在内核中进行设置断点、检查变量值、单步跟踪程序运行等操作。使用 KGDB 调试时需要两台机器,一台作为开发机(Development Machine),另一台作为目标机(Target Machine),两台机器之间通过串口或者以太网口相连。串口连接线是一根RS-232接口的电缆,在其内部两端的第2脚(TXD)与第3脚(RXD)交叉相连,第7脚(接地脚)直接相连。调试过程中,被调试的内核运行在目标机上,GDB 调试器运行在开发机上。
目前, KGDB 发布支持i386、x86_64、32-bit PPC、SPARC等几种体系结构的调试器。
2. KGDB 调试原理
安装 KGDB 调试环境需要为Linux内核应用 KGDB 补丁,补丁实现的gdb远程调试所需要的功能包括命令处理、陷阱处理及串口通讯3个主要的部分。 KGDB 补丁的主要作用是在Linux内核中添加了一个调试Stub。调试Stub是Linux内核中的一小段代码,提供了运行gdb的开发机和所调试内核之间的一个媒介。gdb和调试stub之间通过gdb串行协议进行通讯。gdb串行协议是一种基于消息的ASCII码协议,包含了各种调试命令。当设置断点时, KGDB 负责在设置断点的指令前增加一条trap指令,当执行到断点时控制权就转移到调试stub中去。此时,调试stub的任务就是使用远程串行通信协议将当前环境传送给gdb,然后从gdb处接受命令。gdb命令告诉stub下一步该做什么,当stub收到继续执行的命令时,将恢复程序的运行环境,把对CPU的控制权重新交还给内核。
3. KGDB 安装与配置
下面我们将以Linux 2.6.18 内核为例详细介绍 KGDB 调试环境的建立过程。
3.1 调试环境准备
3.1.1 通过物理串口线进行调试
环境要求:两台运行相同内核版本的Linux系统的设备
3.1.2 通过网络接口进行调试
环境要求:两台运行相同内核版本的Linux系统的设备
KGDB 也支持使用以太网接口作为调试器的连接端口。在对Linux内核应用补丁包时,需应用eth.patch补丁文件。配置内核时在Kernel hacking中选择 KGDB 调试项,配置 KGDB 调试端口为以太网接口,例如:
[*] KGDB : kernel debugging with remote gdb
Method for KGDB communication ( KGDB : On ethernet) --->
( ) KGDB : On generic serial port (8250)
(X) KGDB : On ethernet
另外使用eth0网口作为调试端口时,grub.list的配置如下:
title 2.6.7 KGDB
root (hd0,0)
kernel /boot/vmlinuz-2.6.7- KGDB ro root=/dev/hda1 KGDB wait KGDB oe=@192.168.5.13/,@192.168. 6.13/
其他的过程与使用串口作为连接端口时的设置过程相同。
注意:尽管可以使用以太网口作为 KGDB 的调试端口,使用串口作为连接端口更加简单易行, KGDB 项目组推荐使用串口作为调试端口。
3.1.3 通过 VMware Workstation 搭建调试环境
环境要求:两台运行相同内核版本的Linux系统虚拟机
虚拟机中的串口连接可以采用两种方法:一种是指定虚拟机的串口连接到实际的COM上,例如开发机连接到COM1,目标机连接到COM2,然后把两个串口通过串口线相连接。另一种更为简便的方法是:在较高一些版本的VMware中都支持把串口映射到命名管道,把两个虚拟机的串口映射到同一个命名管道。例如,在两个虚拟机中都选定同一个命名管道 \\.\pipe\com_1,指定Target机的COM口为server端,并选择"The other end is a virtual machine"属性;指定 Development 机的COM口端为client端,同样指定COM口的"The other end is a virtual machine"属性。对于IO mode属性,在Target上选中"Yield CPU on poll"复选择框,Development 机不选。这样,可以无需附加任何硬件,利用虚拟机就可以搭建 KGDB 调试环境。 即降低了使用 KGDB 进行调试的硬件要求,也简化了建立调试环境的过程。
由于通过 VMware Workstation 搭建调试环境最容易实现,而且操作起来简单,所以以该方式来说明 KGDB 安装与配置全过程。
Development 虚拟机串口配置:
Target 虚拟机串口配置:
测试两台机器之间串口连接情况,stty命令可以对串口COM1参数进行设置(注意上面使用的是com_1,串口设备对应的是ttyS1):
Development 虚拟机:
[root@localhost ~]# stty ispeed 115200 ospeed 115200 -F /dev/ttyS1
Target 虚拟机:
[root@BendSha_RHEL5_5_x64 ~]# stty ispeed 115200 ospeed 115200 -F /dev/ttyS1
Development 虚拟机通过串口COM1发送消息:
[root@localhost ~]# echo "this message come from bendsha">/dev/ttyS1
[root@localhost ~]# echo "hoho">/dev/ttyS1
Target 虚拟机通过串口COM1接收消息:
[root@BendSha_RHEL5_5_x64 ~]# cat /dev/ttyS1
this message come from bendsha
hoho
串口测试连接成功。
3.2 安装与配置
下面我们需要应用 KGDB 补丁到Linux内核,设置内核选项并编译内核。这方面的资料相对较少,笔者这里给出详细的介绍。下面的工作在开发机(Development )上进行,以上面介绍的试验环境为例,某些具体步骤在实际的环境中可能要做适当的改动。
3.2.1 内核的配置与编译
下载内核源码
[root@localhost ~]# wget ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.tar.bz2
--2016-11-07 07:18:03-- ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.tar.bz2
=> `linux-2.6.32.tar.bz2'
Resolving ftp.kernel.org... 199.204.44.194, 198.145.20.140, 149.20.4.69
Connecting to ftp.kernel.org|199.204.44.194|:21... connected.
Logging in as anonymous ... Logged in!
==> SYST ... done. ==> PWD ... done.
==> TYPE I ... done. ==> CWD /pub/linux/kernel/v2.6 ... done.
==> SIZE linux-2.6.32.tar.bz2 ... 64424138
==> PASV ... done. ==> RETR linux-2.6.32.tar.bz2 ... done.
Length: 64424138 (61M)
0% [ ] 172,280 18.6K/s eta 54m 6s
配置内核,File System 的配置(因为后期要调试文件系统,所以勾选了如图所示的几个配置):
[root@localhost linux-2.6.32]# make menuconfig
scripts/kconfig/mconf arch/x86/Kconfig
Kernel Hacking 的配置:
保存退出。
编译内核:
[root@localhost linux-2.6.32]# make -j10 bzImage
编译内核模块:
[root@localhost linux-2.6.32]# make modules
将 Development 系统中的 linux-2.6.32 整个目录同步到 Target :
[root@localhost src]# scp -r linux-2.6.32 192.168.117.129:/usr/src
The authenticity of host '192.168.117.129 (192.168.117.129)' can't be established.
RSA key fingerprint is 63:6c:22:cf:a8:55:a9:5a:ea:72:23:1d:56:5f:28:a0.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.117.129' (RSA) to the list of known hosts.
root@192.168.117.129's password:
备注:如果原系统已经是2.6.32的内核,可以只拷贝arch/i386/boot/bzImage,及System.map文件到Target上,然后修改/boot/grub/grub.conf,但由于我是从2.6.18 升级上来,所以需要将整个linux原代码目录拷贝到Target上进行升级。
升级 Target 的内核:
[root@BendSha_RHEL5_5_x64 src]# ln -s linux-2.6.36 linux
[root@BendSha_RHEL5_5_x64 src]# cd linux
[root@BendSha_RHEL5_5_x64 linux]# make modules_install
......
INSTALL /lib/firmware/keyspan_pda/xircom_pgs.fw
DEPMOD 2.6.32
[root@BendSha_RHEL5_5_x64 linux]# make install
sh /usr/src/linux-2.6.32/arch/x86/boot/install.sh 2.6.32 arch/x86/boot/bzImage \
System.map "/boot"
[root@BendSha_RHEL5_5_x64 linux]#
安装完成后,在/boot/目录下会有一个新添加的文件:
[root@BendSha_RHEL5_5_x64 linux]# cd /boot/
[root@BendSha_RHEL5_5_x64 boot]# ls
config-2.6.18-194.el5 lost+found System.map-2.6.32
grub message vmlinuz
initrd-2.6.18-194.el5.img symvers-2.6.18-194.el5.gz vmlinuz-2.6.18-194.el5
initrd-2.6.18-194.el5kdump.img System.map vmlinuz-2.6.32
initrd-2.6.32.img System.map-2.6.18-194.el5
/boot/grub/grub.conf文件也会添加一个新的启动项
[root@BendSha_RHEL5_5_x64 boot]# cat grub/grub.conf
# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE: You have a /boot partition. This means that
# all kernel and initrd paths are relative to /boot/, eg.
# root (hd0,0)
# kernel /vmlinuz-version ro root=/dev/sda2
# initrd /initrd-version.img
#boot=/dev/sda
default=1
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.32)
root (hd0,0)
kernel /vmlinuz-2.6.32 ro root=LABEL=/ crashkernel=128M@16M
initrd /initrd-2.6.32.img
title Red Hat Enterprise Linux Server (2.6.18-194.el5)
root (hd0,0)
kernel /vmlinuz-2.6.18-194.el5 ro root=LABEL=/ crashkernel=128M@16M
initrd /initrd-2.6.18-194.el5.img
注意里面的NOTICE说明,我的系统/boot是一个独立的分区,所以下面配置的文件路径都是相对于/boot/目录的,像 /vmlinuz-2.6.32,实际到它的绝对位置应该是/boot/vmlinuz-2.6.32。在你的系统上请根据实际情况处理。
修改 grub.conf 文件:
[root@BendSha_RHEL5_5_x64 grub]# vi grub.conf
# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE: You have a /boot partition. This means that
# all kernel and initrd paths are relative to /boot/, eg.
# root (hd0,0)
# kernel /vmlinuz-version ro root=/dev/sda2
# initrd /initrd-version.img
#boot=/dev/sda
default=1
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.32)
root (hd0,0)
kernel /vmlinuz-2.6.32 ro root=LABEL=/ crashkernel=128M@16M kgdboc=ttyS1,115200
initrd /initrd-2.6.32.img
title CentOS (2.6.32 kernel debug)
root (hd0,0)
kernel /vmlinuz-2.6.32 ro root=LABEL=/ crashkernel=128M@16M kgdboc=ttyS1,115200 kgdbwait
initrd /initrd-2.6.32.img
title Red Hat Enterprise Linux Server (2.6.18-194.el5)
root (hd0,0)
kernel /vmlinuz-2.6.18-194.el5 ro root=LABEL=/ crashkernel=128M@16M
initrd /initrd-2.6.18-194.el5.img
:
说明:
第一个启动项在原来的基础上添加了kgdb的参数kgdboc=ttyS1,115200
kgdboc 的意思是 kgdb over console,这里将kgdb连接的console设置为ttyS1,波特率为115200,如果不在内核启动项中配置该参数,可以在进入系统后执行命令:echo ttyS1 > /sys/module/kgdboc/parameters/kgdboc
第二个启动项同第一个,使用同一个内核,只是添加了kgdbwait参数,kgdbwait 使 kernel 在启动过程中等待 gdb 的连接。
最终启动菜单如下:
CentOS (2.6.32)
CentOS (2.6.32 kernel debug)
Red Hat Enterprise Linux Server (2.6.18-194.el5)
调用内核模块,就选择第一个,如果要调试内核启动过程,选择第二个.
3.2.2 内核调试
重启Target,通过启动菜单第一项CentOS(2.6.32)进入系统:
进入Development 系统,开始调试:
[root@localhost src]# cd linux-2.6.32
[root@localhost linux-2.6.32]# gdb vmlinux
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-23.el5)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/src/linux-2.6.32/vmlinux...(no debugging symbols found)...done.
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyS1
Remote debugging using /dev/ttyS1
kgdb_breakpoint () at kernel/kgdb.c:1721
1721 wmb(); /* Sync point after breakpoint */
(gdb)
看到上面的内容说明已经连接成功,但 Target 系统依然是假死状态,这时你可以像使用本地gdb一样设置断点(break),单步执行(step,,或其它命令。
输入 cont,继续执行,Target 就继续下面的系统初始化了。
噢噢,出了点小问题,回头解决吧。
系统启动完成后的内核调试:
进入Target 后,执行命令:
[root@BendSha_RHEL5_5_x64 grub]# echo g > /proc/sysrq-trigger
系统同样会中断,进入假死状态,等待远程gdb的连接.KGDB可能会输出如下信息:
上面的命令(echo g > /proc/sysrq-trigger)可以有一个快捷键(ALT-SysRq-G)代替,当然前提是你编译内核时需要选中相关选项,并且需要修改配置文件:/etc/sysctl.conf ,我用了一下,不太好用,因为有的桌面系统中PrintScreen/SysRq键是用于截屏的,所以还是直接用命令来的好!。
可以在~/.bashrc中添加了一句(添加完保存后,要执行source ~/.bashrc应用该配置):
alias debug='echo g > /proc/sysrq-trigger'
之后就可以直接输入debug 来使内核进入调试状态.。
注意:有的技术文档反馈,因为vmware的named piped不能被gdb直接使用,需要使用 socat -d -d /tmp/com_1 /dev/ttyS1转换,然后使用转换后的设备。socat需要自己下载安装,但在我的环境中直接使用并没有出错.。