`
film
  • 浏览: 225275 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

利用GDB和Bochs调试内核源代码 中文版权所有: OldLinux论坛

 
阅读更多

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

=====================================================

14.1 利用bochs调试内核%UU
Bochs具有非常强大的操作系统内核调试功能。这也是本文选择Bochs作为首选实验环境的主要原因之一。有关Bochs调试功能的说明参见前面14.2节,这里基于Linux 0.11内核来说明Windows环境下Bochs系统调试操作的基本方法。m[2
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  8Ft
14.1.1 运行Bochs调试程序.g8O
我们假设Bochs系统已被安装在目录“C:/Program Files/Bochs-2.1.1/”中,并且Linux 0.11系统的Bochs配置文件名称是bochsrc-hd.bxrc。现在在包含内核Image文件的目录下建立一个简单的批处理文件run.bat,其内容如下:R
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  m!X;P3
"C:/Program Files/Bochs-2.1.1/bochsdbg" -q -f bochsrc-hd.bxrcjFypV+
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  HbL
其中bochsdbg是Bochs系统的调试执行程序。运行该批处理命令即可进入调试环境。此时Bochs的主显示窗口空白,而控制窗口将显示以下类似内容:D8v~
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  Y
C:/Documents and Settings/john1/桌面/Linux-0.11>"C:/Program Files/Bochs-2.1.1/boRW6RNY
chsdbg" -q -f bochsrc-hd.bxrc2
========================================================================`)2?os
Bochs x86 Emulator 2.1.13$O@R
February 08, 2004x2
========================================================================/26c3(
00000000000i[ ] reading configuration from bochsrc-hd.bxrcq4
00000000000i[ ] installing win32 module as the Bochs GUI4UW
00000000000i[ ] Warning: no rc file specified.1dsv
00000000000i[ ] using log file bochsout.txtmM
Next at t=0*
(0) context not implemented because BX_HAVE_HASH_MAP=0c
[0x000ffff0] f000:fff0 (unk. ctxt): jmp f000:e05b ; ea5be000f0g$
<bochs:1>i/
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  K.v,qO
此时Bochs调试系统已经准备好开始运行,CPU执行指针已指向ROM BIOS中地址0x000fffff0处的指令处。其中'<bochs:1>'是命令输入提示符,其中的数字表示当前的命令序列号。在命令提示符'<bochs:1>'后面键入'help'命令,可以列出调试系统的基本命令。若要了解某个命令的具体使用方法,可以键入'help'命令并且后面跟随一个用单引号括住的具体命令,例如:“help 'vbreak'”,如下面所示。V7
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  u.SQq
<bochs:1> help@n{L(
help - show list of debugger commands5&6^
help 'command'- show short command description~B
-*- Debugger control -*-e!/;l
help, q|quit|exit, set, instrument, show, trace-on, trace-off,u7J
record, playback, load-symbols, slistr2:
-*- Execution control -*-pOHjE
c|cont, s|step|stepi, p|n|next, modebpj
-*- Breakpoint management -*-|EQ
v|vbreak, lb|lbreak, pb|pbreak|b|break, sb, sba, blist,dL/rBI
bpe, bpd, d|del|deleteyR%|
-*- CPU and memory contents -*-xsMy
x, xp, u|disas|disassemble, r|reg|registers, setpmem, crc, info, dump_cpu,@|'p
set_cpu, ptime, print-stack, watch, unwatch, ?|calc7
<bochs:2> help 'vbreak'_t*<#o
help vbreak/?
vbreak seg:off - set a virtual address instruction breakpoint0
<bochs:3>flx+
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  M`poU/
为了让Bochs直接模拟执行到Linux的引导启动程序开始处,我们可以先使用断点命令在0x7c00处设置一个断点,然后让系统连续运行到0x7c00处停下来。执行的命令序列如下:.Xfk
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  [JHBv
<bochs:3> vbreak 0x0000:0x7c00hDzuWy
<bochs:4> cfMH/'X
(0) Breakpoint 1, 0x7c00 (0x0:0x7c00))W&
Next at t=4409138<-0m5
(0) [0x00007c00] 0000:7c00 (unk. ctxt): mov ax, 0x7c0 ; b8c007b 03*
<bochs:5>3R28Q
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  u[D
此时,CPU执行到boot.s程序开始处的第1条指令处,Bochs主窗口将显示出“Boot From floppy...”等一些信息。现在,我们可以利用单步执行命令's'或'n'(不跟踪进入子程序)来跟踪调试程序了。在调试时可以使用Bochs的断点设置命令、反汇编命令、信息显示命令等来辅助我们的调试操作。下面是一些常用命令的示例:J'P
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  VF
<bochs:8> u /10 # 反汇编从当前地址开始的10条指令。,S/2
00007c00: ( ): mov ax, 0x7c0 ; b8c007Wvl
00007c03: ( ): mov ds, ax ; 8ed8)1f*
00007c05: ( ): mov ax, 0x9000 ; b80090WmD1
00007c08: ( ): mov es, ax ; 8ec0(`|
00007c0a: ( ): mov cx, 0x100 ; b90001iW1|1
00007c0d: ( ): sub si, si ; 29f6ze_QZY
00007c0f: ( ): sub di, di ; 29ffZStl
00007c11: ( ): rep movs word ptr [di], word ptr [si] ; f3a5=yF
00007c13: ( ): jmp 9000:0018 ; ea18000090!s?PR9
00007c18: ( ): mov ax, cs ; 8cc8(
<bochs:9> info r # 查看当前CPU寄存器的内容aM>/D
eax 0xaa55 436057a@`_
ecx 0x110001 1114113/$3$K"
edx 0x0 00j3G?
ebx 0x0 0gE
esp 0xfffe 0xfffee7{
ebp 0x0 0x0]*lX
esi 0x0 0m
edi 0xffe4 65508TD}Zo
eip 0x7c00 0x7c00e
eflags 0x282 642Wlg
cs 0x0 0b s=]
ss 0x0 0=Z'
ds 0x0 02R;%
es 0x0 0!
fs 0x0 0BX/
gs 0x0 0D*)}n{
<bochs:10> print-stack # 显示当前堆栈的内容LN-
0000fffe [0000fffe] 0000Djz6
00010000 [00010000] 0000K/tp0'
00010002 [00010002] 00003s2
00010004 [00010004] 0000?w$!
00010006 [00010006] 0000p8WOC
00010008 [00010008] 00008j
0001000a [0001000a] 0000Iz/[
...©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  e?Rp
<bochs:11> dump_cpu # 显示CPU中的所有寄存器和状态值。}=Sj l
eax:0xaa55R
ebx:0x0Q%z,!}
ecx:0x110001392
edx:0x0B:A%z
ebp:0x0~EgKx
esi:0x0v
edi:0xffe4qDh/
esp:0xfffe}eVM4v
eflags:0x282NQ,r*
eip:0x7c00j7e
cs:s=0x0, dl=0xffff, dh=0x9b00, valid=1%
ss:s=0x0, dl=0xffff, dh=0x9300, valid=7}
ds:s=0x0, dl=0xffff, dh=0x9300, valid=1n+}s,
es:s=0x0, dl=0xffff, dh=0x9300, valid=1H
fs:s=0x0, dl=0xffff, dh=0x9300, valid=1>N
gs:s=0x0, dl=0xffff, dh=0x9300, valid=1-
ldtr:s=0x0, dl=0x0, dh=0x0, valid=03!
tr:s=0x0, dl=0x0, dh=0x0, valid=0+$QrwP
gdtr:base=0x0, limit=0x0lW
idtr:base=0x0, limit=0x3ffBp"&c'
dr0:0x00J<$
dr1:0x0,rQ
dr2:0x0q0E
dr3:0x0o$Dbi
dr6:0xffff0ff0XZZ.
dr7:0x400D
tr3:0x06
tr4:0x0^;Nq
tr5:0x007
tr6:0x0}
tr7:0x0>4
cr0:0x60000010{
cr1:0x0)o4t
cr2:0x0()
cr3:0x0|~x&,
cr4:0x0%A/
inhibit_mask:01A
done©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  y
<bochs:12>N
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  n
由于Linux 0.11内核的32位代码是从绝对物理地址0处开始存放的,因此若想直接执行到32位代码开始处,即head.s程序开始处,我们可以在线性地址0x0000处设置一个断点并运行命令'c'执行到那个位置处。/f
另外,当直接在命令提示符下打回车键时会重复执行上一个命令;按向上方向键会显示上一命令。其他命令的使用方法请参考'help'命令。i!_[&
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  ^
14.1.2 定位内核中的变量或数据结构!Q*l
在编译内核时会产生一个system.map文件。该文件列出了内核Image (bootimage)文件中全局变量和各个模块中的局部变量的偏移地址位置。在内核编译完成后可以使用前面介绍的文件导出方法把system.map文件抽取到主机环境(windows)中。有关system.map文件的详细功能和作用请参见2.10.3节。system.map样例文件中的部分内容见如下所示。利用这个文件,我们可以在Bochs调试系统中快速地定位某个变量或跳转到指定的函数代码处。Z'O}
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  N
...©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  Z{
Global symbols:hbib
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  ;-(~
_dup: 0x16e2cI^3
_nmi: 0x8e08;&&P&t
_bmap: 0xc364/fU0A
_iput: 0xc3b4~&"
_blk_dev_init: 0x10ed05l.Pk+
_open: 0x16dbcB<y~Uk
_do_execve: 0xe3d4W?Op6
_con_init: 0x15cccA.%O
_put_super: 0xd394,!P;y
_sys_setgid: 0x9b54o@5R
_sys_umask: 0x9f54/%
_con_write: 0x14f648fpr
_show_task: 0x6a54'AoeLe
_buffer_init: 0xd1ecS
_sys_settimeofday: 0x9f4ca
_sys_getgroups: 0x9edcv
...©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  (`Kv*I
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  Db
同样,由于Linux 0.11内核的32位代码是从绝对物理地址0处开始存放的,system.map中全局变量的偏移位置值就是CPU中线性地址位置,因此我们可以直接在感兴趣的变量或函数名位置处设置断点,并让程序连续执行到指定的位置处。例如若我们想调试函数buffer_init(),那么从system.map文件中可以知道它位于0xd1ec处。此时我们可以在该处设置一个线性地址断点,并执行命令'c'让CPU执行到这个指定的函数开始处,见如下所示。w|
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  $<
<bochs:12> lb 0xd1ec # 设置线性地址断点。B<p;U/
<bochs:13> c # 连续执行。@B
(0) Breakpoint 2, 0xd1ec in ?? ()H`
Next at t=16689666P{2
(0) [0x0000d1ec] 0008:0000d1ec (unk. ctxt): push ebx ; 53Hn
<bochs:14> n # 执行下一指令。guy1_*
Next at t=16689667X['$~
(0) [0x0000d1ed] 0008:0000d1ed (unk. ctxt): mov eax, dword ptr ss:[esp+0x8] ; 8b442408-/cW
<bochs:15> n # 执行下一指令。6h8o
Next at t=16689668YZp8k2
(0) [0x0000d1f1] 0008:0000d1f1 (unk. ctxt): mov edx, dword ptr [ds:0x19958] ; 8b1558990100fWR4oa
<bochs:16>uzc=MM
©OldLinux论坛 -- 有关早期Linux内核代码发展的论坛。  K^':
程序调试是一种技能,需要多练习才能熟能生巧。上面介绍的一些基本命令需要组合在一起使用才能灵活地观察到内核代码执行的整体环境情况。/

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics