在前文,我们介绍了如何创建自己的启动镜像,并在屏幕输出了"Hello bochs!"打印,本文将以这个启动镜像为对象,介绍如何使用bochs提供的调试功能,本文将介绍如何使用命令行调试功能。
bochs提供的调试功能主要分为执行控制、断点功能、内存监测、内存操作、信息查询、CPU寄存器操作、反汇编、指令跟踪等,本文的示例将会介绍最简单和常用的一些命令功能,调试功能的具体命令可以参考这里,更多细节请参考官网。
跟踪调测我们的程序
首先,启动我们的程序,可以参考创建可使用的镜像,使用bochs -f bochsrc
启动后如下,选择6进入调试界面:
$ bochs -f bochsrc
========================================================================
Bochs x86 Emulator 2.7
Built from SVN snapshot on August 1, 2021
Timestamp: Sun Aug 1 10:07:00 CEST 2021
========================================================================
00000000000i[ ] BXSHARE not set. using compile time default '/usr/local/share/bochs'
00000000000i[ ] reading configuration from bochsrc
------------------------------
Bochs Configuration: Main Menu
------------------------------
This is the Bochs Configuration Interface, where you can describe the
machine that you want to simulate. Bochs has already searched for a
configuration file (typically called bochsrc.txt) and loaded it if it
could be found. When you are satisfied with the configuration, go
ahead and start the simulation.
You can also start bochs with the -q option to skip these menus.
1. Restore factory default configuration
2. Read options from...
3. Edit options
4. Save options to...
5. Restore the Bochs state from...
6. Begin simulation
7. Quit now
Please choose one: [6] 6
00000000000i[ ] installing x module as the Bochs GUI
00000000000i[ ] using log file bochsout.txt
Next at t=0
(0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0
<bochs:1>
程序此时会停留,等待我们进一步的指令,我们来稍微看一下输出的信息,注意到这样一行[0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0
,这个也就是BIOS启动引导分区过程简介中提到的,上电后BIOS会加载到0xf000:0xfff0处,然后处理器跳到CS:IP=0xf000:fff0处执行BIOS代码。我们可以使用sreg
命令查看一下段寄存器,使用reg
查看一下普通寄存器的内容,结果如下:
Please choose one: [6] 6
00000000000i[ ] installing x module as the Bochs GUI
00000000000i[ ] using log file bochsout.txt
Next at t=0
(0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0
<bochs:1> sreg
es:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
cs:0xf000, dh=0xff0093ff, dl=0x0000ffff, valid=7
Data segment, base=0xffff0000, limit=0x0000ffff, Read/Write, Accessed
ss:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ds:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
fs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
gs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1
tr:0x0000, dh=0x00008b00, dl=0x0000ffff, valid=1
gdtr:base=0x00000000, limit=0xffff
idtr:base=0x00000000, limit=0xffff
<bochs:2> reg
eax: 0x00000000 0
ebx: 0x00000000 0
ecx: 0x00000000 0
edx: 0x00000000 0
esp: 0x00000000 0
ebp: 0x00000000 0
esi: 0x00000000 0
edi: 0x00000000 0
eip: 0x0000fff0
eflags 0x00000002: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf af pf cf
<bochs:3>
可以看到cs:0xf000
,eip: 0x0000fff0
,与理论过程完全一致。此时,还并未执行到我们编写的程序,为了让程序在进入我们编写的Hello bochs!
(如何编写参考这里)时停止,我们需要打一个断点,从BIOS启动引导分区过程简介我们知道,BIOS会把我们编写的启动扇区加载到0x7c00地址处执行,因此我们在此处打一个断点,命令为b 0x7c00
,执行完后可以使用命令info break
查看目前有哪些断点:
<bochs:3> b 0x7c00
<bochs:4> info break
Num Type Disp Enb Address
1 pbreakpoint keep y 0x000000007c00
<bochs:5>
可以看到,成功的在0x7c00处打了断点,预期程序会在执行到该地址时停止,此时需要我们让程序继续执行下去指导断点停止,输入c
(意为continue)继续执行程序直到遇到一个断点。
<bochs:5> c
(0) Breakpoint 1, 0x00007c00 in ?? ()
Next at t=47628826
(0) [0x000000007c00] 0000:7c00 (unk. ctxt): mov ax, cs ; 8cc8
<bochs:6>
如预期,程序在0x7c00处停了下来,说明断点生效了,并且可以看到此处的代码是mov ax, cs
,也正是我们编写的Hello bochs!
程序的第一行代码。可以执行reg
和sreg
看一下寄存器的情况,可以看到BIOS执行完后,程序跳到了CS:IP=0x0000:0x7c00位置执行:
<bochs:6> reg
eax: 0x0000aa55 43605
ebx: 0x00000000 0
ecx: 0x00090000 589824
edx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504
edi: 0x0000ffac 65452
eip: 0x00007c00
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
<bochs:7> sreg
es:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
cs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ss:0x0000, dh=0x00009300, dl=0x0000ffff, valid=7
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ds:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
fs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
gs:0x0000, dh=0x00009300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0x0000ffff, Read/Write, Accessed
ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1
tr:0x0000, dh=0x00008b00, dl=0x0000ffff, valid=1
gdtr:base=0x000f9ad7, limit=0x30
idtr:base=0x00000000, limit=0x3ff
<bochs:8>
bochs有个细节做的很好,就是每一次暂停程序执行等待用户输入调试指令,都会在<bochs: 数字>提示符中显示一个递增的数字,表示当前是多少次暂停了。
使用n
(next)可以继续执行:
<bochs:10> n
Next at t=47628827
(0) [0x000000007c02] 0000:7c02 (unk. ctxt): mov ds, ax ; 8ed8
<bochs:11>
可以使用x
或xp
来查看对应地址(物理地址)的内容:
<bochs:15> xp /16bx 0x7c00
[bochs]:
0x00007c00 <bogus+ 0>: 0x8c 0xc8 0x8e 0xd8 0x8e 0xc0 0xbd 0x1b
0x00007c08 <bogus+ 8>: 0x7c 0xb9 0x0c 0x00 0xb4 0x13 0xb0 0x01
<bochs:16>
使用exit
退出调试。