南京大学ICS PA作业
B站视频链接:计算机系统基础习题课ICS_哔哩哔哩_bilibili
本文针对的是PA1中的RTFSC中 "优美地退出"题目
先来走一遍NEMU运行的流程,先来看main函数(nemu/src/nemu-main.c)
根据文档信息,init_monitor()函数(nemu/src/monitor/monitor.c) 进行一些和monitor相关的初始化工作,和题目没关系,看engine_start()函数,同样根据文档,它会执行engine_start()函数,而在engine_start()函数下会执行sdb_mainloop()函数(关系有点复杂,这里只看sdb_mainloop()就行,它是运行ENMU过程函数),转到定义
void sdb_mainloop() {
if (is_batch_mode) {
cmd_c(NULL);
return;
}
for (char *str; (str = rl_gets()) != NULL; ) {
char *str_end = str + strlen(str);
/* extract the first token as the command */
char *cmd = strtok(str, " ");
if (cmd == NULL) { continue; }
/* treat the remaining string as the arguments,
* which may need further parsing
*/
char *args = cmd + strlen(cmd) + 1;
if (args >= str_end) {
args = NULL;
}
#ifdef CONFIG_DEVICE
extern void sdl_clear_event_queue();
sdl_clear_event_queue();
#endif
int i;
for (i = 0; i < NR_CMD; i ++) {
if (strcmp(cmd, cmd_table[i].name) == 0) {
if (cmd_table[i].handler(args) < 0) { return; }
break;
}
}
if (i == NR_CMD) { printf("Unknown command '%s'\n", cmd); }
}
}
cmd_table [] = {
{ "help", "Display information about all supported commands", cmd_help },
{ "c", "Continue the execution of the program", cmd_c },
{ "q", "Exit NEMU", cmd_q },
static int cmd_q(char *args) {
return -1;
}
让我们逐行解读这段代码的功能和执行流程:
- 首先,函数检查是否处于批处理模式(is_batch_mode)。如果是批处理模式,它将调用
cmd_c(NULL)
来执行默认命令,然后函数返回。- 如果不是批处理模式,函数会进入一个循环,不断获取用户输入的命令。
rl_gets()
函数用于获取用户输入的一行字符串,并将其存储在str
中。如果获取的字符串为空,则终止循环。str_end
指向str
字符串的末尾。- 使用
strtok()
函数从str
中提取第一个标记(token)也就是用户输入的指令用空格分开后的第一部分,作为命令。如果命令为空,则继续下一次循环。- 将
args
指针指向命令字符串之后的位置,作为命令的参数。如果args
指针超过了字符串的末尾,将其设置为NULL。- 如果编译选项
CONFIG_DEVICE
被定义,则调用sdl_clear_event_queue()
函数来清除事件队列(可能是与图形界面交互相关的操作)。- 通过遍历
cmd_table
数组,找到与输入命令匹配的命令处理函数。- 如果找到了匹配的命令处理函数,将参数
args
传递给该函数进行处理。如果命令处理函数返回值小于0,函数将立即返回。- 如果遍历完整个
cmd_table
数组都没有找到匹配的命令,将输出一条"Unknown command '命令名称'"的提示信息。- 循环回到步骤3,继续等待用户输入的命令。
这段代码实现了一个命令行解释器的主循环,它会不断等待用户输入的命令,并根据命令查找相应的处理会输出相应的提示信息。在命令处理过程中,还会清除事件队列(如果定义了相关编译选项)。函数进行执行。如果命令匹配成功,则执行相应的处理函数;如果命令未知或无法匹配,
于是当我们输入命令q后,函数会根据q然后执行函数cmd_q,并且返回值-1。
结束了这些,我们再回到main函数,它最后会执行is_exit_status_bad()函数,转到定义
#include <utils.h>
NEMUState nemu_state = { .state = NEMU_STOP };
int is_exit_status_bad() {
int good = (nemu_state.state == NEMU_END && nemu_state.halt_ret == 0) ||
(nemu_state.state == NEMU_QUIT);
return !good;
}
这段代码定义了一个名为
is_exit_status_bad()
的函数,用于判断 NEMU 运行的结束状态是否正常。具体实现方式如下:
首先定义了一个全局变量
nemu_state
,这个变量保存着 NEMU 的当前状态。其中state
成员变量表示当前状态(停止、运行、退出),halt_ret
成员变量表示最后一次执行halt
指令后 CPU 返回的值。
is_exit_status_bad()
函数会根据这个全局变量中的状态来判断 NEMU 运行的结束状态是否正常。如果状态是NEMU_END
且最后一次执行halt
后 CPU 返回值为 0,或者状态为NEMU_QUIT
,则说明运行正常,返回 0;否则说明运行存在异常,返回非 0 值。
理清楚了执行make run命令之后执行的全部函数,就可以通过gdb来调试看是哪一步出现了问题,终端输入gdb,然后输入file riscv32-nemu-interpreter (位于nemu/build/riscv32-nemu-interpreter)
list命令查看代码, 发现sdb_mainloop()函数在第100行,那我们在这里打断点(break 100),然后输入r命令运行,再输入n命令一步一步调试代码
这里要用户输入命令,我们输入q,然后继续执行
后面退出sdb_mainloop()函数后,按照之前代码结构的分析到执行is_exit_status_bad()函数了,这个函数中由上知返回状态数0时正确,其他则错误
打印 good的值发现返回值错误(返回!good,但现在!good=1),分析good的值是由nemu_state.state决定的,我们打印这个值,发现它是1,根据文档显示它对应1时为运行状态
此时nemu_state.state应该为2(即NEMU_END)才对,那么在is_exit_status_bad()函数的定义中改一下代码就行了
改完就不报错了