目录
一、前言:
相信很多人都有使用模拟器的需求,因为并不是所有的场景都能用虚拟机代劳,如果你需要对程序精心的调试,实时查看内存与寄存器信息、研究指令集的工作原理,那么模拟器就是一种非常重要的工具。常见的模拟器有很多,最出名的就是Qemu,它既可以作为模拟器又可以作为虚拟机使用,并且对大多数CPU指令集架构提供支持,且有多种工作模式,支持kvm加速、gdb调试等等功能,可以说对于操作系统内核开发、嵌入式开发、指令集设计等都有着非常重要的作用。但是我今天不讲Qemu而是另一个开源的x86模拟器 --- bochs(读音同:box [bɒks]).对于内核开发者来说,这也是一个非常重要的工具,它可以提供比Qemu+gdb更加精细、精准的调试,可以查看更多的信息,对于内核开发初学者来说是一个强悍的工具,更重要的是它是开源的,并且现在还在更新。我相信许多学习内核开发的新手都是先使用bochs进行模拟和调试,然后再转向Qemu等其他模拟器,Bochs作为这么一个基础仿真软件,初学者用起来如果比较困惑的话可能对后续的内核开发不利。同时,如果大家也把Bochs作为内核开发使用的模拟器,大家最好在正式学习之前把基础的Linux命令给学会,这样未来的学习将会事半功倍。大家如果觉得本文章还是有那么一点点帮助的话,可以把它推荐给其他内核学习者,毕竟简化环境的搭建对于初学者而言还是非常重要的。这篇文章也许比较长,但是和官方文档比起来已经很精简、很浓缩了,对于内核开发者而言,看几千页的手册也只是家常便饭,请大家耐心看完吧。
bochs当前信息
令人高兴的是Bochs模拟器现在已经全面转向GItHub进行开发了,并且仓库几乎是日日更新,如果大家觉得出现了bug或者需要提一些建议的话可以直接发一个issue,如果是做虚拟化方向的话可以参考bochs或者给它提交pr。
bochs作为一个开源工具,它本身是可以使用GNU/linux自带的包管理器安装的,但直接使用包管理器安装的bochs并不具备调试功能,如果想使用bochs来调试自己的内核,请务必下载它的源代码并自己配置和编译。
至于为什么我会想起来写bochs的文档,那正是因为bochs在今年(2024)三月份发布了其最新版本bochs-2.8,我就想开源开发者这么努力、坚持不懈做出更好、更先进、支持更多CPU和指令集的模拟器,那么我是不是也该为社区做一点什么呢?bochs的英文文档可以说是已经非常详细了,但是它的中文文档好像就比较少,只有一些自己配置bochs的资料,对于全面使用bochs模拟器来调试内核来说还是不太够用,那么希望大家看了这篇文章就可以配置出适合自己的bochs模拟器,并用它来调试和学习x86架构和操作系统内核。
最新版新增了几块现代CPU,有了这些新CPU,就可以在模拟器里为自己的内核开发出更多的基础功能,大家不要着急,我会在后面的章节告诉大家怎么把这些CPU给使用起来,让自己的操作系统内核跑在如图所示的先进的CPU上面。不过我听说Bochs的开发者是Intel的员工,不知道是不是因为这个原因,新版本的Bochs只是新增了多块Intel CPU的支持,对于AMD则没有新增,并且根据我的亲自尝试,模拟的AMD的CPU这块CPU甚至还不支持x2APIC,这对于开发一个现代内核来说非常不方便,如果大家想基于AMD芯片调试内核,那么我还是推荐大家使用Qemu+gdb。
既然我的标题里都写了这是中文文档,那么我肯定会教大家怎么使用到Bochs来调试自己的内核。祝大家内核学习顺利吧。
二、Bochs简介
1、什么是Bochs
Bochs是一个完整模拟Intel x86指令集的计算机程序。它包括对Intel x86CPU、常见IO设备和自定义BIOS的仿真,除了IntelCPU之外还有AMD处理器的支持。Bochs拥有标准PC外设的设备模型:鼠标、键盘、VGA卡/显示器、磁盘、定时器芯片、网卡等。
Bochs最早由Kevin Lawton大佬于1994年开始编写,2000年3月,Bochs以LGPL协议开源出来。
它一开始使用svn集中式版本管理工具,现在已经改成了Git分布式版本管理来管理项目。
LGPL协议: This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2、Bochs能在哪些机器上运行
Bochs使用C++编程语言编写,由于C++具有跨平台性,因此Bochs可以在x86,PPC,Alpha,Sun,MIPS等指令集的CPU上面运行。Bochs是一个单纯的模拟器、它不是虚拟机,它的指令集的实现并不依赖于宿主机本身拥有的指令集,也就是不使用VMX等虚拟化指令集的支持。Bochs支持x86的高度仿真,也就是可以把它当成一台物理机器,拥有自己的BIOS。
bochs实际上提供了源码包、exe文件和rpm包,其中最重要的就是源代码。有了源代码,Bochs可以拿来跑在很多机器上,只是可能需要自己去编译和构建。
3、Bochs模拟器适合哪些人使用
实际上Bochs完全可以拿来当成一个“虚拟机”,比如在没有Windows生态的机器上跑Windows操作系统。不过我是站在内核开发者的角度来看,更多把Bochs作为一个能够模拟真实PC环境的仿真工具。实际上Bochs在为indows、Linux、MacOS都可以跑,由于我没有其中一些设备,因此我就在GNU/Linux上面使用Bochs作为例子。
三、准备使用Bochs
可以使用包管理器安装,但是包管理器安装的Bochs不具备调试功能。
1、下载Bochs的源代码
Bochs模拟器有很多版本,从最开始的1.0版本到现在最新的2.8版本,大家需要选择一个合适的版本,不同版本有不同的特点,使用的配置文件的语法也不尽相同,如果大家知道自己需要哪一个版本就最好,如果大家不知道该选择哪一个版本那么我推荐大家使用Bochs-2.8也就是最新的版本,我也是按照这个版本的文档写成的中文文档。新旧版本的Bochs的配置文件语法会有不同(而官方文档里的是最新的语法),所以推荐大家大胆使用最新版本即可。本文是我对Bochs英文文档的个人理解,里面也许会有错误,请大家还是以官方文档为主,如果大家认为我有什么地方写的不对,可以在评论区指正一下。
点进Bochs2.8这个目录,可以看到提供了很多不同的包,那么我们这里选择下载源码包,名称中带有src的就是源码包。.zip源码包带有msvc字样,应该是给Windows用的。我主要在Linux上写内核,因此会选择bochs-2.8.tar.gz.
2、解压并观察源代码
tar -xvf bochs-2.8.tar.gz
解压后会在tar包所在目录产生一个新目录bochs-2.8
我们不要急着就开始配置和编译,而是要先观察。
文件/目录 | 作用 |
configure | 非常重要的shell脚本,配置的时候执行它 |
Makefile.in | 根据configure脚本的配置 生成最终的Makefile文件 |
install-sh | 用于在安装过程中复制文件 |
main.cc | Bochs的主程序入口点 |
bios目录 | 包含Bochs使用的BIOS文件 |
gui目录 | Bochs支持图形界面调试(不过我更喜欢字符界面调试) |
我们在最终执行make install之前实际上要稍微修改一些文件才能编译通过,否则可能会出现错误,因此需要简单了解一下目录里面的构成。
3、配置Bochs
这一步可以说是最重要的,我们怎么配置Bochs,那么最终构建出来的Bochs就会具有什么样的能力;或者说关闭一些配置,那么Bochs就会失去什么样的能力。
我们在执行make install 之前执行./configure,但不是直接这么执行就好,而是要自己配置。
比如./configure --with-x11 ....等一系列配置
平台相关的配置
Platform | GUI | 说明 |
Win32/MinGW | --with-win32 | Windows平台带上这个参数 |
MacOS/Darwin | --with-carbon | Mac平台带上这个参数 |
MacOS9 | --with-macos | 这应该比较老了 |
AmigaOS | --with-amigaos | 我不了解这个系统 |
带有x显示服务器的Linux | --with-x11 | Linux上用这个参数 |
虽然说是需要带有x显示服务器的Linux用--with-x11,但实际上只要是Linux就用这个参数就好了,Bochs是可以跑在Xwayland上面的。
Bochs使用的图形库
由于Bochs是跑在操作系统上的,因此需要调用图形库才能使用。
显示库选项 | 作用 |
--with-x11 | Linux上用这个 |
--with-win32 | Win32原生GUI |
--with-carbon | 使用Carbon GUI |
--with-sdl | 启用对SDL 1.2.x GUI接口的支持 |
--with-sdl2 | 启用对SDL 2.x GUI接口的支持 |
--with-term | 需要curses库 |
--with-wx | 启用对wxWidgets配置和显示接口的支持 |
--with-nogui | 你不关心视频输出/单纯调试 |
--with-all-libs | 自动检测系统上安装的库(我没试过) |
我启动了三个选项
--with-x11 --with-wx --with-sdl2
大家可以参考我的做法
Bochs通用配置
这会是一个大的表格,如果大家没有耐心看了,那么可以直接参考我的做法
通用选项 | 默认情况 | 说明 |
--enable-plugins | 关 | 启用插件支持 |
--enable-debugger | 关 | Bochs内置调试器,默认关闭因此使用包管理器安装的Bochs不带调试功能 |
--enable-debugger-gui | 开(如果debugger开) | 有图形界面的调试器 |
--enable-readline | - | 提供命令行编辑和历史记录功能 |
--enable-gdb-stub | 关 | 使用gdb调试,它与--enable-debugger不兼容,并且gdb调试和smp也不兼容 |
--enable-docbook | - | 如果系统安装了docbook2html则默认开启 |
--enable-instrumentation=directory | 关 | 仪器支持 |
--enable-xpm | 开 | XMP |
--enable-show-ips | 开 | 启用测量IPS(每秒指令数)的日志记录功能 |
--enable-logging | 开 | 运行时记录日志 |
--enable-cpp | 关 | 将所有.cc文件重命名为.cpp |
--enable-idle-hack | 关 | 保持Bochs响应性(Bochs不占用过量的系统资源) |
--enable-assert-checks | - | BX_ASSERT事件在断言失败时会导致panic |
我的做法:除了--enable-docbook,全开
CPU与内存配置
配置参数 | 默认情况 | 说明 |
--enable-cpu-level={3,4,5,6 } | 6 | 386/486/586/686及以上(你选6就好) |
--enable-smp | 关 | 对称多处理器支持 |
--enable-fpu | 开 | 浮点运算单元 |
--enable-3dnow | 关 | AMD的指令集 |
--enable-vmx | 关 | 支持Intel虚拟化扩展(VMX) |
--enable-svm | 关 | 支持AMD SVM(安全虚拟机)扩展模拟 |
--enable-avx | 关 | 支持AVX指令集 |
--enable-x86-debugger | 关 | 支持x86调试器 |
--enable-monitor-mwait | 关 | 支持MONITOR/MWAIT指令 |
--enable-alignment-check | - | 如果CPU级别大于4则开启(支持CPU中的对齐检查和#AC异常) |
--enable-configurable-msrs | 关 | 支持用户配置模拟的MSR |
--enable-long-phy-address | 关 | 支持大于32位的客户物理地址 |
--enable-a20-pin | 开 | 支持A20引脚 |
--enable-large-ramfile | 开 | 支持大于主机支持的客户内存 |
--enable-repeat-speedups | 关 | 启用对重复I/O和内存复制加速的支持 |
--enable-fast-function-calls | 关 | 启用对快速函数调用的支持 |
--enable-handlers-chaining | 关 | 启用对处理程序链优化的支持 |
--enable-all-optimizations | 关 | 开启所有开发者认为安全使用的速度优化选项:--enable-repeat-speedups 、--enable-fast-function-calls 、--enable-handlers-chaining |
我的做法:全部打开
对设备的模拟支持
配置选项 | 默认情况 | 说明 |
--enable-cdrom | 开 | 启用对真实CD-ROM/DVD驱动器的使用 |
--enable-sb16 | 关 | 启用Sound Blaster模拟,可用的低级声音接口会自动检测。 |
--enable-es1370 | 关 | 启用ES1370声音模拟 |
--enable-gameport | 关 | 启用标准PC游戏端口 |
--enable-ne2000 | 关 | 启用NE2000网络卡支持 |
--enable-pnic | 关 | 启用PCI伪NIC(网络卡)支持 |
--enable-e1000 | 关 | 启用Intel(R) 82540EM千兆以太网适配器支持 |
--enable-clgd54xx | 关 | 启用Cirrus Logic GD54xx(CL-GD5430 ISA或CL-GD5446 PCI)视频卡支持 |
--enable-voodoo | 关 | 启用实验性的3dfx Voodoo Graphics模拟。Voodoo1已知可以工作,Voodoo2支持尚未完成,但几乎可用 |
--enable-iodebug | 跟随debugger | Dave Poirier编写了一个使用I/O端口的实验性调试器接口,使得在客户操作系统中运行的软件可以访问调试器的功能 |
--enable-pci | 开 | 启用有限的i440FX / i430FX / i440BX PCI支持 |
--enable-pcidev | 关 | 启用PCI主机设备映射支持 |
--enable-usb | 关 | 启用i440FX / i440BX PCI USB支持(UHCI) |
--enable-usb-ohci | 关 | 启用USB OHCI支持,提供带有2端口根集线器的主控制器 |
--enable-usb-ehci | 关 | 启用USB EHCI支持,提供带有6端口根集线器的主控制器 |
--enable-usb-xhci | 关 | 启用USB xHCI支持,提供带有4端口根集线器的主控制器 |
--enable-raw-serial | 关 | 启用对串行端口模拟的支持,以访问宿主的串行端口 |
我的做法:--enable-voodoo --enable-iodebug可以打开
其他配置你可以根据自己的情况自行配置,比如你想写USB驱动,那么就可以打开--enable-usb等
总结configure
相比大家对官方文档里的这些配置参数有了一定的了解,可以看到,参数真的非常的多,如果大家不知道哪些是自己必备的,那么你可以尽可能多的选择一些参数,编译出来你不使用也是可以的。但是如果你参数加少了,那么你就得重新编译了,这样得不偿失。
这里我提供我自己的configre
./configure --with-x11 --with-wx --with-sdl2 --enable-plugins --enable-debugger --enable-debugger-gui --enable-readline --enable-xpm --enable-show-ips --enable-logging --enable-assert-checks --enable-cpp --enable-idle-hack --enable-cpu-level=6 --enable-smp --enable-fpu --enable-3dnow --enable-x86-64 --enable-vmx --enable-svm --enable-avx --enable-x86-debugger --enable-monitor-mwait --enable-alignment-check --enable-configurable-msrs --enable-long-phy-address --enable-a20-pin --enable-large-ramfile --enable-repeat-speedups --enable-fast-function-calls --enable-handlers-chaining --enable-all-optimizations --enable-cdrom --enable-sb16 --enable-es1370 --enable-gameport --enable-ne2000 --enable-pnic --enable-e1000 --enable-clgd54xx --enable-voodoo --enable-iodebug --enable-pci --enable-pcidev --enable-usb --enable-usb-ohci --enable-usb-ehci --enable-usb-xhci --enable-raw-serial --enable-vmx=2 --prefix=/home/april_zhao/bochs
大家可以看到我还添加了一个参数--prefix=directory
这个参数就是给Bochs安一个家,否则会放置到默认的FHS位置,由于我在调试内核的时候可能会使用不同版本和编译参数的Bochs,因此我不会去使用系统全局的Bochs而是我项目本地的Bochs,因此放置到一个我自己创建的目录里。
当然这只是我的一个建议,因为这么做的话你可以把bochs附加到你的项目里,假如你需要多台设备同时用于开发的话就不需要每个设备各自编译一次,你git clone的时候就会把bochs环境自动带到的新设备然后直接make bochs就可以调试你的内核。
你可以不参考我的做法,可以为你的每台设备都各自构建一个bochs,当然如果你嫌麻烦的话我已经打包好了带有调试功能的bochs的rpm包,你下载我的rpm包然后使用包管理器安装就可以了。
如果你使用了我打包的rpm包,请保护这个包的版本,不要让它升级或降级,这样会被官方提供的、没有调试功能的Bochs给覆盖掉,你需要保护这个包的版本。
sudo dnf versionlock add bochs
如果你不使用rpm系Linux可以看看我下面打的两个包:
带有调试功能的Bochs的deb包https://github.com/hehellooedas/ToyOS/releases/download/Kernel/bochs_2.8-2_amd64.deb带有调试功能的Bochs的tgz包
https://github.com/hehellooedas/ToyOS/releases/download/Kernel/bochs-2.8.tgz
如果大家以上的尝试都失败了也没有关系,我给大家把bochs打包进了docker,大家可以通过docker来运行bochs.这里我就把代码放在这里,就不发布构建好的镜像,大家下载我的压缩包然后自己构建这样更好,代码是开源的,也无需担心有什么病毒。
bochs_debug_docker.tar.xzhttps://github.com/hehellooedas/ToyOS/releases/download/Kernel/bochs_debug_docker.tar.xz大家先安装好docker或者podman,然后下载这个.tar.xz,下载好之后解压缩。
tar -xvf bochs_debug_docker.tar.xz
cd dockerfile
docker build -t bochs_debug_docker .
#然后耐心等待一段时间
docker run -it --rm -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix bochs_debug_docker bash
运行之后会进入docker 环境
dnf install python3-dnf-plugin-versionlock
dnf versionlock add bochs
在这个环境里,大家就可以直接运行 bochs -f /path/bochsrc
由于这是在容器里,很多操作也许是无法进行的,大家可以在宿主机里编译好自己的内核,然后移动到docker里运行.
docker cp /path/kernel containerid:/root
同时,如果你启动bochs提示panic,也不要慌,可能是一些音频设备无法访问物理机的设备文件,大家可以使用alwayscont跳过这些panic,毕竟你如果想写音频驱动的话,大概率已经有能力使用更高级的Qemu了,也一般也不用再用bochs来调试.
sdl12-compat-1.2.68-2.fc40.x86_64
qemu-ui-sdl-8.2.2-2.fc41.x86_64
qemu-audio-sdl-8.2.2-2.fc41.x86_64
这是sdl音频相关的包,大家也可以在docker容器里面安装它
这里最重要的一点我在前面的表格里已经提到了:--enable-gdb-stub这个参数和--enable-debugger是不能共存的,也就是你要么使用bochs自带的调试工具,要么使用gdb远程调试,不可兼得。并且gdb调试是不能够开启smp的,也就是--enable-gdb-stub和--enable-smp不能共存。
如果大家想体验两种调试风格,那就只能编译两次,也就是编译出两个Bochs,然后可以都放进项目里,bochs_debug和bochs_gdb_debug.
因此我在这里推荐,尽量使用bochs自带的调试工具吧,除非你的内核不需要开启smp或者你不需要多核处理器的支持。
做好这一些配置之后,大家千万不要忙着直接就make install,因为这么多很有可能会出错的。
make前的准备
cp misc/bximage.cpp misc/bximage.cc
cp iodev/hdimage/hdimage.cpp iodev/hdimage/hdimage.cc
cp iodev/hdimage/vmware3.cpp iodev/hdimage/vmware3.cc
cp iodev/hdimage/vmware4.cpp iodev/hdimage/vmware4.cc
cp iodev/hdimage/vpc.cpp iodev/hdimage/vpc.cc
cp iodev/hdimage/vbox.cpp iodev/hdimage/vbox.cc
cp misc/bxhub.cpp misc/bxhub.cc
cp iodev/network/netutil.cpp iodev/network/netutil.cc
在我的配置中,我就需要做一些cp操作,修改一些c++程序的文件名,否则会导致报错,大家可以跟着这么做,然后再执行make install ,假如这么做了之后还是报错了,那么大家有可能需要更多的cp操作,同样是把.cpp文件替换成.cc文件就好。在编译过程中还会出现各种各样的错误,它们很有可能是因为你的系统缺少了某些库或者包,这就需要大家根据报错日志自行安装这些缺少的库,使用包管理器安装就好了。安装好之后再执行make install,如果还是提示同样的错误,那么你可以重新执行./configure然后再make install.
make install执行成功之后,在你的--prefix指定的目录里会出现三个新目录:bin lib share
4、make构建之后
1、bin目录
这是Bochs最关键的目录,它里面保存了几个重要二进制命令:bochs(模拟器本身)、bxhub(虚拟网络环境)、bximage(创建虚拟磁盘)。
①bochs是模拟器本身
这个我会在后面讲到该怎么使用。
②bximage是一个磁盘工具
这个工具非常有用,你就把它当作是一个交互的命令行就好,直接执行bximage.然后根据它的提问进行回答,以创建你的虚拟磁盘。
❯ bximage
========================================================================
bximage
Disk Image Creation / Conversion / Resize and Commit Tool for Bochs
$Id$
========================================================================
1. Create new floppy or hard disk image #创建磁盘
2. Convert hard disk image to other format (mode) # 切换磁盘格式
3. Resize hard disk image # 修改磁盘尺寸
4. Commit 'undoable' redolog to base image
5. Disk image info
0. Quit
Please choose one [0] 1
Create image
Do you want to create a floppy disk image or a hard disk image? #创建硬盘还是软盘
Please type hd or fd. [hd]
What kind of image should I create?
Please type flat, sparse, growing, vpc or vmware4. [flat]
Choose the size of hard disk sectors.
Please type 512, 1024 or 4096. [512]
Enter the hard disk size in megabytes, between 10 and 8257535
[10] 512M
What should be the name of the image?
[c.img] hard.disk
Creating hard disk image 'hard.disk' with CHS=1040/16/63 (sector size = 512)
The following line should appear in your bochsrc:
ata0-master: type=disk, path="hard.disk", mode=flat
❯ ls
公共 模板 视频 图片 文档 下载 音乐 桌面 bochs_rpm coding hard.disk test.c wpsoffice
❯ file hard.disk
hard.disk: data
❯ ll -h hard.disk
-rw-r-----. 1 april_zhao april_zhao 512M 3月27日 16:18 hard.disk
回答的问题和磁盘分区的时候类似。
这里面的英文也不难,相信大家都能够看得懂。
这里面比较难分别的可能是虚拟磁盘的格式,我这里给大家说一说:
- Flat扁平格式:是一种预分配的磁盘格式,磁盘的容量在创建的时候就被分配和固定给下来。性能更好,不会动态分配磁盘空间。如果你需要的虚拟磁盘大小不大,就几十MB,调试内核和做磁盘驱动、文件系统使用,就可以拿Flat格式作为启动磁盘。如果你不知道该怎么选择磁盘格式,那就选这个。
- Sparse稀疏格式:是动态增长的虚拟磁盘,在创建的时候体积很小,不断写入数据后体积会增大。
- Growing:和Sparse类似。
- vpc:和Microsoft Virtual PC兼容。
- vmware4:和vmware虚拟机兼容。
③bxhub是一个网络工具
它支持互连两个 Bochs 会话,话 使用名为“bxhub”的外部程序通过 UDP 在同一台机器上运行。也就是bxhub可以连接多个Bochs模拟出来的系统进行UDP网络连接。
❯ bxhub
RX port #1 in use: 40001
RX port #2 in use: 40003
Host MAC address: b0:c4:20:00:00:0f
FTP / TFTP support and network boot disabled
Press CTRL+C to quit bxhub
想进行网络连接当然是需要网络适配器的,这是计算机网络物理层的要求,因此你需要在你的bochsrc里进行配置,让Bochs给你模拟一块网卡:
ne2k: mac=52:54:00:12:34:56, ethmod=socket, ethdev=mymachine:40000, script=""
ne2k: mac=52:54:00:12:34:56, ethmod=socket, ethdev=40000, script=""
如果你和我一样,直接在你的主机系统里敲这个命令行bxhub.那么会使用默认情况:
- UDP端口40000
- 默认进行两个Bochs会话进行连接
- 服务器端的MAC地址:b0:c4:20:00:00:0f
- 禁用FTP/TFTP
如果你不使用默认的情况,你需要添加一些参数:
- -ports=[2--6] 虚拟以太网端口的数量
- base= - 基本UDP端口
- -mac= 主机的MAC地址
- tftp= 使用指定目录作为root,以获取FTP支持
- -bootfile= DHCP的网络引导文件
- -log= 设置日志级别(1,2,3)默认是1
- -logfile= 将日志输出发送到指定文件
- --help 显示帮助信息
“vnet”服务器现在还使用 TFTP 目录作为 root 提供被动 FTP 支持。 FTP 服务器名称为 vnet-ftp。 对于只读访问,必须将用户名设置为匿名并使用任意密码。 该模式支持浏览目录子树和下载文件。 对于读/写访问,必须将用户设置为 bochs,密码为 bochs。 这支持上传、重命名和删除文件、创建和删除目录。
2、lib目录
这个目录下的文件是Bochs模拟器的插件库,它们是Bochs运行时可以按需加载的共享库(.so文件)。这些插件提供了对各种设备的模拟支持,包括声音设备、网络接口卡、USB控制器、图形适配器等。大家知道这个目录的作用就行,我们后续不太需要去配置和管理这个目录下的文件(没事不要去动它,Bochs模拟器会自己选择需要的运行库的)。
里面的文件大致作用如下表:
文件名 | 作用 |
libbx_ne2k.so | 提供对NE2000网卡的模拟 |
libbx_sb16.so | 提供对Sound Blaster 16声音卡的模拟 |
libbx_e1000.so | 提供对Intel E1000网卡的模拟 |
libbx_vga.so | 提供对VGA图形适配器的模拟 |
libbx_usb*.so | 提供对USB控制器标准的模拟 |
文件名 | 作用 |
libbx_eth_slirp.so | 通过用户模式网络堆栈(如slirp)提供网络连接 |
libbx_eth_socket.so | 通过套接字提供网络连接 |
libbx_eth_tuntap.so | 通过TUN/TAP设备提供网络连接 |
文件名 | 作用 |
libbx_sdl2_gui.so | 提供基于SDL 2.x的图形用户界面 |
libbx_x_gui.so | 提供基于X Window系统的图形用户界面 |
3、share
- BIOS-bochs-latest:BIOS模拟软件
- VGABIOS-lgpl-latest-cirrus:VGA模拟软件
大家主要需要了解的就是以上两个文件,我会在后面再提到他俩。在share目录中还有两个子目录,分别是doc和man,这两个目录原本是要依照FHS放置到/usr里头的。当你执行man bochs命令的时候会把内容显示出来,作为帮助文档,那么既然我们使用--prefix参数把Bochs放置到自己的目录里而不是依照FHS标准放置的话,就不能使用man命令去查看帮助文档了,大家可以手动翻阅这两个目录里的文档。
让你的Bochs跑起来:
想让Bochs跑起来其实非常简单,我们只需要使用bochs/bin/bochs这个命令,直接运行它,那么bochs就跑起来了,但是Bochs这时候会非常疑惑,它不知道自己该跑什么程序,因此我们要通过一个配置文件来告知Bochs模拟器让它以何种方式模拟。
bochs -f bochsrc
使用-f选项指定Bochs要使用的配置文件,这个bochsrc就是配置文件的文件名,当然本质上是配置文件的位置。bochsrc是我取的名字,你不一定要让这个配置文件叫这个名字,实际上叫什么名字都可以。总之使用-f选项指定配置文件的位置。
如果你是使用的自己编译的Bochs,或者你改变了Bochs目录的位置,那么在启动的时候是需要配置环境变量的:
export BXSHARE = xxxx/bochs/lib/bochs/share/bochs
export LTDL_LIBRARY_PATH = xxxx/bochs/lib/bochs/plugins
是如图所示的这两个环境变量,你可以把它写入到~/.zshrc或 ~/.bashrc 或 /etc/bashrc,这些地方。那么当你的shell启动的时候就会引入这两个环境变量。
如果你不想直接执行bochs脚本这么麻烦,而是自己写了一个Makefile脚本,那么你可以在Makefile脚本里添加环境变量,不需要添加到bashrc了
BXSHARE = bochs/lib/bochs/share/bochs
export LTDL_LIBRARY_PATH = bochs/lib/bochs/plugins
好了,当你看到这里的时候你已经配置并安装好了Bochs,并且已经知道怎么启动它,但这仅仅是一个开始,重头戏其实在后面。
四、编写Bochs的配置文件
编写配置文件可以说是这篇博客里最麻烦的过程,有很多各参数需要配置和调试,我在观看官方的英文文档的时候也是非常头疼,不过麻烦的部分我尽可能帮大家给解决掉,请大家耐心听我一一道来。
我会由浅入深的开始讲,方便读者随时知道自己当前进行设置的目的是什么。
1、ROM image
我先填一个前面埋的坑,就是那两个重要文件。
#设置ROM和VGA模拟
romimage:file="bochs/share/bochs/BIOS-bochs-latest"
vgaromimage:file="bochs/share/bochs/VGABIOS-lgpl-latest"
#Bochs自带的VGA文件设定的VBE显示地址为 0xe0000000 ,你启动VBE后可以往这里头写入数据以显示像素
bochsrc里需要指定这两个文件的位置,如果你的bochs是跟随项目的,那就如上述代码这么写就好;
如果你想要使用默认FHS的Bochs
romimage:file="/usr/share/bochs/BIOS-bochs-latest"
vgaromimage:file="/usr/share/bochs/VGABIOS-lgpl-latest"
#你可以添加参数进去
romimage:file="/usr/share/bochs/BIOS-bochs-latest",options=fastboot
#使用这个参数可以让BIOS跳过启动菜单,那么启动就可以快一点
那就要这样写。总之就是要明确两个重要文件的位置。如果你找不到你的这两个文件在什么地方,你可以使用find命令去查找:
sudo find / -name "*BIOS-bochs-latest*"
我安装了不止一个版本的Bochs.
对于新手而言,只要知道这两个文件就足够了。如果你还需要更多的配置,还得看官方文档。
2、配置内存
我想从简单到难的顺序写,内存对于大家来说肯定是非常不陌生的。
megs: 128 #设定内存大小为128MB
megs: 2048 #最大支持的内存大小为2048MB
这样就设置了Bochs模拟出来的计算机环境的内存是128MB大小。Bochs模拟器支持的最大内存为2048MB也就是2GB 。你可以填入的最小的数是1,也就是1MB大小的内存,毕竟8086CPU的地址总线是20位,最大的寻址空间就是1MB.其实在官方文档里,megs参数还有其他用法,不过我个人觉得按照上面的样子这么写就可以了,也不用做过多的考虑,我相信内核开发者的计算机内存大小一般也不会低于1GB了,如果你的内存实在不足,bochs将会提示:std::bad_alloc 这样的字样(充满了C++命名空间的气息)。官方文档虽然写的非常详细,但是也没有必要全部都考虑进去,能够达到自己的目的就好。
3、调试选项
调试选项就是你设置bochs以什么方式来调试,是字符界面调试还是图形界面调试,bochs产生的界面使用哪一个图像库来绘制这样。
#使用命令行进行调试
config_interface:textconfig
display_library:x
#使用图形界面进行调试
config_interface:textconfig
display_library:x,options="gui_debug"
以上是我的选项,我建议读者按照我这么设置就挺好了,不过也可以根据自己的需要进行调试设置。
config_interface可选参数: textconfig / wx / win32config
使用示例:
display_library: x
display_library: sdl2, options=fullscreen
display_library: options=cmdmode
"cmdmode" - 按 F7 后调用标题栏按钮处理程序(sdl、sdl2、win32、x)
"fullscreen" - 以全屏模式启动(sdl、sdl2)
"gui_debug" - 使用 GTK 调试器 GUI (sdl, x) / Win32 调试器 GUI (sdl, sdl2, win32)
"hideIPS" - 禁用状态栏中的 IPS 输出(rfb、sdl、sdl2、term、vncsrv、win32、wx、x)
"nokeyrepeat" - 关闭主机键盘重复(sdl、sdl2、win32、x)
"no_gui_console" - 使用系统控制台而不是内置 GUI 控制台(rfb、sdl、sdl2、vncsrv、x)
"timeout" - 等待客户端(rfb、vncsrv)的时间(以秒为单位)
4、控制可选插件
plugin_ctrl: unmapped=0, e1000=1 # unload 'unmapped' and load 'e1000'
就是控制哪些插件是开启的,哪些插件是关闭的,为1是开启,为0是关闭。
默认情况下将加载这些插件(如果存在):'biosdev', 'extfpuirq', 'Gameport'、'iodebug'、'Parallel'、'Serial'、'Speaker' 和 'Unmapped'。
提示:有些插件可能需要调用系统的API,如果你缺少某写包或者未开放某些端口,bochs是会报错的,因此如果你的bochs启动的时候出现了panic,你可以关掉某些插件。
#设置插件
plugin_ctrl:unmapped=1,biosdev=1,speaker=1,extfpuirq=1,parallel=1,serial=1,iodebug=1
这是我的插件设置。
5、设置启动方式和磁盘
boot: floppy #从软盘启动
boot: cdrom, disk #先从cd启动,如果不行再从硬盘启动
boot: network, disk #先从网络启动,如果不行再去硬盘启动
boot: cdrom, floppy, disk #先cd启动,不行就去软盘找,再不行就去硬盘找
不用写4个boot上去,写一个boot就好,表明从哪个存储设备启动,并设置启动顺序,你最多可以写三个设备进去。
磁盘设置可以说是整个bochsrc配置文件里面最重要的设置了,如果你在启动bochs的时候提示,没有找到可启动的设备,或者没有启动项,那么大概率是磁盘设置错误。就是说Bochs不知道该怎么从你的磁盘加载MBR进行启动。
1、设置软盘
floppy_bootsig_check:disabled=0
floppya:type=1_44,1_44="./tools/boot.img",status=inserted,write_protected=0 #3.5英寸高密度软盘
虽然现在来看软盘早已淘汰,不过有时候可以用来研究软盘。
floppy_bootsig_check: disabled=1
这将禁用启动软盘上的0xaa55签名检查。默认情况下检查是启用的。
正常情况下,启动从软盘时会检查BOOT扇区的末尾是否有标准的0xaa55签名。但是有些非标准的软盘可能缺少这个签名。通过设置disabled=1可以禁用这个检查,从而允许来自没有签名的软盘的启动。
默认情况下,检查是启用的(disabled=0)。只有在需要兼容某些非标准启动软盘的情况下,才需要设置为disabled=1禁用检查。
软盘比较在意尺寸和大小,大家在创建的时候就应该自己的软盘是什么规格的软盘
支持以下软盘介质类型:2_88,1_44,1_2,720k,360k,320k,180k,160k,以及"image"让Bochs能自动检测软盘介质类型(只对图像有效,不适用于原始软盘驱动器)。在这种情况下,尺寸必须匹配支持的类型之一。
2、设置硬盘
实例:
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15
ata2: enabled=1, ioaddr1=0x1e8, ioaddr2=0x3e0, irq=11
ata3: enabled=1, ioaddr1=0x168, ioaddr2=0x360, irq=9
这些选项支持最大4个ata通道。每个通道的双io地址和中断必须指定。默认下ata0和ata1处于启用状态,具有上述值。
irq指定模拟的ATA控制器对应的中断线路,也就是磁盘通过ATA给你发送中断消息时候的中断号,比如irq=14,则你应该设定0x2e中断跳转到对应的磁盘处理函数,因为前0x20个中断被Intel保留了,因此从0x20开始计算普通中断号。
这里请注意一下,虽然官方文档里已经提供了4个ATA通道的irq值,但是经过我的尝试,这个值是有可能与别的设备的中断号有冲突,如果大家遇到了冲突的现象可以尝试更换一个中断号。还有就是在设置ATA通道连接的master和slave磁盘的时候,如果你ioaddr1对应master、ioaddr2对应slave失败了,你可以尝试交换了一下这二者,把原本连接到master的磁盘连接到slave磁盘。
ioaddr为IO端口的起始,如ioaddr=0x1f0
/* 磁盘0 */
#define PORT_DISK0_DATA 0x1f0 //数据端口
#define PORT_DISK0_ERROR 0x1f1 //错误状态
#define PORT_DISK0_SECTOR_CNT 0x1f2 //要操作的扇区数
#define PORT_DISK0_SECTOR_LOW 0x1f3 //扇区号/LBA(7:0)
#define PORT_DISK0_SECTOR_MID 0x1f4 //柱面号(7:0)/LBA(15:8)
#define PORT_DISK0_SECTOR_HIGH 0x1f5 //柱面号(15:8)/LBA(23:16)
#define PORT_DISK0_DEVICE 0x1f6 //设备工作方式
#define PORT_DISK0_CMD 0x1f7 //控制命令端口
#define PORT_DISK0_STATUS 0x3f6 //状态/控制寄存器
/* 磁盘1 */
#define PORT_DISK1_DATA 0x170
#define PORT_DISK1_ERROR 0x171
#define PORT_DISK1_SECTOR_CNT 0x172
#define PORT_DISK1_SECTOR_LOW 0x173
#define PORT_DISK1_SECTOR_MID 0x174
#define PORT_DISK1_SECTOR_HIGH 0x175
#define PORT_DISK1_DEVICE 0x176
#define PORT_DISK1_CMD 0x177
#define PORT_DISK1_STATUS 0x376
通过上面的端口可以分别对ATA0通道的master磁盘和slave磁盘进行操作。其他三个ATA通道也是类似的做法。每个ATA通道可以连接两块磁盘,因此你需要为每个ATA选项提供ioaddr1和ioaddr2,表示该硬盘对应的I/O端口号的初始地址。
ata0-master: type=disk, path=10M.img, mode=flat, cylinders=306, heads=4, spt=17, translation=none
ata1-master: type=disk, path=2GB.cow, mode=vmware3, cylinders=5242, heads=16, spt=50, translation=echs
ata1-slave: type=disk, path=3GB.img, mode=sparse, cylinders=6541, heads=16, spt=63, translation=auto
ata2-master: type=disk, path=7GB.img, mode=undoable, cylinders=14563, heads=16, spt=63, translation=lba
ata2-slave: type=cdrom, path=iso.sample, status=inserted
选项 | 含义 | 有效值 |
type | 当前磁盘的类型 | disk/cdrom |
path | 指定磁盘的位置 | 相对位置或绝对位置 |
mode | 虚拟磁盘的模式 | flat | concat | dll | sparse | vmware3 | vmware4 | undoable | growing | volatile | vpc | vbox | vvfat |
cylinders | 柱面数 | |
heads | 磁头数 | |
spt | 扇区数 | |
status | 当前状态(仅对cdrom有效) | inserted | ejected] |
biosdetect | 没啥用不用看 | auto | cmos | none |
translation | 磁盘转换方案 | none | lba | large | rechs | auto |
model | 识别ATA,命令返回的字符串 | 字符串 |
journal | 我也不知道这是做什么的 | 没用过这个参数 |
-
对bximage创建的磁盘镜像文件,如果CHS设置为0/0/0,Bochs会根据heads=16和spt=63自动计算柱面值。
-
对其他磁盘镜像或模式,柱面、磁头和扇区数必须手动指定。镜像文件实际大小必须等于CHS*512字节。
-
如果使用其他项目产生的平扁磁盘镜像,文件尾可能包含附加信息,导致校验失败。这时候可以选择"继续"跳过Panic。
-
磁盘转换方案用于老式INT13 BIOS功能和DOS等旧系统:
none: 不转换,支持528MB以下盘
large: 标准位移算法,支持4.2GB以下盘
rechs: 改进的echs算法,假设15个磁头,支持7.9GB以下盘
lba: 标准LBA辅助算法,支持8.4GB以下盘
auto: 自动选择最佳方案
简而言之:
- 对bximage镜像,可以自动检测柱面值
- 对其他镜像需要手动指定CHS值
- 文件大小必须匹配CHS格式
- 转换方案为老旧系统提供向下兼容
现代计算机和计算机标准的发展速度真是太快了,为了方便和可调式,没有必要再使用麻烦的CHS方案去寻找扇区了,直接使用LBA方案,这样不用考虑太多的硬件和硬件的兼容性的问题。就算要使用CHS方式来寻找扇区,那么尽量使用bochs的官方工具bximage来产生虚拟磁盘,这样可以免于自己设定磁盘的CHS信息,当bochs启动的时候会初始化IDE硬盘,你可以在日志里查看你设定的磁盘的CHS信息。
如图所示,这表明,我的ata0的主盘和ata1的从盘分别连接着一块磁盘。
同一块磁盘是不能连上两个接口的,当你的ata的path选项里第一次出现这块磁盘的时候,它会被bochs给锁定,如果你在另一个path里写上同一个文件的路径,那么这样会在bochs启动的时候被阻止。
6、设置pci
pci: enabled=1, chipset=i440fx # 如果编译时打开了pci支持,那么这个选项是默认的
pci: enabled=1, chipset=i440fx, slot1=pcivga, slot2=ne2k, advopts=noacpi
pci: enabled=1, chipset=i440bx, slot5=voodoo, slot1=e1000
chipset
当前支持i430FX、i440FX和i440BX(有限)芯片组,默认为i440FX。
slotX
可以指定连接到PCI插槽的设备。最多5个插槽。对于PCI/ISA混合设备,如果要模拟PCI模式,要指定插槽(cirrus、ne2k、pcivga)。支持为PCI只设备指定插槽,但如果不指定会自动分配(e1000、es1370、pcidev等)。每个插槽类型只能使用一次。对于i440BX,插槽5是AGP槽。目前只有'voodoo'可以连接到AGP槽。
advopts
使用高级PCI选项可以控制芯片组的行为。以逗号分隔多个选项。默认的“Bochs i440FX”启用ACPI和HPET,但原始i440FX不支持。"noacpi"和"nohpet"可以禁用它们。"noagp"禁用i440BX不完整的AGP子系统。
这里我想大家也不用考虑太多,按照Bochs模拟器默认的配置就好了
7、设置VGA信息
vga:extension=cirrus,extension=vbe,update_freq=60,realtime=1,ddc=file:monitor.bin
这是官方文档里写的配置。其中我们需要注意到的是update_freq这个参数,它代表了你的VGA屏幕的刷新率。VGA 更新频率指定每秒显示更新的次数。 VGA 更新定时器默认使用实时引擎,值为 10(有效范围:1 到 75)。 该参数可以在运行时更改。 特殊值 0 支持使用模拟图形设备的帧速率。我设置为60HZ,这意味着1/60秒钟Bochs会把显示缓冲区里的数据映射到屏幕上。
“realtime”选项指定VGA更新定时器的操作模式。 如果设置为 1,则 VGA 计时器基于实时,否则基于 ips 设置。 如果主机速度较慢(低 ips、update_freq)并且客户机适当地使用 HLT,则将其设置为 0 且“clock:sync=none”可能会在客户机空闲时提高客户机 GUI 的响应能力。 默认值为 1。
参数“ddc”定义返回显示器 EDID 数据的 DDC 仿真的行为。 默认情况下,使用“Bochs Screen”的“内置”值。 其他选择是“禁用”(无 DDC 仿真)和“文件”(从用冒号分隔的文件/路径名读取监视器 EDID)。
8、设置键盘与鼠标
keyboard: type=mf, serial_delay=150, paste_delay=100000
keyboard: keymap=gui/keymaps/x11-pc-de.map
keyboard: user_shortcut=ctrl-alt-del
type - 指定键盘类型(xt/at/mf),默认mf
- xt键盘:
是最早期IBM PC推出的键盘类型,1982年诞生。
采用最简单的电气规格,没有LED指示灯,功能键较少。
- at键盘:
是 IBM AT机型于1984年推出的升级型键盘。
加入了打字机样式的结构,增加了PageUp/Down, Home, End等功能键。
引入了面向插针式连接设计。
- mf键盘:
是微软在1990年推出兼容PC标准的键盘设计。
增加了方向键集成入数字键盘区, Esc键变大,Del键独立出来。
变成我们现在熟悉的104键标准布局。
serial_delay -键盘到控制器的串行延迟,单位微秒
paste_delay - 粘贴字符到控制器的尝试频率,给 guests 的时间应对输入流,默认0.1秒
keymap - 重新映射本地键盘到US标准键盘布局
user_shortcut - 指定用户按钮对应的快捷键组合,最多3个按键名用'-'连接
支持的按键包括Alt/Ctrl/Del等modifier,数字功能键,方向键等。
mouse: enabled=1
mouse: type=imps2, enabled=1
mouse: type=serial, enabled=1
mouse: enabled=0, toggle=ctrl+f10
type - 鼠标类型,默认ps2,也支持imps2(滚动ps2鼠标)、serial串口鼠标等
enabled - 是否创建鼠标事件,0为关闭。但硬件模拟不受影响。
toggle - 运行时切换鼠标 capturing 方法,默认ctrl+鼠标中键,也可以换成ctrl+F10等组合键。
mouse类型可以模拟不同接口的鼠标。enabled控制用户交互,切勿默认开启。
toggle设置运行时方便切换鼠标状态的组合键,如 dosbox 的ctrl+F10。
如图所示,红色框框里的这个按钮就是鼠标进入键,当你点击这个按钮之后,你的鼠标就被Bochs给捕获了,也就是你不能再操控你的主机电脑了。那么该怎么让Bochs把鼠标还给我们呢。
toggle这个参数就是设置了一套按键组合,当你使用了这个组合按键,那么鼠标就会还给主机系统。
mouse: type=ps2, enabled=1, toggle=ctrl+mbutton
默认是鼠标中键,如果大家不小心点进去了,但又拿不回来的话,你可以使用Ctrl + Alt + t打开一个终端,那么这时候鼠标会短暂的还给你,然后你快速点击终端,再用kill关闭Bochs.
pkill -9 bochs
不过这是一个笨方法,是在实在没办法的情况下才使用的,现在大家知道了,这个返还鼠标的组合键是可以自己设置的。
9、设置clock时钟
Syntax:
clock: sync=[none|slowdown|realtime|both], time0=[timeValue|local|utc]
Examples:
clock: sync=none, time0=local # Now (localtime)
clock: sync=slowdown, time0=315529200 # Tue Jan 1 00:00:00 1980
clock: sync=none, time0="Mon Jan 1 00:00:00 1990" # 631148400
clock: sync=realtime, time0=938581955 # Wed Sep 29 07:12:35 1999
clock: sync=realtime, time0="Sat Jan 1 00:00:00 2000" # 946681200
clock: sync=none, time0=1 # Now (localtime)
clock: sync=none, time0=utc # Now (utc/gmt)
默认的配置 sync=none, rtc_sync=0, time0=local
sync控制与主机时间的同步程度,time0设置引导时间,可以指定绝对时间戳或相对主机的本地/UTC时间。slowdown(牺牲性能保持可重复)、realtime(牺牲可重复性实时同步)
这个时钟一般来说就保持默认配置就好,即把你的宿主机操作系统的时间同步到Bochs的虚拟机的硬件RTC里,除非说你需要设定虚拟时间,否则没必要修改。
10、日志信息
示例:
logprefix: %t-%e-@%i-%d
logprefix: %i%e%d
这设置日志行前缀格式字符串。可以使用以下特殊标签:
%t : 11 位十进制定时器计数
%i : 8 位十六进制当前CPU指令指针(SMP模式下忽略)
%e : 1 个字符事件类型(i/d/p/e)
%d : 5 个字符设备名称(在[]中)
默认:%t%e%d
logprefix允许定制日志行前缀的格式,包括时间戳、cpu寄存器、事件类型和设备名等信息。
通过组合特殊标签可以生成结构化的前缀,方便分类和查询日志。
11、调试信息
示例:
debug: action=ignore, pci=report
info: action=report
error: action=report
panic: action=ask
Bochs会产生四类事件:
- debug:用于调试,会产生大量消息
- info:有意义但不常见的事件
- error:不应该出现但不影响模拟的条件
- panic:模拟无法继续运行
这四行分别设置每类事件的响应动作:
- fatal:终止模拟
- ask:询问用户下一步
- warn:显示对话框并继续
- report:输出到控制台
- ignore:忽略
调试信息输出位置
你可以把Bochs生成的调试信息导出到文件然后慢慢观察。
示例:
debugger_log: debugger.out
debugger_log: /dev/null (Unix only)
debugger_log: -
需要指定保存的位置。如果你不需要把调试信息保存到文件里可以把调试信息输出到/dev/null 这个“黑洞”设备里,或者干脆不生成。
类似这样的小配置还有很多,这里我不想一一列举,我就放一个我的配置在这里供大家参考,如果大家有需要,我再一一说明。
print_timestamps: enabled=0
debugger_log: -
magic_break: enabled=0
port_e9_hack: enabled=0
private_colormap: enabled=0
clock: sync=none, time0=local, rtc_sync=0
# no cmosimage
# no loader
log: -
logprefix: %t%e%d
debug: action=ignore
info: action=report
error: action=report
panic: action=ask
12、CPU信息设置
这也是整个bochsrc的核心配置,通过这个配置能决定bochs将模拟哪一块CPU,支持哪些指令集,能够提供什么样的性能,这些都是可以自己配置的。为了让大家比较形象地看到参数的配置方式,我这里先放上我的配置过程供大家参考,随后我会解析每一个配置参数应该怎么填写。
Intel CPU配置如下:
cpu: count=1:4:2, ips=5000000, quantum=16, model=tigerlake, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0, msrs="msrs.def"
cpuid: x86_64=1,level=6, mmx=1,sep=1, aes=1, movbe=1, xsave=1,apic=x2apic,sha=1,adx=1,xsaveopt=1,avx_f16c=1,avx_fma=1,bmi=bmi2,1g_pages=1,pcid=1,fsgsbase=1,smep=1,smap=1,mwait=1,vmx=1,svm=1
cpuid: family=6, model=0x1a, stepping=5, vendor_string="GenuineIntel", brand_string="11th Gen Intel(R) Core(TM) i5-1135G7 (TigerLake)"
AMD CPU配置如下:
cpu: count=1:1:1, ips=4000000, quantum=16, model=ryzen, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0, msrs="msrs.def"
cpuid: x86_64=1,level=6, mmx=1, sep=1, aes=1, movbe=1, xsave=1,apic=x2apic,sha=1,adx=1,xsaveopt=1,avx_f16c=1,avx_fma=1,bmi=bmi2,1g_pages=1,pcid=1,fsgsbase=1,smep=1,smap=1,mwait=1,vmx=1
cpuid: family=6, model=0x1a, stepping=5, vendor_string="AuthenticAMD", brand_string="AMD Ryzen 7 1700"
大家可能觉得这里比较复杂,前面的配置只需要一个或者两个参数就配置好了,而这里却有十余个地方可以配置,如果大家觉得太麻烦,可以直接用我的配置。大家在调试内核的时候不一定非得只用一个bochsrc,像我的话是Intel CPU和AMD CPU各自写了一份bochsrc,那么在调试的时候就可以用来观察Intel和AMD CPU的不同之处。在这里,我们需要做的就是配置好cpu和cpuid这两个参数.
cpu配置的是CPU的具体能力,性能如何?什么样的工作限制?用什么模型?
cpuid就是配置CPU的信息,属于哪一个家族?支持哪些指令集?CPU名字是什么?这里的cpuid其实就是我们熟知的cpuid,你使用x86的指令cpuid查出来的信息就是在这里设置的;你使用CPU-Z之类的软件去查看CPU的时候就会显示这些信息。
由于我在Linux,我就使用开源的CPU-X替代一下CPU-Z。大家可以对比一下,其中的stepping参数就是这里的步进,vendor_string就是型号。这样我们让这二者对比起来,大家现在应该明白这两个参数的重要作用了。虽然Bochs能够模拟的CPU数量并不算特别多,但是可选参数是真的多,我相信也足够大家使用了,如果大家震惊于Bochs模拟器对x86架构CPU仿真的认真和精准,大家不妨可以给Bochs项目点个star,甚至提交pr,让Bochs项目可以发扬光大,只是Bochs对AMD CPU的仿真就不是那么先进,有AMD公司的大牛可以试着帮助其对AMD CPU的仿真。
架构名 | CPU型号 | CPU等级 |
---|---|---|
bx_generic | Default Bochs CPU configured with CPUID option | cpu level 5 |
pentium | Intel Pentium (P54C) | cpu level 5 |
pentium_mmx | Intel Pentium MMX | cpu level 5 |
amd_k6_2_chomper | AMD-K6(tm) 3D processor (Chomper) | cpu level 5 |
p2_klamath | Intel Pentium II (Klamath) | cpu level 6 |
p3_katmai | Intel Pentium III (Katmai) | cpu level 6 |
p4_willamette | Intel(R) Pentium(R) 4 (Willamette) | cpu level 6 |
core_duo_t2400_yonah | Intel(R) Core(TM) Duo CPU T2400 (Yonah) | cpu level 6 |
atom_n270 | Intel(R) Atom(TM) CPU N270 | cpu level 6 |
p4_prescott_celeron_336 | Intel(R) Celeron(R) 336 (Prescott) | cpu level 6, x86-64 |
athlon64_clawhammer | AMD Athlon(tm) 64 Processor 2800+ (Clawhammer) | cpu level 6, x86-64 |
athlon64_venice | AMD Athlon(tm) 64 Processor 3000+ (Venice) | cpu level 6, x86-64 |
turion64_tyler | AMD Turion(tm) 64 X2 Mobile TL-60 (Tyler) | cpu level 6, x86-64 |
phenom_8650_toliman | AMD Phenom X3 8650 (Toliman) | cpu level 6, x86-64 |
core2_penryn_t9600 | Intel Mobile Core 2 Duo T9600 (Penryn) | cpu level 6, x86-64 |
corei5_lynnfield_750 | Intel(R) Core(TM) i5 750 (Lynnfield) | cpu level 6, x86-64 |
corei5_arrandale_m520 | Intel(R) Core(TM) i5 M 520 (Arrandale) | cpu level 6, x86-64 |
corei7_sandy_bridge_2600k | Intel(R) Core(TM) i7-2600K (Sandy Bridge) | cpu level 6, x86-64, avx |
zambezi | AMD FX(tm)-4100 Quad-Core Processor (Zambezi) | cpu level 6, x86-64, avx |
trinity_apu | AMD A8-5600K APU (Trinity) | cpu level 6, x86-64, avx |
ryzen | AMD Ryzen 7 1700 | cpu level 6, x86-64, avx |
corei7_ivy_bridge_3770k | Intel(R) Core(TM) i7-3770K CPU (Ivy Bridge) | cpu level 6, x86-64, avx |
corei7_haswell_4770 | Intel(R) Core(TM) i7-4770 CPU (Haswell) | cpu level 6, x86-64, avx |
broadwell_ult | Intel(R) Processor 5Y70 CPU (Broadwell) | cpu level 6, x86-64, avx |
corei7_skylake_x | Intel(R) Core(TM) i7-7800X CPU (Skylake) | cpu level 6, x86-64, avx |
corei3_cnl | Intel(R) Core(TM) i3-8121U CPU (Cannonlake) | cpu level 6, x86-64, avx |
corei7_icelake_u | QuadCore Intel Core i7-1065G7 (IceLake) | cpu level 6, x86-64, avx |
tigerlake | 11th Gen Intel(R) Core(TM) i5-1135G7 (TigerLake) | cpu level 6, x86-64, avx |
大家不要着急,你先看一看我上面放的表,这也是官方文档里的表,这张表非常重要。
-
bx_generic 是Bochs的默认CPU定义,根据CPUID指令参数进行配置。
-
pentium, pentium_mmx等是Intel各个年代的桌面CPU,如Pentium,Pentium MMX等。
-
amd_k6_2_chomper是AMD K6系列CPU。
-
p2_klamath是Intel Pentium II系列。
-
p3_katmai是Intel Pentium III系列。
-
p4_willamette是Intel Pentium 4系列中的Willamette核心。
-
core_duo_t2400_yonah是Intel Core Duo移动CPU。
-
atom_n270是Intel Atom系列低功耗CPU。
-
athlon64_clawhammer等是AMD第一代Opteron和Athlon 64系列CPU。
-
turion64_tyler是AMD Turion 64移动CPU。
-
phenom_8650_toliman是AMD Phenom系列CPU。
-
core2_penryn_t9600是Intel Core 2 Duo笔记本CPU。
-
corei5/i7系列代表Intel各代酷睿CPU,从Lynnfield到现代的Tiger Lake都有支持。
-
zambezi是AMD FX四核CPU。
-
trinity_apu是AMD时代第一代整合GPU的CPU。
-
ryzen代表AMD现代Ryzen家族CPU。
①cpu的各个参数:
count指的是处理器的拓扑结构,它是以:符号分隔,这是SMP体系结构里的重要概念。
其中1:4:2 的含义是当前计算机系统有1块CPU,每块CPU拥有4个物理核心,每个物理核心拥有两个逻辑线程。总而言之就是,当前计算机CPU是4核心8线程。请注意,Bochs对CPU的配置自由度还是比较高的,虽然现实里你模拟的这块CPU是4核心8线程,但实际上这个count参数你可以自己设置,比如你设置成 1:1:1 那么它就会变成单核CPU,不管你后面cpuid里写的是哪一块CPU。相信大家也猜到了,这么count的数量不可能可以设置成无限大,它实际上受限于对APIC的仿真能力。大家可以尝试一下它最多模拟多少逻辑核心。这里稍微注意一下,由于bochs对AMD CPU仿真能力还没跟上时代,如果大家想做SMP的实验请使用Intel的CPU.这个参数里面总的逻辑线程数量越大,初始化的时间就越长,请大家按照需求来考虑。
ips指的是每秒钟能够处理的指令的数量,这个参数非常能够反映CPU的性能,下面一个参考表格。
Bochs | Speed | Machine/Compiler | Typical IPS |
---|---|---|---|
2.4.6 | 3.4Ghz | Intel Core i7 2600 with Win7x64/g++ 4.5.2 | 85 to 95 MIPS |
2.3.7 | 3.2Ghz | Intel Core 2 Q9770 with WinXP/g++ 3.4 | 50 to 55 MIPS |
2.3.7 | 2.6Ghz | Intel Core 2 Duo with WinXP/g++ 3.4 | 38 to 43 MIPS |
2.2.6 | 2.6Ghz | Intel Core 2 Duo with WinXP/g++ 3.4 | 21 to 25 MIPS |
2.2.6 | 2.1Ghz | Athlon XP with Linux 2.6/g++ 3.4 | 12 to 15 MIPS |
你可以根据你的需要来调整仿真的CPU的性能,数量越大,仿真的CPU性能越强,大家自行考虑。
quantum指的是在将控制权返回到另一个 cpu 之前允许处理器执行的最大指令量。
-
当Bochs在SMP模式下运行时,它会轮流地让每个CPU执行一小段代码,然后切换到下一个CPU上执行。这个一个CPU执行的最多指令数量就叫做"quantum"。
-
小的quantum值意味着CPU间切换的频率更高,每一个CPU得到运行的时间slice更短。大的quantum值则相反,每个CPU可以连续运行更长时间。
-
调整这个quantum参数可以控制Bochs模拟的多个CPU的时间片轮转频率,如何平衡它们的负载和响应时间。
-
太小的quantum可能会造成过多的上下文切换开销,太大的quantum则可能导致某个CPU独占超长时间不让其他CPU执行。
如果大家不知道这是什么意思,请按照我的设置来做。
model这个参数我相信大家应该不陌生,它其实就是我上面那个表格里的CPU架构,你选择了哪一块CPU,那么就把那块CPU对应的CPU架构的值复制粘贴到这里来。比如我选择了11th Gen Intel(R) Core(TM) i5-1135G7 (TigerLake)这一块CPU,那么我就应该设置model值为tigerlake.
reset_on_triple_fault
这个参数的含义是当发生三重错误时,Bochs的行为,你设置为1就好。意味着当发生三重错误时,Bochs会重启,物理机也会是这样。所以当你的内核无缘无故重启的时候,你就要看看是否发生了三重错误,三重错误意味着遇到了一些离了个大谱的问题,不重启不行了。
cpuid_limit_winnt
这个参数固定设置为0就好了,完全就是为了兼容旧的WIndowsNT.
Windows NT在引导过程中,仅能支持CPUID功能号 ≤ 2的处理器。更高号的CPUID信息它无法识别和使用。
但是现实中的CPU的CPUID功能号往往大于2。如果直接使用复杂的真CPU特性,Windows NT就可能无法正常启动。
所以通过设置cpuid_limit_winnt=1,Bochs会限制模拟CPU的最大CPUID功能号为2。
这可以起到“欺骗”Windows NT,让它以为CPU仅支持较低功能,从而顺利完成安装和引导
ignore_bad_msrs
MSR寄存器为x86架构中非常重要的一系列用于控制CPU运行、功能开关、调试、跟踪程序执行、监测CPU性能等方面的寄存器。但是如果你访问到不识别的MSR地址,原本应该是会出发GP通用保护异常的,而Bochs为了兼容更多的CPU,并不模拟所有的MSR寄存器。因此这个参数的含义就是忽略错误的MSR寄存器访问,就是说就算你读写了一个不存在的MSR地址,也不会出发GP异常。ignore就是忽略的意思。
mwait_is_nop
mwait是一种机制,就是让CPU在低功耗下运行,物理机当然可以这么做,但是Bochs想实现这种机制,难度实在太大。因此这个选项的含义就是把你的mwait指令给替换成nop指令,nop指令执行起来很简单。你可以关闭这个转换机制。
msrs
MSR是模型专有寄存器,不同CPU型号的MSR种类和属性各不相同。
Bochs内置仅支持部分常见MSR,无法覆盖所有真实CPU的MSR。因此需要你来指定模拟文件的位置。你可以按照我的写法写。
#
# ----------------------------------
# Bochs CPU MSRs configuration
# ----------------------------------
#
# LEGEND:
# ------
#
# MSR ADDRESS - MSR address in hex (supplied in ECX register for RDMSR/WRMSR)
# MSR TYPE - MSR type, see below
#
# The following fields have any meaning for MSRs with no type only:
#
# RESET_HI - reset value of the MSR (bits 63:32)
# RESET_LO - reset value of the MSR (bits 31:00)
#
# NOTE: the value of the MSR doesn't change on INIT (software reset).
#
# RSRVD_HI - mask of reserved bits (bits 63:32)
# RSRVD_LO - mask of reserved bits (bits 31:00)
#
# NOTE: #GP fault will be generated when trying to modify any of MSR
# reserved bits.
#
# IGNRD_HI - mask of ignored bits (bits 63:32)
# IGNRD_LO - mask of ignored bits (bits 31:00)
#
# NOTE: Ignored bits will keep their reset value, all writes to these
# bits are ignored.
#
# MSR TYPES:
# ---------
#
# 0 - No type.
# 1 - MSR contains linear address,
# #GP if writing non-canonical address in 64-bit mode.
# 2 - MSR contains physical address,
# #GP if writing a value which exceeds emulated physical address size.
#
# ADDRESS TYPE RESET_HI RESET_LO RSRVD_HI RSRVD_LO IGNRD_HI IGNRD_LO
# ---------------------------------------------------------------------------------
0x02c 0 00000000 00000000 00000000 00000000 00000000 00000000
有兴趣的了解一下就好,大多数人不需要看懂这些文件。
②cpuid的各个参数
level是模拟的CPU级别,你选好要仿真的CPU之后直接查表,是5就设置为5,是6就设置为6
mmx=1则打开了mmx指令集。MMX 主要用于通过硬件加速支持整数SIMD 操作。
sep=1则打开了SEP指令集的支持,这是sysenter和sysexit,我相信写过内核的对这两条指令应该不陌生,它们能快速地在内核态与用户态之间进行切换,快速系统调用。
aes=1则打开了AES指令集的支持,这是加密、解密相关的指令集。
相信经过我上面的这几个例子,大家已经知道这几个参数该怎么写了,开启就=1,关闭就=0.我们现在要弄懂的就是每个指令集提供的功能是什么。我帮大家做了一个表格。
指令集/可选参数名 | 描述 | 使用条件 |
mmx | 多媒体扩展指令集 | BX_CPU_LEVEL>=5 |
apic | 高级可编程中断控制器 | BX_CPU_LEVEL>=5 |
sep | 快速系统调用与返回 | BX_CPU_LEVEL>=6 |
simd | 单指令多数据流指令集支持(SSE SSE2 SSE3 SSSE3 SSE4_1等) | >=6 |
sse4a | AMD sse4_a支持 | >=6 |
misaligned_sse | AMD错位SSE模式支持 | >=6 |
aes | aes指令集支持 | >=6 |
sha | sha指令集支持 | >=6 |
movbe | ~ | >=6 |
adx | ADCX/ADOX支持 | >=6 |
xsave | XSAVE拓展支持 | >=6 |
xsaveopt | XSAVEOPT指令支持 | >=6 |
avx_f16c | AVX float16转换指令支持 | 编译时启用--enable-avx |
avx_fma | AVX融合乘加(FMA)指令支持 | 编译时启用--enable-avx |
bmi | BMI1/BMI2指令支持 | 编译时启用--enable-avx |
fma4 | AND四操作数FMA指令支持 | 编译时启用--enable-avx |
xop | AMD XOP指令支持 | 编译时启用--enable-avx |
tbm | AMD TBM指令支持 | 编译时启用--enable-avx |
x86_64 | 启用长模式支持 | 需要x86-64支持 |
1g_pages | 启用1GB大小页面支持 | 需要x86-64支持 |
pcid | 启用进程上下文标识符OCID支持 | 需要x86-64支持 |
smep | 启用监督式执行保护支持 | >=6 |
smap | 启用监督式预防支持 | >=6 |
mwait | 选择monitor/mwait指令支持 | 编译时启用--enable-monitor-mwait |
vmx | 选择VMX扩展模拟支持 | 编译时启用--enable-vmx |
svm | 选择AMD SVM安全虚拟机扩展支持 | 编译时启用--enable-svm |
- Family: 表示CPU家族,如Intel Sandy Bridge为6等
- Model: 表示CPU型号,不同产品会有不同model值
- Stepping: 表示片上细化版本
- stepping字段表示CPU的微型号,数字越大表示芯片做工水平越高
这几个参数就是你在bochs启动之后使用cpuid指令查出来的信息,就是在这里设置的。你可以封装这些从cpuid查出来的信息,写成类似CPU-Z 的带有图形界面的程序。
vendor_string这是CPU的厂商标识符
已知的制造商以及其ID如下:
"AMDisbetter!"
– AMD K5的早期工程样品"AuthenticAMD"
– AMD"CentaurHauls"
– IDT/半人马座(Centaur)/兆芯"CyrixInstead"
– Cyrix/意法半导体/IBM"GenuineIntel"
– 英特尔"TransmetaCPU"
– 全美达"GenuineTMx86"
– 全美达"Geode by NSC"
– 国家半导体"NexGenDriven"
– NexGen"RiseRiseRise"
– Rise"SiS SiS SiS "
– 硅统"UMC UMC UMC "
– 联华电子(台联电)"VIA VIA VIA "
– 威盛"Vortex86 SoC"
– DM&P Vortex86" Shanghai "
– 兆芯"HygonGenuine"
– 海光"Genuine RDC"
– RDC"E2K MACHINE"
– MCST Elbrus
我在这里放了这么多个厂商,并不是都能放进bochs里,总之要模拟哪个厂商的CPU,这个字符串就填写哪一个就好了。
brand_string这也是一个字符串,是CPU的型号或者说是名字,取值只能是前面表格里的。
相信大家看到这里已经跃跃欲试了,想自己写CPU的配置文件出来,那么我在这里举几个例子,大家看了之后就一切都清楚了。
配置文件1及其效果:
cpu: count=1:4:2, ips=10000000, quantum=16, model=tigerlake, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0, msrs="msrs.def"
cpuid: x86_64=1,level=6, mmx=1,sep=1, aes=1, movbe=1, xsave=1,apic=x2apic,sha=1,adx=1,xsaveopt=1,avx_f16c=1,avx_fma=1,bmi=bmi2,1g_pages=1,pcid=1,fsgsbase=1,smep=1,smap=1,mwait=1,vmx=1,svm=1
cpuid: family=6, model=0x1a, stepping=5, vendor_string="GenuineIntel", brand_string="11th Gen Intel(R) Core(TM) i5-1135G7 (TigerLake)"
上面的Bochs图就是我使用CPUID这条汇编指令查看到了CPU信息。
配置文件2及其效果:
cpu: count=1:1:1, ips=4000000, quantum=16, model=ryzen, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0, msrs="msrs.def"
cpuid: x86_64=1,level=6, mmx=1, sep=1, aes=1, movbe=1, xsave=1,apic=x2apic,sha=1,movbe=1,adx=1,xsaveopt=1,avx_f16c=1,avx_fma=1,bmi=bmi2,1g_pages=1,pcid=1,fsgsbase=1,smep=1,smap=1,mwait=1,vmx=1
cpuid: family=6, model=0x1a, stepping=5, vendor_string="AuthenticAMD", brand_string="AMD Ryzen 7 1700"
大家看见了,我修改了配置文件之后,CPUID查看到的信息也会跟着改变。
配置文件3及其效果:
cpu: count=1:4:2, ips=10000000, quantum=16, model=corei7_skylake_x, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0, msrs="msrs.def"
cpuid: x86_64=1,level=6, mmx=1,sep=1, aes=1, movbe=1, xsave=1,apic=x2apic,sha=1,adx=1,xsaveopt=1,avx_f16c=1,avx_fma=1,bmi=bmi2,1g_pages=1,pcid=1,fsgsbase=1,smep=1,smap=1,mwait=1,vmx=1,svm=1
cpuid: family=6, model=0x1a, stepping=5, vendor_string="GenuineIntel", brand_string="Intel(R) Core(TM) i7-7800X CPU (Skylake)"
相信大家已经总结出规律了:如果你想换另外一块CPU进行模拟仿真,那么你只需要对照我前面给你的那张表格,修改model,ventor_string,brand_string即可。
cpuid这个参数还是非常重要的,它代表了你要模拟仿真的CPU具备哪些指令集的支持,你在做内核编程的时候在使用较为先进的指令之前也需要对它进行判断,只有你的CPU支持该指令集,你才可以使用这条指令,否则的话CPU就会报#UD异常。这里我需要提醒一下,Bochs的I/O APIC和HPET这两个设备是默认打开的,大家在使用Bochs模拟这两个设备的时候不需要去ACPI找它们的地址也不需要去主板上的芯片找,直接跳过开启阶段,直接使用就好。大家在设置指令集支持情况的时候,除非你说你就是在要没有硬件指令集支持的情况下做软件支持,否则的话你就尽可能打开更多的指令集,这样会更加贴近你的pc机器的实际情况,因为现代x86计算机支持的指令集已经是非常多了。
相信大家看到这里,已经对bochsrc配置有了一定的了解。我上面讲述的配置并不完善,还有一些其他的配置,希望大家能够看英文的官方文档。如果你是一个初学者,可以照着我的描述写自己的配置。
megs:2048
#设置插件
plugin_ctrl:unmapped=1,biosdev=1,speaker=1,extfpuirq=1,parallel=1,serial=1,iodebug=1
#bochs调试选项
config_interface:textconfig
display_library:x
#设置ROM和VGA模拟
romimage:file="/usr/share/bochs/BIOS-bochs-latest"
vgaromimage:file="/usr/share/bochs/VGABIOS-lgpl-latest"
#设置软盘启动
#boot:floppy
#floppy_bootsig_check:disabled=0
#floppya:type=1_44,1_44="./tools/boot.img",status=inserted,write_protected=0 #3.5英寸高密度软盘
#设置硬盘启动
boot:disk
ata0:enabled=1,ioaddr1=0x1f0,ioaddr2=0x3f0,irq=14
ata0-master:type=disk,path="./tools/boot.img",mode="flat",translation=lba #主盘
ata0-slave:type=none
ata1:enabled=1,ioaddr1=0x170,ioaddr2=0x370,irq=15
ata1-master:type=none
ata1-slave:type=disk,path="./tools/hd60M.img",mode="flat",translation=lba
ata2:enabled=0
ata3:enabled=0
pci:enabled=1,chipset=i440fx
vga:extension=cirrus,extension=vbe,update_freq=60,realtime=1
cpu: count=1:4:2, ips=10000000, quantum=16, model=tigerlake, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0, msrs="msrs.def"
cpuid: x86_64=1,level=6, mmx=1,sep=1, aes=1, movbe=1, xsave=1,apic=x2apic,sha=1,adx=1,xsaveopt=1,avx_f16c=1,avx_fma=1,bmi=bmi2,1g_pages=1,pcid=1,fsgsbase=1,smep=1,smap=1,mwait=1,vmx=1,svm=1
cpuid: family=6, model=0x1a, stepping=5, vendor_string="GenuineIntel", brand_string="11th Gen Intel(R) Core(TM) i5-1135G7 (TigerLake)"
print_timestamps: enabled=0
debugger_log: -
magic_break: enabled=0
port_e9_hack: enabled=0
private_colormap: enabled=0
clock: sync=none, time0=local, rtc_sync=0
# no cmosimage
# no loader
log: -
logprefix: %t%e%d
debug: action=ignore
info: action=report
error: action=report
panic: action=ask
keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none
keyboard: user_shortcut=ctrl-alt-del
mouse: type=ps2, enabled=1, toggle=ctrl+mbutton
speaker: enabled=1, mode=sound
sb16:midimode=2,midifile=output.mid,wavemode=3,wavefile=output.wav,dmatimer=900000
sound: waveoutdrv=sdl,waveindrv=alsa,,waveout=dunmy
parport1: enabled=1, file=none
parport2: enabled=0
com1: enabled=1, mode=null
com2: enabled=0
com3: enabled=0
com4: enabled=0
clock:sync=none, rtc_sync=0, time0=local
voodoo:enable=1,model=voodoo1
#gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0
#该配置在使用gdb调试的时候使用,port=1234指明gdb调试的端口号
#你在另一个终端执行gdb命令,然后执行target remote :1234就可以连接到bochs
#如果你使用bochs自带的调试器,就不能在配置里出现gdbstub的配置(二者是水火不容)
我这里给出我的完整的配置,大家可以在它的基础上修修改改,符合自己的实际情况就好。
其中还有一个参数叫voodoo,这是Bochs模拟器支持的Voodoo图形卡的仿真。Voodoo图形卡是3dfx公司在90年代推出的一系列著名的视频卡,主要用于加速3D计算机图形的渲染。按照Bochs官方文档的说法,Bochs提供了几种型号的图形卡的仿真支持:
- Voodoo1
- Voodoo2
- Banshee
- Voodoo3
我在配置文件里写的是Voodoo1,因为它已经完整的支持了,实际上你可以改成Voodoo2,文档里说这个模型的支持已经基本完成,可以是用了。其他两张卡还在建设中。你可以认为:Bochs模拟器提供的VBE显示缓冲区的地址为0xe0000000这个地方,Bochs会把这个区域里的数据映射到“显卡”的显存里,你只需要往这个地方写入数据,就会在屏幕上相应的位置显示像素点。
五、Bochs调试内核
在编写好配置文件后,就可以执行bochs -f /bochsrc来启动bochs,当然你可以把这一步写近Makefile或者Shell脚本里。
我就介绍一下命令行调试内核的方法,Bochs本身是可以用图形界面调试的,只是我用不习惯gui,还是命令行原汁原味好一点。我会在这个章节演示一下我调试内核的场景。
请注意:如果你想调试内核,那么你在执行./configure的时候必须开启Bochs内置的调试器!
./configure --enable-debugger
如果Bochs运行正常的话会进入这样一个界面。
对于我们来说,直接使用6开始调试就可以了。如果你不想调试,你可以使用7退出bochs.
这就是Bochs刚开启时候的界面。左上角是Bochs的logo,右上角有一个x按钮,你点击它是不能够关闭Bochs的,因为你是在终端里打开,你想关闭Bochs就只能在终端里与Bochs进行交互的时候,使用q命令才能正常关闭Bochs,除此之外,你还可以使用我下面将要介绍的Power按钮关闭Bochs.
第二行有一些列的按钮,我挨个介绍他们的作用:
- 软盘按钮:切换软盘介质的状态(inserted/ejected) 有两个软盘按钮,左边那个控制floopya,右边那个控制floopyb;
- CD-ROM按钮:切换第一个CD-ROM驱动器的介质状态;
- 鼠标按钮:是否捕获主机系统的鼠标(这个我之前着重讲过了);
- 用户按钮(USER):向客户操作系统(模拟器里运行的系统)发送预定义的键盘快捷键;
- 复制按钮(Copy):将文本模式屏幕上的文本导出到你的剪贴板;
- 粘贴按钮(Paste):将你的主机系统剪贴板里的东西作为模拟按键输入到虚拟机里
- 快照按钮(Snapshot):保存Bochs屏幕的快照,就是截图的意思;
- 配置按钮(CONFIG):停止模拟并启动运行时配置
- 重置按钮(Reset):触发模拟的硬件重置
- 挂起按钮(Suspend):将当前模拟状态保存到磁盘,稍后可以使用bochs -r命令恢复
- 电源按钮(Power):停止模拟并退出Bochs
大家不用专门去记忆这些按钮是干嘛的,它上面写着英文,理解一下就好。
我这里着重再讲一下这个配置按钮,就是一个锤子一个扳手、下面写着CONFIG的那个按钮,你在Bochs正在运行的时候点击这个按钮,就会进入如图所示的界面。
你可以在这个界面里更改软盘或CD-ROM的镜像或设备、调整日志选项、USB选项等。
大家可以看到,Bochs提示了一些“reset”的初始化信息,并且默认使用的CPU核心是CPU0(Switching to CPU0),可以通过命令去设置查看当前选择的CPU信息,这个我以后再说。
因为我现在开启了4核心8线程,因此可以看到8条一模一样的信息:
(0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0
这是刚启动时候的CPU地址,此时BIOS还没有开始执行自检的代码,也就是开机的时候,执行地址位于0xffff0这个地方(CS=0xf000,IP=fff0),相信大家学过8086汇编语言的都知道这个。
它执行的第一行代码就是 jmpf 0xf000:e05b ,这个地方位于主板上的ROM映射到内存的区域,跳过去之后执行一些BIOS自检代码。自检完成之后最终会跳到0x7c00这个MBR地址,也就是你设置的磁盘的0磁头0柱面1扇区这个地方,ROM里的代码会自动把磁盘上的该扇区放置到0x7c00 - 0x7dff(共512字节)。
1、执行控制命令
这也是Bochs中最常用的命令,Bochs模拟器刚开启的时候是一条你的代码也没有执行的,甚至连你的MBR都还没有加载到内存中呢!
①执行到底
c
cont
continue
短的是简写,方便调试
意思就是Bochs一直往后面执行,像真的CPU一样,不停地执行机器码,直到遇到断点(实际上我们的调试就是基于这个,让ip在执行到错误的指令或者访问到错误的数据之前停下来,然后反汇编查看是否有什么错误),或者遇到悬停指令(jmp $ / jmp .),它才会停下来。如果遇到重大错误(不可能恢复的那种),Bochs会直接重启,然后会和你执行c命令之前一样的状态,等待着你的控制和调试。
这里我推荐大家记忆的时候记全名,然后实际调试的时候,用缩写。
②单步执行
s [count]
step [count]
执行count条指令,这个count默认为1.
注意这个[]是表示可选参数的意思,你调试的时候不要写[]上去。
s [cpu] [count]
step [cpu] [count]
这个单步调试还可以指定CPU,当你开启SMP以后,那么你就有多个CPU执行单元,你可以选择其中一个执行单元执行count条指令
s all [count]
s all [count]
你当前的所有CPU执行单元同时执行count条指令
两个重要快捷键:
Ctrl+C : 停止执行,控制权交还给用户。
Ctrl+D:在停止执行的状态退出Bochs,相当于q、quit、exit命令,它们的作用是一样的,都是正常退出Bochs,如果你是以不正常的方法退出Bochs,则会留下一些lock文件保护你的硬盘映像什么的。
③打个断点
打断点可以说是整个调试过程里面,最重要的一部分了,
pbreak addr
pb addr
break addr
b addr
上面这四条指令的含义是一样的,就是在物理地址addr这块地方设置一个断点,这里的p的含义就是physical, 我们通常来说使用这条指令就足够了。这里注意一下,因为内存地址我们常用16进制来表示,所以你在写这个addr的时候假如你是16进制,请你加上0x开头,否则就是10进制的地址了。如果你要用8进制作为地址那也可以,使用0开头。
hexidecimal: 0xcdef0123 decimal: 123456789 octal: 01234567
此外,由于x86架构的分段特性,如果仅使用物理地址来指明你要打的断点的内存地址的话,也许是不太方便的,那么Bochs模拟器非常贴心的给出了一些其他适配与Intel分段机制的打断点的方法。
vbreak reg:offset
vb reg:offset
想必大家也看出来这么做的意图了,reg指定断点所在位置的的段地址,offset指定偏移量,如果你在实模式下调试的话,这么做会方便很多。不过我测试下来,b指令好像也能接收这样的参数。
查看断点:
info break
我打的断点会全部被记录下来,并且默认是开启状态,这个是否开启你也可以进行设置。
bpe n 开启断点
bpd n 关闭断点
delete n 删除断点
del n 缩写
d n 更简单的缩写
除了虚拟地址和物理地址之外,还有一种线性地址打断点的方式,这个不太常用啊,我这里就不说了。
以上是代码执行到的断点,也就是只有你的cs:ip执行到了这个地址才会中断,假如没有执行到,那么这个断点其实就是白费的,不起作用的。
下面是数据访问的断点,或者更确切来说是监视点
watch read addr
watch r addr
就是在addr这个内存地址处插入一个监视点,当有代码去访问、去读这块内存地址的时候,会中断它的操作。
那么也有写监视点
watch write addr
watch w addr
大家可以看到,当我读写入数据到这块内存地址的时候,果然中断了。
watch 查看当前所有已设置的监视点
unwatch 取消所有监视点
unwatch addr 取消执行地址的监视点
watch stop 当监视点被访问的时候中断掉
watch continue 当监视点被访问的时候继续模拟,什么也不做
④查看信息
当你已经在断点处中断的时候,你需要查看当前的实时信息,从而进行调试。这里的信息可以是寄存器的值、内存里的数据、代码段里的代码(当前这是反汇编得出来的)。
1、查看寄存器的值
Bochs真的可以查看好多好多信息啊,大家可以则其所需,挑选你需要查看的寄存器查看。在多核心开启的情况下是有多组寄存器的。
命令 | 功能描述 |
---|---|
r|reg|regs|registers | 列出CPU的整数寄存器及其当前值。 |
fp|fpu | 列出所有浮点处理单元寄存器的内容。 |
mmx | 列出所有MMX寄存器的内容。 |
sse|xmm | 列出所有流式SIMD扩展(SSE)寄存器的内容。 |
ymm|zmm | 列出所有高级向量扩展(AVX)寄存器的内容。 |
amx|tile n | 显示AMX技术状态和TILE寄存器的内容。 |
sreg | 列出所有段寄存器(如CS, DS等)及其内容。 |
dreg | 列出所有调试寄存器(如DR0, DR1等)及其内容。 |
creg | 列出所有控制寄存器(如CR0, CR3等)及其内容。 |
这里面对重要的应该就是r和sreg,查看通用寄存器信息和段寄存器信息。
比如creg指令,在查看缺页中断的时候你就可以用来查看cr2寄存器的值
命令 | 功能描述 |
---|---|
info cpu | 列出所有CPU寄存器及其内容。 |
info eflags | 显示解码后的EFLAGS寄存器内容。 |
info break | 显示当前断点的状态信息。 |
info tab | 显示页表地址转换信息。 |
info idt | 显示中断描述符表(IDT)的内容。 |
info gdt | 显示全局描述符表(GDT)的内容。 |
info ldt | 显示局部描述符表(LDT)的内容。 |
info device | 显示指定设备的状态。 |
以上是一些info类的指令,它用来查看特殊寄存器的信息。从这里我们不难看出,Bochs对于x86体系结构相关的信息查看是更加全面和底层的,Bochs的开发者对于x86体系结构是非常熟悉了解的,所以对于手写内核的人来说,Bochs是一个更加适合入门的工具,对x86体系结构的学习更加好。但是它的缺点就是当你用c语言写内核的时候,它无法像gdb那样使用gcc -g添加的额外调试信息来帮助调试c代码。所以当你已经熟悉x86体系结构的时候,当你已经脱离MBR和loader进入kernel的时候,你不妨就可以替换成Qemu+gdb,这样调试c代码会更加方便。否则的话你只能在c语言写的内核里自己构建更加强大的调试和日志功能,使得你在代码出问题的时候方便查探到是哪一个内存地址、哪一条机器指令导致的出错。
Bochs除了能查看寄存器的信息,甚至还可以修改某些寄存器的值。注意:你只能用来修改通用寄存器和ip寄存器,其他寄存器的值你不能修改。
set eax = 2+2/2
set esi = 2*eax+ebx
注意寄存器名称之后需要有一个空格来分隔,你可以直接写一个立即数上去,也可以使用别的寄存器的值参与计算
<bochs:1> r
CPU0:
rax: 00000000_00000000
rbx: 00000000_00000000
rcx: 00000000_00000000
rdx: 00000000_00000000
rsp: 00000000_00000000
rbp: 00000000_00000000
rsi: 00000000_00000000
rdi: 00000000_00000000
r8 : 00000000_00000000
r9 : 00000000_00000000
r10: 00000000_00000000
r11: 00000000_00000000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: 00000000_0000fff0
eflags: 0x00000002: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf af pf cf
<bochs:2> set eax = 0x1234
<bochs:3> set ebx = 0xabcd
<bochs:4> set ecx = eax+ebx
<bochs:5> r
CPU0:
rax: 00000000_00001234
rbx: 00000000_0000abcd
rcx: 00000000_0000be01
rdx: 00000000_00000000
rsp: 00000000_00000000
rbp: 00000000_00000000
rsi: 00000000_00000000
rdi: 00000000_00000000
r8 : 00000000_00000000
r9 : 00000000_00000000
r10: 00000000_00000000
r11: 00000000_00000000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: 00000000_0000fff0
eflags: 0x00000002: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf af pf cf
<bochs:6>
怎么样,是不是非常有意思?可以在Bochs的调试窗口修改寄存器的值。当然,你也可以修改rip的值,但是修改之前你要小心,要是rip指向了你不知道的地址,那么出错是很正常的,你可以在内存的某个固定的地方写入一个调试函数,然后一旦出现什么错误,你就可以在Bochs里修改rip是它进入这个函数里,然后打印你需要的信息。
2、查看内存里的数据
xp /nuf addr 查看内存物理地址内容
x /nuf addr 查看内存线性地址内容
其中的nuf指的是:
- n(数量):指定要显示的单位数量。
- u(单位大小):
b
:字节h
:半字(2字节)w
:字(4字节)g
:巨字(8字节)
- f(格式):
x
:十六进制d
:十进制u
:无符号十进制o
:八进制t
:二进制
比如我想看看我的MBR的二进制数据
xp /256hx 0x7c00
那么我就可以查看物理地址从0x7c00开始256个半字(总共512B),以16进制的形式打印。
通常来说,我们只需要查看内存里的内容已经能够满足需求了,但是Bochs还有更多的操作。
setpmem 0x7dfe 2 0x55aa
使用setpmem指令可以修改内存里的值,比如说我这里要修改0x7dfe这个内存地址的2B,交换0x55和0xaa的顺序。
内存地址里的值修改成功!我尝试了一下,在交换了0x55和0xaa的顺序之后代码还是正常运行的,看来检测OBR是否为OBR的过程是在加载MBR的过程中做的,也就是BIOS自己的代码。
转储指定内存地址的数据到文件
writemem "mbr.bin" 0x7c00 512
这就是把0x7c00这个内存地址开始512字节的部分存储到指定的文件里,这里我设置成了mbr.bin,请注意文件名一定要用双引号。
这确实是我写的mbr
loadmem "filename" addr
把文件里的数据加载到addr指定的内存里。有了这个工具,你就可以暂时不从文件系统里读出你的loader和kernel而是直接写入到内存里,然后用jmp指令跳过去。
deref addr deep
进行多层解引用操作。例如,deref rax 3
表示解引用寄存器 rax
三次。
crc addr1 addr2
crc addr1 addr2
:计算从 addr1
到 addr2
的物理内存范围内的CRC32校验值。
上面这两条指令是在是不常用到,大家了解一下就好了。
3、反汇编
在正式介绍反汇编之前,有两条指令需要大家知道一下
trace on
trace off
trace是追踪的意思,当你每执行一条指令之后都会被记录下来,在关闭的情况下,你使用c执行到底的话,它是不会记录反汇编的结果的。
trace-mem on
trace-men off
在内存追踪模式开启的情况下,程序对内存的每一次访问都会被记录和显示,包括读取和写入操作。这对于分析程序如何与内存交互、诊断内存访问错误非常有帮助。
在你的程序加载和运行之前,你可以进行静态的反汇编,即使用objdump等工具进行反汇编。我相信每一位内核开发者这个工具都是不陌生的,我这里再强调一下 --- objdump确实不可或缺。
一下是objdump命令的参数
选项 | 长格式 | 描述 |
---|---|---|
-a | --archive-headers | 显示归档文件的头信息。 |
-b | --target=bfdname | 指定目标文件的二进制格式。 |
-C | --demangle[=style] | 解析低级别符号名称到用户级别名称。 |
-d | --disassemble | 只反汇编可能包含指令的段。 |
-D | --disassemble-all | 反汇编所有非空非BSS段。(重要) |
-f | --file-headers | 显示文件的头部信息。 |
-g | --debugging | 显示调试信息。 |
-h | --section-headers 或 --headers | 显示节头信息。 |
-i | --info | 显示所有支持的架构和对象格式列表。 |
-l | --line-numbers | 在反汇编输出中显示源代码行号。 |
-S | --source | 显示源代码。 |
-t | --syms | 显示符号表。 |
-x | --all-headers | 显示所有头信息,包括符号表和重定位条目。 |
-M | --disassembler-options | 为反汇编器指定目标特定的信息。 |
下面我开始介绍Bochs自带调试器的反汇编功能:
u start end
反汇编一段内存,从start开始到end结束。
<bochs:2> u 0x100000 0x100020
0000000000100000: ( ): mov ax, 0x0010 ; 66b81000
0000000000100004: ( ): mov ds, ax ; 8ed8
0000000000100006: ( ): mov es, ax ; 8ec0
0000000000100008: ( ): mov fs, ax ; 8ee0
000000000010000a: ( ): mov ss, ax ; 8ed0
000000000010000c: ( ): mov esp, 0x00007e00 ; bc007e0000
0000000000100011: ( ): lgdt ds:[rip+96176] ; 0f0115b0770100
0000000000100018: ( ): lidt ds:[rip+100275] ; 0f011db3870100
000000000010001f: ( ): mov ax, 0x0010 ; 66b81000
能够在Bochs的调试器界面看到自己写的汇编代码,是不是很感动,很开心?
反汇编更多的时候是用在调试的时候,当你出错的时候你去探测出错的代码的位置,然后反汇编这段地址,看是否是某一条指令的错误或者是反汇编的结果和你的代码的结果不一样。因为gcc编译器可能会指令重排列,它不会严格按照你指示的顺序进行汇编,而是会有自己的优化。
x86架构的指令是非定长指令,也就是每一条指令的机器码的长度不是固定的,这和risc-v等定长指令指令集全然不同,risc-v指令集你可以通过机器码反推出对应的汇编指令,但是x86平台你想这么做的话,难度可不是一般的大。因此在x86架构上调试,程序员是相当相当依赖于反汇编器的,可以说你会不会x86架构,就看你反汇编调试的能力是否足够强。
4、多核CPU
Bochs模拟器是支持硬件的SMP功能的,就看你怎么设置了。多核技术就是把多个CPU核心塞进一个CPU里面,而超线程技术是每个处理器核心能够集成两个逻辑处理单元(通常来说是两个)。
实际上每个逻辑核心都有一套自己的寄存器,所以Bochs它不是只能查看一套寄存器的值,而是每一套寄存器的值都能够查看、能够设置。
set $cpu=n
我们使用这样一条指令来设置你当前选择的那个核心,CPU从0开始索引。
<bochs:2> r
CPU0:
rax: 00000000_00000000
rbx: 00000000_00000000
rcx: 00000000_00000022
rdx: 00000000_00000001
rsp: ffff8000_0013fff8
rbp: ffff8000_0013fff8
rsi: 00000000_0000000a
rdi: 00000000_00000010
r8 : 00000000_0000ff00
r9 : 00000000_00000000
r10: ffff8000_00104f53
r11: 00000000_00080000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: ffff8000_00104022
eflags: 0x00000246: id vip vif ac vm rf nt IOPL=0 of df IF tf sf ZF af PF cf
<bochs:3> set $cpu=1
Switching to CPU1
<bochs:4> r
CPU1:
rax: 00000000_00000001
rbx: 00000000_00000000
rcx: ffff8000_001250e0
rdx: ffffffff_ffffffff
rsp: ffff8000_0160fec8
rbp: ffff8000_0160fff8
rsi: ffff8000_00142500
rdi: ffff8000_00000000
r8 : 00000000_00080000
r9 : 00000000_00000000
r10: 00000000_00000000
r11: 00000000_00000000
r12: 00000000_00000000
r13: 00000000_00000000
r14: 00000000_00000000
r15: 00000000_00000000
rip: ffff8000_00113b1f
eflags: 0x00000293: id vip vif ac vm rf nt IOPL=0 of df IF tf SF zf AF pf CF
在多核调试的过程中,其实本质和单核调试差不多,就是你在查看寄存器信息之前需要找到对应的CPU核心,而不是直接一个r指令,那样只能查看到CPU0的信息。
⑤通用设置指令
这些指令不是在你调试的时候用的,而是在你正式开始调试前,你要告诉Bochs,你想要什么样的体验。
命令 | 功能描述 |
---|---|
show | 显示当前的 show 模式设置,即当前启用了哪些符号信息显示。 |
show mode | 切换处理器模式切换事件的显示。启用后,每当处理器切换模式时,Bochs 将显示相关信息。 |
show int | 切换中断事件的显示。启用后,每当发生中断时,Bochs 将显示相关信息。 |
show call | 切换调用指令事件的显示。启用后,每当执行调用指令时,Bochs 将显示相关信息。 |
show ret | 切换从中断返回指令(iret)事件的显示。启用后,每当执行 iret 指令时,Bochs 将显示相关信息。 |
show off | 关闭所有符号信息的显示。此命令将禁用上述所有事件的显示。 |
show dbg-all | 打开所有符号信息显示标志。此命令启用所有类型事件(模式、中断、调用、返回)的显示。 |
show dbg-none | 关闭所有符号信息显示标志。此命令禁用所有类型事件的显示。 |
show int的结果
也就是每一次产生中断的时候都会输出日志,告诉程序员你的CPU现在产生中断了,我的内核这里不断地产生时钟中断,每触发一次,就会告诉我一下。这样的做法相当于是触发器-回调函数了,每次触发就会执行对应的函数打印日志。Bochs还具备可定制化的检测和回调,这里我只是说明一下可以这么做,具体怎么做请查看官方文档。
⑥其他指令
命令 | 功能描述 |
---|---|
ptime | 打印自模拟开始以来的当前时间(时钟滴答数)。 |
sb delta | 在未来插入一个以 "delta" 指令为时间间隔的时间断点("delta" 是一个跟随 "L" 的64位整数,例如 1000L)。 |
sba time | 在指定的 "time" 处插入一个时间断点("time" 是一个跟随 "L" 的64位整数,例如 1000L)。 |
print-stack [num words] | 打印栈顶的数个16位字,"num words" 默认为16。仅在受保护模式下且栈段的基地址为零时可靠工作。 |
print-string addr | 从一个线性地址打印一个以 null 结束的字符串。 |
bt [num_entries] | 打印回溯跟踪。 |
source file | 使调试器执行一个脚本文件。 |
addlyt file | 每次执行停止时,使调试器执行一个脚本文件。 |
六、Bochs已实现的功能和未来的计划
这一部分其实已经不是Bochs模拟器的使用教程了,不过既然官方文档讲述了这些内容,我这里就就放一些链接在这里,大家感兴趣的可以去了解一下。