上一篇文章中,我们介绍了如何创建一个简单的引导扇区,触发 BIOS 中断,从而在计算机屏幕上显示出一行我们想要的字符串。 计算机是如何启动的?如何制作自己的操作系统
那么,作为一个程序员,首先想到的问题就是,如何去调试这段汇编代码呢?怎么能够知道程序执行的每一步计算机各个寄存器中的数据是否如我们预期呢? 别急,本节我们就来详细解答。
最基本的调试方式就是反汇编,通过将二进制文件反汇编成不带有伪指令的汇编代码,可以看到每一步操作做了什么。 下面的命令将二进制程序反汇编成为 NASM 的汇编源码:
ndisasm -o 0x7c00 boot.bin >> disboot.asm
我们最为希望得到的是一个具备断点调试以及随时查看、关注变量或寄存器的值的功能的调试工具。 开源的虚拟机 bochs 就具备这些强大的调试功能: http://bochs.sourceforge.net/
你可以通过源码编译安装,也可以通过包管理工具进行安装,如果你通过包管理工具安装,需要安装 bochs、bochs-x。 同时,bochs 是一个跨平台的虚拟机,支持 windows、mac 等多个平台,甚至在安卓、IOS 等平台下也可以进行安装,这里不赘述安装过程了,windows、mac 用户可以直接官网下载可执行文件进行安装。 如果你是通过源码编译进行安装,一定要在 configure 执行时添加参数 —enable-debugger 和 —enable-disasm 用以添加调试功能。
安装好后,bochs 启动需要至少进行以下配置:
下面是我在当前最新的 2.6.11 版本所使用的一个配置:
###############################################################
# Configuration file for Bochs
###############################################################
# how much memory the emulated machine will have
megs: 32
# filename of ROM images
romimage: file=$BXSHARE/BIOS-bochs-latest
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
# what disk images will be used
floppya: 1_44=c:/Debin/Workspace/code/oranges/boot.img, status=inserted
# choose the boot disk.
boot: floppy
# where do we send log messages?
# log: bochsout.txt
# disable the mouse
mouse: enabled=0
# enable key mapping, using US layout as default.
keyboard: keymap=$BXSHARE/keymaps/x11-pc-us.map
# change mouse capture hotkey
mouse: enabled=1
mouse: type=imps2, enabled=1
mouse: type=serial, enabled=1
mouse: enabled=0, toggle=ctrl+f10
在 linux 或 mac 环境下,执行下面的命令就可以启动 bochs 了:
bochs -f bochsrc
bochsrc 是我们上面编写的配置文件。 执行后,显示出下面的界面:
输入 6,回车,就可以开始调试、运行了。
在 windows 环境下,安装路径中有两个可执行文件:bochs.exe 和 bochsdbg.exe。 顾名思义,bochs.exe 是虚拟机的直接执行文件,而 bochsdbg.exe 则用于对系统的调试。
bochs 和我们熟悉的 gdb 界面非常像,可以通过命令进行系统的调试工作。 输入 help 可以看到支持的调试命令。
上面的 seg、off、addr 都支持十六进制、十进制或八进制,但支持支下面三种格式:
所有的添加断点指令都支持条件触发,条件需要被双引号括起来并放到 if 关键字后,例如:
break 0x123cff if "ax = 0"
上述操作中,参数 /nuf 是可选的,其中 n 表示显示多少个单位,默认为 1,u 表示单位大小,默认为字节,f 表示打印格式,默认为16进制方式打印。
u 可以取值:
f 可以取值:
在 DOS 系统中,原生具备了调试功能,但我们要让程序从 DOS 规范中规定的内存起始地址 0100h 开始加载。 因此我们需要将代码第一行的“org 07c00h” 改为“org 0100h”,并执行汇编操作生成 COM 文件:
nasm boot.asm –o boot.com
然后下载并安装 DOSBox:https://www.dosbox.com/download.php?main=1。 打开 DOSBox,执行 debug 命令即可进行调试。
DOS 中所有的数字都是十六进制的,所以你不能指定进制转换及如何显示。 下表是 DOS 的全部调试指令。
指令 | 简称 | 参数 | 说明 |
---|---|---|---|
assemble | A | [address] | 从 CS:0100 地址开始编写汇编代码,不支持宏指令或标签 |
compare | C | range address | 比较两个内存块,如果没有差异,则显示 - |
dump | D | [range] [length] | dump 内存范围,如 d c000:0010 |
enter | E | address [list] | 将数据或指令(作为机器代码)直接输入到内存位置,例如 e ffcb d2 将内存 ffcb 位置修改为 d2 |
fill | F | range list | 用连续重复的值填充内存范围,例如 f 100 12f ’BUFFER’ 将地址 100 到 12f 之间的区域循环用 BUFFER 填充 |
go | G | [=address] [addresses] | 运行直到断点或指定的内存地址 |
hex | H | value1 value2 | 十六进制加法操作 |
input | I | port | 进行 IO 操作 |
load | L | [address] [drive] [firstsector] [number] | 加载指定内存地址、驱动器、扇区 number 数量的值到内存中 |
move | M | range address | 将指定范围内的所有字节复制到新地址,例如 m 7c00 7cff 600 将内存 7c00 到 7cff 范围内的信息复制到内存地址 600 起始的位置 |
name | N | [pathname] [arglist] | 加载指定的 com 文件 |
output | O | port byte | 进行 IO 操作 |
proceed | P | [=address] [number] | 单步执行,但跳过函数 |
quit | Q | | 退出调试 |
register | R | [register] | 显示所有寄存器及存储的内容 |
search | S | range list | 在内存范围内查询一个或多个字节值,例如 s fe00:0 ffff "BIOS" 命令在 fe00:0 ffff 范围内查找了 BIOS 字符串 |
trace | T | [=address] [number] | 单步执行,进入函数 |
unassemble | U | [range] | 反汇编指定内存范围内的指令,默认从 0010 开始进行反汇编 32 字节 |
write | W | [address] [drive] [firstsector] [number] | 将程序从 DEBUG 中保存到硬盘 |
bochs 如何模拟 dos 环境,加载我们的程序呢? 我们可以下载 bochs 官方提供的 FreeDos 镜像:http://bochs.sourceforge.net/guestos/freedos-img.tar.gz。 解压后,得到 FreeDos 的软盘镜像:a.img,将该文件路径配置到 bochsrc 的 floppya 配置项中,就可以打开 Dos 系统了。 为了区分,我们将 a.img 重命名为 freedos.img。
可是在虚拟机中的 FreeDos 系统,我们要如何才能加载宿主机上我们要调试的系统呢? 只要把需要调试的系统烧录到另一个软盘镜像上,作为 bochs 另一个盘启动,位于 floppya 中的 FreeDos 系统就可以找到这个文件了。
首先我们需要创建一个软盘镜像。 bochs 提供了 bximage 命令, 用来生成磁盘镜像文件:
a.img 是一个镜像文件,我们需要将这个镜像装载为软盘,才能够完成镜像的格式化,并拷入我们需要的程序文件。 这就需要使用 Linux 中的 loop 设备,类似于 Windows 的虚拟光驱。 如果你是在 windows 环境下使用 WSL,那么很遗憾,当前版本 wsl 不支持 loop 设备,你可以下载虚拟软驱加载 a.img 完成这些操作。 在 Linux 环境下,依次执行下列命令格式化我们生成的软盘镜像:
dd if=/dev/null of=a.img bs=512 count=1 conv=notrunc # 写入空白内容 sudo losetup /dev/loop0 a.img # 将 a.img 更改为 loop device sudo mkfs.msdos /dev/loop0 # 格式化为 DOS 文件系统格式 sudo fsck.msdos /dev/loop0 # 检视文件系统 sudo losetup -d /dev/loop0 # 删除临时 loop device
执行 file a.img,可以看到下面的信息,说明已经格式化完成:
a.img: DOS floppy 1440k, x86 hard disk boot sector
我们需要按照上面说的,将代码第一行的“org 07c00h”改为“org 0100h”,并执行汇编操作生成 COM 文件:
nasm boot.asm –o boot.com
然后执行下面命令将 boot.com 复制到软盘 a.img 中:
sudo mkdir /mnt/floppy # 创建挂载点 sudo mount -o loop a.img /mnt/floppy # 挂载 loop 设备 a.img sudo cp boot.com /mnt/floppy # 将 boot.com 复制到 a.img sudo umount /mnt/floppy # 接触挂载
###############################################################
# Configuration file for Bochs
###############################################################
# how much memory the emulated machine will have
megs: 32
# filename of ROM images
#romimage: file=/usr/local/share/bochs/BIOS-bochs-latest
romimage: file=$BXSHARE/BIOS-bochs-latest
#vgaromimage: /usr/local/share/vgabios/vgabios.bin
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
# what disk images will be used
floppya: 1_44=C:\Debin\Workspace\code\oranges\freedos\freedos.img, status=inserted
floppyb: 1_44=C:\Debin\Workspace\code\oranges\freedos\a.img, status=inserted
# choose the boot disk.
boot: floppy
# where do we send log messages?
# log: bochsout.txt
# disable the mouse
mouse: enabled=0
# enable key mapping, using US layout as default.
keyboard: keymap=$BXSHARE/keymaps/x11-pc-us.map
mouse: enabled=1
mouse: type=imps2, enabled=1
mouse: type=serial, enabled=1
mouse: enabled=0, toggle=ctrl+f10
运行 bochs,即可打开 Dos 系统,执行 b:\boot.com。
http://bochs.sourceforge.net/ http://bochs.sourceforge.net/doc/docbook/user/internal-debugger.html。 https://www.dosbox.com/ https://www.gsp.com/cgi-bin/man.cgi?section=5&topic=bochsrc。 https://montcs.bloomu.edu/Information/LowLevel/DOS-Debug.html。