生成更多调试信息
这洞府里可有称手的兵器?
Last updated
Was this helpful?
这洞府里可有称手的兵器?
Last updated
Was this helpful?
相信你已经发现了, 到目前为止 OS lab 实验环境一直都没有支持调试, 可谓万事俱备, 只欠东风.
不用多说你也知道, 调试是一件非常重要的事情, 尤其是在 OS 这类大型软件中. 这也是为什么 Linux 内核在初始化的时候硬是要先提供一个几乎在什么地方都能用的 printk
函数. 还好目前我们并不是要从零开始写个 EOS, 或者是给 EOS 写驱动. 我们的目的只是为了借助调试器, 通过在 EOS 内下断点分析其行为.
在此之前, 一些额外的手段也是必要的.
这种需求并不奇怪: 有的时候我们需要根据函数名来确定其虚拟地址, 或者是根据虚拟机中获取到的虚拟地址来判断当前正在执行哪个函数. 然而, 此时你也许会一脸懵逼, 不知如何是好.
你应该还记得, 我们在构建 EOS 时, 针对每一个 .asm
文件, 不仅输出了 .o
或者 .bin
, 还输出了一个 .lst
文件. 这个文件的内容正是汇编源文件中每条语句对应的二进制机器指令, 以及其所在的内存地址. 对于内核 kernel.dll
来说, 我们也能生成一个类似的东西:
objdump
可以用来输出生成的二进制中的许多信息, 我们通常用它来对二进制进行反汇编. 上述命令会以 (-M intel
) 输出 build/kernel.dll
的反汇编信息 (-d
). 执行后你应该能看到控制台中输出了一大堆内容, 这就是我们需要的信息.
你会说: 刚刚的输出甚至都一眼望不到头, 我们要怎么看清呢? 没关系, 到文件即可:
此时 build/lst/kernel.lst
就已经存储了刚刚输出的反汇编信息.
使用 QEMU 作为虚拟机运行 EOS 可以让我们通过 GDB 远程接入进行调试:
这个命令和之前启动 QEMU 的命令有两点不同:
-S -s
: 前者会让 CPU 启动后立刻暂停, 方便我们调试; 后者会在本机 1234
端口开启一个 GDB stub;
结尾的 &
: 我不说你可能都不会注意到这个符号. 它的目的是让 QEMU 在后台运行, 不要占用终端, 否则我们没办法在终端中操作 GDB.
运行之后, 你会看到弹出窗口中的 QEMU 停住了, 等待我们用 GDB 去远程连接它. 这个时候我们需要在终端中启动 GDB:
你会看到 GDB 已经载入了其中的调试符号. 接着我们开始远程调试:
此时 QEMU 依然不为所动, GDB 输出了以下信息:
并且等待我们的输入, 刚才我们说到 QEMU 目前处于暂停状态, CPU 还在 FFF0h
处等着执行 BIOS 的指令, 于是我们在 GDB 的终端中输入:
此时 QEMU 就会继续执行了.
比方说我们想调试 EOS 中进程的创建, 此时需要在 PsCreateProcess
函数下断点. 我们先回到终端, 按下 Ctrl+C
后, GDB 会开始接受我们的命令:
然后输入 c
继续执行. 此时如果我们在 EOS 中运行之前的 hello.exe
, GDB 会立即停住:
这说明我们的断点起了作用. 之后愿意单步执行, 还是想输出内存地址的值, 一切都随你.
我不会 GDB 怎么办?
你的选择有很多: STFW 找教程, 或者直接 RTFM.
那如果我们想调试 hello.exe
怎么办呢? 这个时候你想直接在 hello.exe
的 main
函数下断点, GDB 会报错:
我们必须首先让 GDB 载入 hello.exe
:
这个时候就可以在 main
函数下断点了:
我们每次打开 GDB 都需要手动载入内核 DLL, 然后再连接 1234
端口的 QEMU. 其实可以在 ~/eos
目录新建一个 .gdbinit
文件:
GDB 会自动执行这两个命令. 需要解释一下: 第二条命令会在 target remote
的同时启动 QEMU, 此时 GDB 会通过管道直接和 QEMU 建立连接; 而 QEMU 这边由于没有接收到参数 -s
, 所以不会在端口 1234
监听 GDB, 取而代之的是参数 -gdb stdio
, 即通过标准输入输出 (管道) 接收 GDB 的命令.
此时 eos
的目录结构为:
为了防止 GDB 由于安全原因不自动加载初始化脚本, 我们需要新建 ~/.gdbinit
并写入:
这样我们就可以更方便的开启调试了, 只需要:
GDB 就会和 QEMU 同时启动, 立即进入调试模式.
目前我们有两个新需求:
每次构建内核时, 生成新的 kernel.lst
;
可以一键开启调试.
更新后的 Makefile 如下:
但是你会发现, 在调试用户应用程序时并不会出现这种情况. GDB 可以完美的读取到用户应用程序的调试符号信息, layout src
一切正常, 并且可以输出所有变量的值.
这个问题教程作者目前依然没有解决, 推测可能由以下原因造成:
MinGW 在编译 DLL 时输出了不能被 GDB 正确读取的调试信息;
NASM 输出了不正确的调试信息, 导致最终连接而成的 DLL 无法正常被 GDB 读取;
GDB 不能正确处理调试 DLL 的情况;
其他未知原因.
上述推测尚未经过任何证实, 如果你有兴趣, 可以尝试自行解决这个问题.
关于 GDB 的详细用法, 你可以参考. 此时我们需要先指定要调试的文件, 因为我们要调试内核, 所以执行:
在目前的调试环境中还存在一个比较严重的问题: 如果尝试使用 GDB 调试 EOS 内核, 并且在调试断点时输入 layout src
, 你会发现 GDB 提示你 No Source Avaliable
. 这会对你的调试体验造成一定的影响, 因为本来你可以使用 p <var_id>
来输出一个变量的值, 但是由于 GDB 找不到源代码, 你目前并不能这么做, 只能通过和 来推断你想要输出的变量的地址.