一、实验目的
- 了解ELF符号表的解析
- 进一步完善调试器的功能,理解编译器的设计原理
二、实验步骤
在PA2-1中实现了CPU对指令的解码和执行,在PA2-2中实现了由kernel对ELF文件的程序头表解析以及装载(并且谨记kernel编译出来的指令也是由2-1的CPU来解码的!)。在PA2-3中,我们将要实现对NEMU调试器的完善。例如:给定一个符号(如,全局变量)的名字,返回其在内存中的值。
首先,我们需要了解我们应该怎么去对ELF文件中的符号表和字符串表进行解析。逻辑如下:
其代码位于nemu/src/monitor/elf.c
,里面的函数作用是获取解析出来的值,这可以相当于一个个封装好的接口让我们在后面办成对调试器的完善。
对调试器的完善的代码位于nemu/src/monitor/ui.c
。
先别急着去写代码,我知道你很急但你先别急。我们先来看看NEMU的启动过程:
-
首先我们进入了
/nemu/src/
中的main.c
,这是整个虚拟机的入口。我们来看NEMU的启动命令,参数的输入决定了文件的装载:single_run
函数是怎么处理这参数的呢:可以看到参数被输入了
load_exec
。文件的装载是靠load_exec
函数来实现的,装载的关键在于fread
的调用(自己去查查fread())。这就解释了ELF文件和kernel.img的去向,是什么时候被装进去的:
-
接下来我们就继续走进位于
nemu/src/monitor/ui.c
的ui_mainloop
函数,这个看注释的意思就是这个函数会给你提供NEMU内建的基于字符串界面(CLI)的调试器,让你运行NEMU的时候输入参数来决定NEMU的走向,即调用不同种类的cmd_handler
(有一个cmd_table
,自己去看源码) -
因为我们一开始的参数有一个
--autorun
,所以我们在cmd_handler
中就进入了cmd_c
中,开始不断的执行: -
然后我们就来到
nemu/src/cpu/cpu.c
中的exec
函数了。
好,我们到这里已经理解了NEMU的启动过程,以及这个CLI调试界面是如何工作的。现在我们就要在此之上加上自己的完善,即表达式的求值功能,主要有几个步骤:
-
实现表达式的词法分析
- 为算术表达式中的各种
token
类型添加规则 - 在成功识别出
token
后, 将token
的信息依次记录到tokens
数组中.
- 为算术表达式中的各种
-
实现表达式的递归求值
- 使用BNF完成递归求值。
-
添加变量和函数名支持
- 通过符号表建立变量名和其地址之间的映射关系。
完成后的调试器的功能如下:
命令 | 格式 | 使用举例 | 说明 |
---|---|---|---|
帮助 | help | help | 打印帮助信息 |
继续运行 | c | c | 继续运行被暂停的程序 |
退出 | q | q | 退出当前正在运行的程序 |
单步执行 | si [N] | si 10 | 单步执行N条指令,N缺省为1 |
打印程序状态 | info <r/w> | info r info w | 打印寄存器状态 打印监视点信息 |
表达式求值 | p EXPR | p $eax + 1 | 求出表达式EXPR的值(EXPR中可以出现数字,0x开头的十六进制数字,$开头的寄存器,*开头的指针解引用,括号对,和算术运算符) |
扫描内存 | x N EXPR | x 10 0x10000 | 以表达式EXPR的值为起始地址,以十六进制形式连续输出N个4字节 |
设置监视点 | w EXPR | w *0x2000 | 当表达式EXPR的值发生变化时,暂停程序运行 |
设置断点 | b EXPR | b main | 在EXPR处设置断点。除此以外,框架代码还提供了宏BREAK_POINT,可以插入到用户程序中,起到断点的作用 |
删除监视点或断点 | d N | d 2 | 删除第N号监视点或断点 |