利用GDB和Bochs调试内核源代码
本节说明如何在现有Linux系统(例如RedHat 9)上使用Bochs模拟运行环境和gdb工具来调试Linux 0.11内核源代码。在使用这个方法之前,你的Linux系统上应该已经安装有X window系统。由于Bochs网站提供的RPM安装包中的Bochs执行程序没有编译进与gdb调试器进行通信的gdbstub模块,因此我们需要下载Bochs源代码来自行编译。
gdbstub可以使得Bochs程序在本地1234网络端口侦听接收gdb的命令,并且向gdb发送命令执行结果。从而我们可以利用gdb对Linux 0.11内核进行C语言级的调试。当然,Linux 0.11内核也需要进行使用-g选项重新编译。
14.13.1 编译带gdbstub的Bochs系统
Bochs用户手册中介绍了自行编译Bochs系统的方法。这里我们给出编译带gdbstub的Bochs系统的方法和步骤。首先从下面网站下载最新Bochs系统源代码(例如:bochs-2.2.tar.gz):
http://sourceforge.net/projects/bochs/
使用tar对软件包解压后会在当前目录中生成一个bochs-2.2子目录。进入该子目录后带选项“--enable-gdb-stub”运行配置程序configure,然后运行make和make install即可,见如下所示:
[root@plinux bochs-2.2]# ./configure --enable-gdb-stub
checking build system type... i686-pc-linux-gnu
checking host system type... i686-pc-linux-gnu
checking target system type... i686-pc-linux-gnu
...
[root@plinux bochs-2.2]# make
[root@plinux bochs-2.2]# make install
若在运行./configure时我们碰到一些问题而不能生成编译使用的Makefile文件,那么这通常是由于没有安装X window开发环境软件或相关库文件造成的。此时我们就必须先安装这些必要的软件。
14.13.2 编译带调试信息的Linux 0.11内核
通过把Bochs的模拟运行环境与gdb符号调试工具联系起来,我们既可以使用Linux 0.11系统下编译的带调试信息的内核模块来调试,也可以使用在RedHat 9环境下编译的0.11内核模块来调试。这两种环境下都需要对0.11内核源代码目录中所有Makefile文件进行修改,即在其中编译标志行上添加-g标志,并去掉链接标志行上的-s选项:
LDFLAGS = -M -x // 去掉 -s 标志。
CFLAGS =-Wall -O -g -fomit-frame-pointer \ // 添加 -g 标志。
进入内核源代码目录后,利用find命令我们可以找到以下所有需要修改的Makefile文件:
[root@plinux linux-0.11]# find ./ -name Makefile
./fs/Makefile
./kernel/Makefile
./kernel/chr_drv/Makefile
./kernel/math/Makefile
./kernel/blk_drv/Makefile
./lib/Makefile
./Makefile
./mm/Makefile
[root@plinux linux-0.11]#
另外,由于此时编译出的内核代码模块中含有调试信息,因此system模块大小可能会超过写入内核代码映像文件的默认最大值SYSSIZE = 0x3000(定义在boot/bootsect.s文件第6行)。我们可以按以下方法修改源代码根目录中的Makefile文件中产生Image文件的规则,即把内核代码模块system中的符号信息去掉后再写入Image文件中,而原始带符号信息的system模块保留用作gdb调试器使用。注意,目标的实现命令需要以一个制表符(TAB)作为一行的开始。
Image: boot/bootsect boot/setup tools/system tools/build
cp -f tools/system system.tmp
strip system.tmp
tools/build boot/bootsect boot/setup system.tmp $(ROOT_DEV) $(SWAP_DEV) > Image
rm -f system.tmp
sync
当然,我们也可以把boot/bootsect.s和tools/build.c中的SYSSIZE值修改成0x8000来处理这种情况。
14.13.3 调试方法和步骤
下面我们根据在现代Linux系统(例如RedHat 9)系统上和运行在Bochs中Linux 0.11系统上编译出的内核代码分别来说明调试方法和步骤。
1 调试现代Linux系统上编译出的Linux 0.11内核
假设我们的Linux 0.11内核源代码根目录是linux-rh9-gdb/,则我们首先在该目录中按照上面方法修改所有Makefile文件,然后在linux-rh9-gdb/目录下创建一个bochs运行配置文件并下载一个配套使用的根文件系统映像文件。我们可以直接从网站下载已经设置好的如下软件包来做实验:
http://oldlinux.org/Linux.old/bochs/linux-0.11-gdb-rh9-050619.tar.gz
使用命令“tar zxvf linux-gdb-rh9-050619.tar.gz”解开这个软件包后,可以看到其中包含以下几个文件和目录:
[root@plinux linux-gdb-rh9]# ll
total 1600
-rw-r--r-- 1 root root 18055 Jun 18 15:07 bochsrc-fd1-gdb.bxrc
drwxr-xr-x 10 root root 4096 Jun 18 22:55 linux
-rw-r--r-- 1 root root 1474560 Jun 18 20:21 rootimage-0.11-for-orig
-rwxr-xr-x 1 root root 35 Jun 18 16:54 run
[root@plinux linux--gdb-rh9]#
这里的bochs配置文件与其他Linux 0.11配置文件的主要区别是在文件头部添加有以下一行内容,表示当bochs使用这个配置文件运行时将在本地网络端口1234上侦听gdb调试器的命令:
gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0
运行这个实验的基本步骤如下:
(1).启动X window系统后打开两个终端窗口;
(2).在一个窗口中,把工作目录切换进linux-gdb-rh9/目录中,并运行程序“./run”,此时该窗口中会显示一条等待gdb来连接的信息:“Wait for gdb connection on localhost:1234”,并且系统会创建一个Bochs主窗口(此时无内容);
(3).在另一个窗口中,我们把工作目录切换到内核源代码目录中linux-gdb-rh9/linux/,并运行命令:“gdb tools/system”;
(4).在运行gdb的窗口中键入命令“break main”和“target remote localhost:1234”,此时gdb会显示已经连接到Bochs的信息;
(5).在gdb环境中再执行命令“cont”,稍过一会gdb会显示程序停止在init/main.c的main()函数处。
下面是运行gdb和在其中执行的一些命令示例。
[root@plinux linux]# gdb tools/system
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
(gdb) break main
Breakpoint 1 at 0x6621: file init/main.c, line 110.
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x0000fff0 in sys_mkdir (pathname=0x0, mode=0) at namei.c:481
481 namei.c: No such file or directory.
in namei.c
(gdb) cont
Continuing.
Breakpoint 1, main () at init/main.c:110
110 ROOT_DEV = ORIG_ROOT_DEV;
(gdb) list
105 { /* The startup routine assumes (well, ...) this */
106 /*
107 * Interrupts are still disabled. Do necessary setups, then
108 * enable them
109 */
110 ROOT_DEV = ORIG_ROOT_DEV;
111 drive_info = DRIVE_INFO;
112 memory_end = (1<<20) + (EXT_MEM_K<<10);
113 memory_end &= 0xfffff000;
114 if (memory_end > 16*1024*1024)
(gdb) next
111 drive_info = DRIVE_INFO;
(gdb) next
112 memory_end = (1<<20) + (EXT_MEM_K<<10);
(gdb) print /x ROOT_DEV
$3 = 0x21d
(gdb) quit
The program is running. Exit anyway? (y or n) y
[root@plinux linux]#