NJU PA1 “优美地退出“ 用gdb调试代码解决退出时的报错

南京大学ICS PA作业

B站视频链接:计算机系统基础习题课ICS_哔哩哔哩_bilibili

课程网站:Introduction · GitBook

本文针对的是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;
}

让我们逐行解读这段代码的功能和执行流程:

  1. 首先,函数检查是否处于批处理模式(is_batch_mode)。如果是批处理模式,它将调用cmd_c(NULL)来执行默认命令,然后函数返回。
  2. 如果不是批处理模式,函数会进入一个循环,不断获取用户输入的命令。
  3. rl_gets()函数用于获取用户输入的一行字符串,并将其存储在str中。如果获取的字符串为空,则终止循环。
  4. str_end指向str字符串的末尾。
  5. 使用strtok()函数从str中提取第一个标记(token)也就是用户输入的指令用空格分开后的第一部分,作为命令。如果命令为空,则继续下一次循环。
  6. args指针指向命令字符串之后的位置,作为命令的参数。如果args指针超过了字符串的末尾,将其设置为NULL。
  7. 如果编译选项CONFIG_DEVICE被定义,则调用sdl_clear_event_queue()函数来清除事件队列(可能是与图形界面交互相关的操作)。
  8. 通过遍历cmd_table数组,找到与输入命令匹配的命令处理函数。
  9. 如果找到了匹配的命令处理函数,将参数args传递给该函数进行处理。如果命令处理函数返回值小于0,函数将立即返回。
  10. 如果遍历完整个cmd_table数组都没有找到匹配的命令,将输出一条"Unknown command '命令名称'"的提示信息。
  11. 循环回到步骤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 运行的结束状态是否正常。具体实现方式如下:

  1. 首先定义了一个全局变量 nemu_state,这个变量保存着 NEMU 的当前状态。其中 state 成员变量表示当前状态(停止、运行、退出),halt_ret 成员变量表示最后一次执行 halt 指令后 CPU 返回的值。

  2. 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()函数的定义中改一下代码就行了

 改完就不报错了

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
目录 I 基础知识9 1 MINIX操作系统简介11 1.1 MINIX与UNIX . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.2 探索MINIX . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.3 编辑器:vi . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.4 编译器:CC . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.5 实习题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2 Intel 8086体系结构19 2.1 8086 CPU结构. . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.2 运算器与指令部件. . . . . . . . . . . . . . . . . . . . . . . . 19 2.3 寄存器组. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.4 主存. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.5 堆栈. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.6 系统启动. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2.7 二进制文件结构. . . . . . . . . . . . . . . . . . . . . . . . . . 25 3 ACK 8086汇编语言27 3.1 寻址. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.2 数据移动指令. . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.3 常量、标号、数据与全局变量. . . . . . . . . . . . . . . . . . 29 3.4 运算指令. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.5 标志位操纵指令. . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.6 串操作指令. . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.7 跳转、分支与循环. . . . . . . . . . . . . . . . . . . . . . . . 33 3.8 堆栈与子程序. . . . . . . . . . . . . . . . . . . . . . . . . . . 34 5 6 目录 4 实习:Hello World 37 4.1 Hello World程序. . . . . . . . . . . . . . . . . . . . . . . . . 37 4.2 实习题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 II 汇编语言进阶41 5 C与汇编联合开发43 5.1 函数调用. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 5.2 全局与局部变量. . . . . . . . . . . . . . . . . . . . . . . . . . 45 5.3 编译与连接. . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 5.4 实习题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 6 中断与I/O 49 6.1 中断. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 6.2 输入输出设备. . . . . . . . . . . . . . . . . . . . . . . . . . . 51 6.3 编写中断处理程序. . . . . . . . . . . . . . . . . . . . . . . . 51 6.4 实习题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 7 实习:小游戏55 7.1 make工具. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 7.2 实习题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 III 操作系统内核59 8 进程切换61 8.1 进程模型. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 8.2 进程的实现. . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 8.3 进程切换. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 8.4 中断嵌套. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 8.5 MiniOS中的进程切换. . . . . . . . . . . . . . . . . . . . . . . 68 8.6 操作系统实现原则. . . . . . . . . . . . . . . . . . . . . . . . 69 9 进程通信71 9.1 信号量. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 9.2 消息机制. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 9.3 消息机制的实现. . . . . . . . . . . . . . . . . . . . . . . . . . 76 9.4 调试消息内核. . . . . . . . . . . . . . . . . . . . . . . . . . . 78 目录7 10 实习:操作系统内核83 10.1 引导操作系统. . . . . . . . . . . . . . . . . . . . . . . . . . . 83 10.2 微内核与层次式操作系统结构. . . . . . . . . . . . . . . . . . 83 10.3 实习题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 IV 轻量操作系统实现87 11 设备驱动程序89 11.1 设备驱动程序原理. . . . . . . . . . . . . . . . . . . . . . . . 89 11.2 键盘设备. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 11.3 屏幕设备. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 11.4 死锁的产生与预防. . . . . . . . . . . . . . . . . . . . . . . . 93 12 存储管理95 12.1 存储管理与系统调用服务进程. . . . . . . . . . . . . . . . . . 95 12.2 进程映像的创建与终止. . . . . . . . . . . . . . . . . . . . . . 97 12.3 替换进程映像. . . . . . . . . . . . . . . . . . . . . . . . . . . 98 13 文件系统101 13.1 文件与目录. . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 13.2 文件与目录管理. . . . . . . . . . . . . . . . . . . . . . . . . . 103 13.3 文件描述符. . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 14 实习:迷你操作系统109 14.1 一个简易的Shell . . . . . . . . . . . . . . . . . . . . . . . . . . 109 14.2 实习题. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 附录111 A vi常用命令113 B 虚拟机与外部的文件交换117

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

idMiFeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值