PA1.1

版权声明:本文为DmrfCoder原创作品,如有转载请注明出处 https://blog.csdn.net/qq_36982160/article/details/79460727

##RTFSC
###首先对从github上pull下来的代码进行一个大概的阅读
####遇到的问题
第一次在文件夹中看到这么多的代码文件感觉无从入手,后来我找到了一个折中的方法,使用本机的Clion将nemu整体导入,这样我就可以从main入口一步一步深入地剖析项目结构。
###讲义中提到的问题:
####一个程序是从哪开始执行?
一个程序c项目的入口应该是main.c

####在cmd_c()函数中,调用cpu_exec()函数参数传入-1是为什么?
本来我的思路是找到cpu_exec(uint64_t n)这个函数,想看看函数里面有没有对 n= =- 1 的专门处理(比如if(n== -1)这类语句),然而看了代码后发现并没有这类语句,但是我却发现了如下一句:

bool print_flag = n < MAX_INSTR_TO_PRINT;

这句是在判断n是否小于MAX_INSTR_TO_PRINT,而回头看一下这个函数的形参,其指定的是无符号整形,如果给个-1,那么无疑n(-1)就会被当成无符号最大的数,这样就足够在后面的循环里面执行最大次数的循环从而执行完毕所有语句。
##实现正确的结构体CPU_state
在做pa0的时候就提到了这个,在nemu下执行make run的时候会出现 assertion fail,其原因便是 nemu/include/cpu/reg.h 文件中的 cpu_state 结构体没有调试正确,现在需要实现其结构体。
###实现过程
因为文档中提示到使用匿名union,而union最直观的理解就是内存公用,立刻联想到计算机中CPU中寄存器都是公用一片内存的,所以就直接将CPU_state最外面改为了typedef union,本以为这样就可以搞定,可是实验后发现依然报错,后来看到CPU_state内部又有一个结构体,所以又给其加了一个union,而对于eax, ecx, edx, ebx, esp, ebp, esi, edi,eip这些寄存器,刚开始也给其外面套了一个union,但是问题依旧,后来干脆直接将其放在struct里面,然后竟然不报错了,但是原理不是很懂,希望之后可以归档掉。
###实验结果
完成后的结构体:
这里写图片描述
修改完成后执行make clean->make run之后就会进入nemu:
这里写图片描述
###遇到的坑
在完成这个结构体的时候遇到的最大一个坑是刚开始没看清楚题以为是要自己写个结构体结果在reg.c中写了半天愣是被搞懵,直到后来认真读了几遍文档才发现,要细心啊年轻人…
###log截图
这里写图片描述
##实现单步执行
###预备知识

  • Strtok函数可以按照给定的结点字符将字符串分解成一段段小字符串 这样就能通过空格将输入的指令依次分隔开

  • sscanf 函数可以将字符串转化为需要的进制数 比如可以将代表十六进制数的字符串转换为一个十六进制数。
    ###完成cmd_table
    首先我们直到实现单步执行的效果是在t下输入命令回车后nemu会进行对应处理,那么nemu是如何检测你的输入呢,自然想到main_loop:

    void ui_mainloop(int is_batch_mode) {
    if (is_batch_mode) {
    cmd_c(NULL);
    return;
    }

      while (1) {
          char *str = rl_gets();
          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 HAS_IOE
    extern void sdl_clear_event_queue(void);
    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,应该是将拿到的命令和cmd_table中的值进行逐个比较然后执行对应操作,所以跟到cmd_table中,发现其定义如下:

cmd_table[] = {
        {"help", "Display informations about all supported commands", cmd_help},
        {"c",    "Continue the execution of the program",             cmd_c},
        {"q",    "Exit NEMU",                                         cmd_q}
        /* TODO: Add more commands */

};

很明显,help、c、q是nemu已经可以识别的命令,如果我们像实现类似单步执行这样的命令也必须在cmd_table中注册对应命令,查看对应readme后完成cmd_table如下:

 {"si",   "step few steps",                                    cmd_si},

其效果应该是在终端输入si之后会执行cmd_si这个函数,但是我在代码里面没找到类似函数,所以应该是要自己实现。
###完成cmd_si()
因为有类似的cmd_help(),所以按照其格式书写如下:

static int cmd_si(char *args) {
    char *arg = strtok(NULL, " ");
    int n = 0;
    sscanf(arg, "%d", &n);
    for (int j = 0; j < n; ++j) {
        cpu_exec(1);
    }

    return 1;
}

其中 sscanf(arg, “%d”, &n);是为了获取执行的步数,得到需要执行的步数之后使用n个循环,每次给cpu_exec()中传入1,感觉给个cpu_exec(n)也可以?虽然没试过,但是觉得肯定会出现某些情况下的异常(比如n的数值为负)
###实验结果
实现后可以进行指定步数执行:
这里写图片描述
###git log截图
这里写图片描述
##实现打印寄存器
有了实现单步执行的经验,实现打印寄存器也同理:
###添加cmd_table

   {"info", "print the register states",                         cmd_info}

###完成cmd_info()
打印寄存器状态实际上就是将寄存器的名称以及值打印出来,即打印cpu.gpr[i]的值以及对应的寄存器名称即可,需要注意的一点是打印8位寄存器时cpu.gpr[i]._8有两个维度,对应都要打印,实现代码如下:

void print_reg() {
    for (int i = 0; i < 8; ++i) {
        printf("%s %x\n", regsl[i], cpu.gpr[i]._32);//32位寄存器
    }
    for (int i = 0; i < 8; ++i) {
        printf("%s %x\n", regsw[i], cpu.gpr[i]._16);//16位寄存器
    }
    for (int i = 0; i < 8; ++i) {
        for (int j = 0; j < 2; ++j) {
            printf("%s %x\n", regsb[i], cpu.gpr[i]._8[j]);//8位寄存器
        }

    }

}


static int cmd_info(char *args) {
    char *arg = strtok(NULL, " ");
    if (strcmp(arg, "r") == 0) {//说明输入的命令是info r即应该打印寄存器状态
        print_reg();
    }

    return 1;
}

###实验结果
这里写图片描述
###git log截图
这里写图片描述##实现扫描内存功能
###完成cmd_table

  {"x",    "scanning memory",                                   cmd_x}

###实现cmd_x()
文档上讲到扫描内存的代码在memory.c中,打开该文件,发现和读相关的有两个函数:

uint32_t paddr_read(paddr_t addr, int len) {
  return pmem_rw(addr, uint32_t) & (~0u >> ((4 - len) << 3));
}


uint32_t vaddr_read(vaddr_t addr, int len) {
  return paddr_read(addr, len);
}

其实就是vaddr_read调用paddr_read,传入了两个参数,一个是起始地址,另一个是扫描长度,可以看到len只能为4,所以我们只需要在我们的函数里面调用vaddr_read并传入对应参数就可以了,因为需要两个参数,所以cmd_x()需要使用两个strtok()和两个sscanf(),拿到起始地址和扫描长度后循环调用vaddr_read(),实现代码如下:

static int cmd_x(char *args) {

    char *arg1=strtok(NULL," ");
    char *arg2=strtok(NULL," ");

    int len;
    vaddr_t addr;
    sscanf(arg1,"%d",&len);
    sscanf(arg2,"%x",&addr);
    printf("0x%x:",addr);
    for (int j = 0; j <len ; ++j) {
        printf("%x ",vaddr_read(addr,4));
        addr+=4;
    }
    printf("\n");
    return 1;
}

###实验结果
这里写图片描述
###git log截图
这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_36982160/article/details/79460727
1.1