基础设施
简易调试器是NEMU中一项非常重要的基础设施. 我们知道NEMU是一个用来执行其它客户程序的程序, 这意味着, NEMU可以随时了解客户程序执行的所有信息. 然而这些信息对外面的调试器(例如GDB)来说, 是不容易获取的. 例如在通过GDB调试NEMU的时候, 你将很难在NEMU中运行的客户程序中设置断点, 但对于NEMU来说, 这是一件不太困难的事情.
为了提高调试的效率, 同时也作为熟悉框架代码的练习, 我们需要在monitor中实现一个具有如下功能的简易调试器 (相关部分的代码在nemu/src/monitor/debug
目录下), 如果你不清楚命令的格式和功能, 请参考如下表格:
命令
格式
使用举例
说明
帮助(1)
help
help
打印命令的帮助信息
继续运行(1)
c
c
继续运行被暂停的程序
退出(1)
q
q
退出NEMU
单步执行
si [N]
si 10
让程序单步执行N
条指令后暂停执行,
当N
没有给出时, 缺省为1
打印程序状态
info SUBCMD
info r
info w
打印寄存器状态 打印监视点信息
表达式求值
p EXPR
p $eax + 1
扫描内存(2)
x N EXPR
x 10 $esp
求出表达式EXPR
的值, 将结果作为起始内存
地址, 以十六进制形式输出连续的N
个4字节
设置监视点
w EXPR
w *0x2000
当表达式EXPR
的值发生变化时, 暂停程序执行
删除监视点
d N
d 2
删除序号为N
的监视点
备注:
(1) 命令已实现
(2) 与GDB相比, 我们在这里做了简化, 更改了命令的格式
解析命令
NEMU通过readline
库与用户交互, 使用readline()
函数从键盘上读入命令. 与gets()
相比, readline()
提供了"行编辑"的功能, 最常用的功能就是通过上, 下方向键翻阅历史记录. 事实上, shell程序就是通过readline()
读入命令的. 关于readline()
的功能和返回值等信息, 请查阅
从键盘上读入命令后, NEMU需要解析该命令, 然后执行相关的操作. 解析命令的目的是识别命令中的参数, 例如在si 10
的命令中识别出si
和10
, 从而得知这是一条单步执行10条指令的命令. 解析命令的工作是通过一系列的字符串处理函数来完成的, 例如框架代码中的strtok()
. strtok()
是C语言中的标准库函数, 如果你从来没有使用过strtok()
, 并且打算继续使用框架代码中的strtok()
来进行命令的解析, 请务必查阅
另外, cmd_help()
函数中也给出了使用strtok()
的例子. 事实上, 字符串处理函数有很多, 键入以下内容:
其中<TAB>
代表键盘上的TAB键. 你会看到很多以str开头的函数, 其中有你应该很熟悉的strlen()
, strcpy()
等函数. 你最好都先看看这些字符串处理函数的manual page, 了解一下它们的功能, 因为你很可能会用到其中的某些函数来帮助你解析命令. 当然你也可以编写你自己的字符串处理函数来解析命令.
另外一个值得推荐的字符串处理函数是sscanf()
, 它的功能和scanf()
很类似, 不同的是sscanf()
可以从字符串中读入格式化的内容, 使用它有时候可以很方便地实现字符串的解析. 如果你从来没有使用过它们, RTFM, 或者到互联网上查阅相关资料.
单步执行
单步执行的功能十分简单, 而且框架代码中已经给出了模拟CPU执行方式的函数, 你只要使用相应的参数去调用它就可以了. 如果你仍然不知道要怎么做, RTFSC.
打印寄存器
打印寄存器就更简单了, 执行info r
之后, 直接用printf()
输出所有寄存器的值即可. 如果你从来没有使用过printf()
, 请到互联网上搜索相关资料. 如果你不知道要输出什么, 你可以参考GDB中的输出.
扫描内存
扫描内存的实现也不难, 对命令进行解析之后, 先求出表达式的值. 但你还没有实现表达式求值的功能, 现在可以先实现一个简单的版本: 规定表达式EXPR
中只能是一个十六进制数, 例如
这样的简化可以让你暂时不必纠缠于表达式求值的细节. 解析出待扫描内存的起始地址之后, 你就使用循环将指定长度的内存数据通过十六进制打印出来. 如果你不知道要怎么输出, 同样的, 你可以参考GDB中的输出.
实现了扫描内存的功能之后, 你可以打印0x100000
附近的内存, 你应该会看到程序的代码, 和默认镜像进行对比, 看看你的实现是否正确.
Last updated
Was this helpful?