PA实验问题及记录


PA实验问题及记录

PA0

平常使用linux较多,较为熟悉,未碰到环境配置方面的困难

实验环境:

OS:Manjaro 20.2 Nibia

Kernal:x86_64 Linux 5.9.11-3-MANJARO

  • 初始化相关环境变量

    根目录init.sh中注释掉第十行return

    后执行 bash init.sh ,使用的不是默认shell,需要更改20-21 行

  • 设置$ISA环境变量

  • nemu/makefile 中

    • 第33行:

      CFLAGS   += -O0 -g -MMD -Wall -ggdb3 -std=c99 $(INCLUDES) -D__ISA__=$(ISA) -fomit-frame-pointer -mmanual-endbr -fcf-protection=none #-Werror 禁止编译器优化,调试时使用
      
    • 第71行:

      @$(LD) -O0 -rdynamic $(SO_LDLAGS) -o $@ $^ -lSDL2 -lreadline -ldl
      
    • 注释掉第25行关闭自动添加commit历史

    • ···

  • make 报错 找不到optarg

    src/monitor/monitor.c: In function ‘parse_args’:
    src/monitor/monitor.c:67:3: warning: implicit declaration of function ‘getopt’ [-Wimplicit-function-declaration]
       while ( (o = getopt(argc, argv, "-bl:d:a:")) != -1) {
       ^
    src/monitor/monitor.c:70:28: error: ‘optarg’ undeclared (first use in this function)
           case 'a': mainargs = optarg; break;
                                ^
    src/monitor/monitor.c:70:28: note: each undeclared identifier is reported only once for each function it appears in
    make: *** [build/obj-mips32/monitor/monitor.o] Error 1
    

    在include/common.h 中 #include<getopt.h> 解决

  • 调试环境

    gdb调试

PA1

选择ISA为:riscv-32

task实现如下:

单步执行

解析出执行步数(无参数时默认为一),直接调用cpu_exec(num)即可

static int cmd_si(char *args) {
  char * arg = strtok(args, " ");
  if (arg == NULL) {
    cpu_exec(1);
    return 0;
  }
  int num = atoi(arg);
  cpu_exec(num);
  return 0;
};

打印寄存器

完善isa_reg_display()函数即可,riscv的寄存器为32个通用寄存器加上$PC

void isa_reg_display() {
    printf("General reg: ----------------------------------------------------------- \n");
    int i, j;
    for(i = 0 ; i < 32 ; i++) {
      printf("%-3s :0x%08x |  ", regsl[i], cpu.gpr[i]._32);
      if ((i+1)%4 == 0)
        printf("\n");
    }
    printf("Special reg: ----------------------------------------------------------- \n");
    printf("$pc :0x%08x\n\n", cpu.pc);
}

扫描内存

通过 strtok 分别获得字符串型的地址和扫描长度,调用expr(后续实现)求得表达式结果作为地址,调用 vaddr_read 函数扫描内存后按格式打印即可。

...
vaddr_t addr = expr(EXPR, &success);
...
for (int i = 0; i < n; i++) {
		uint32_t data = vaddr_read(addr + i * 4, 4);
		printf("0x%08x	", addr + i * 4);
		for (int j = 0; j < 4; j++) {
      printf("0x%02x	" , data & 0xff);
      data = data >> 8;
		}
		printf("\n");
	}
...

表达式求值

  • 首先需要完善正则匹配:

      {" +", TK_NOTYPE, 0},              // spaces
      {"0[xX][0-9a-fA-F]+", TK_HEX, 0},  // hex
      {"[0-9]+", TK_DEX, 0},             // dex
      {"(\\$[0a-zA-Z]+)|([xX][0-9]+)",TK_REG, 0},			 // register $ OR x0~x31
      {"\\|\\|", TK_OR ,1},              // or
      {"&&", TK_AND, 2},                 // and 
      {"==", TK_EQ, 3},                  // equal
      {"!=", TK_NEQ, 3},                 // not equal
      {"\\+", '+', 4},                   // plus
      {"-", '-', 4},                     // sub
      {"\\*", '*', 5},                   // mul
      {"/", '/', 5},                     // div
      {"!", '!', 6},                     // not
      {"\\(", '(', 7},                   // bra_l
      {"\\)", ')', 7},                   // bra_r
    

    在原来rule的基础上增加了优先级字段,用于后续在表达式递归下降的过程中求得主运算符。

    这里有几个需要注意的地方:

    ​ - 像是*或者 ( 这样的符号需要经过两次转义,一次是c语言字符串转义,一次是正则引擎需要的转义

    ​ - regex.h 不支持正则中类似于 <! 这样的前置匹配,所以无法直接配置十进制,这里采用优先匹配十六进制再匹配十进制的方式绕过去

  • 接着完善make_token()函数

    识别出表达式中的每一个token。在for循环中,用regexec()函数匹配目标文本串和前面定义的rules[i]中的正则表达式比较,pmatch.rm_so==0表示匹配串在目标串中的第一个位置,pmatch.rm_eo表示结束位置,position和substr_len表示读取完后的位置和读取长度。成功识别到对应规则后,进行type、pri、str的复制即可

  • 接着进入对与表达式的处理eval函数,首先是对于表达式是否合法进行判断,这里需要满足start<= end 的条件,当start == end 的时候表示进行到了递归结束点,进行相应位置表达式(HEX、DEX、REG)的求值即可。对于start < end 的情况,首先需要判断表达式括号是否匹配,这里增加了一个函数

    static check_bra check_parentheses(int start, int end),参数为表达式起始和结束位置,返回值为枚举类型check_bra

    typedef enum {
      BRA_SURRONDED, MATCH, DISMATCH 
    } check_bra;
    

    其中BRA_SURROUNDED代表表达式被()包裹且括号内为括号匹配的表达式,MATCH代表括号匹配但不满足前一种情况的表达式,DISMATCH代表不满足括号匹配的表达式。

    当遇到DISMATCH时停止解析返回即可;当遇到BRA_SURRONDED时直接解析内层表达式即可;当遇到MATCH时才是我们需要进行处理的部分

  • 在MATCH的情况下,为了进行表达式的递归下降,首先要找到表达式中最后进行运算的符号,即主运算符,以此讲表达式分成前后两部分后分别进行计算,由函数 static int dominant_operator(int start, int end) 实现。

    static int dominant_operator(int start, int end)
    {
    	int op = start, pri_min = 10;
    	for (int i = start; i <= end;i ++)
    	{
    		if (tokens[i].type == TK_HEX || tokens[i].type == TK_DEX || tokens[i].type == TK_REG)
    			continue;
    		int bra_count = 0;
    		bool flag = true;
    		for (int j = i - 1; j >= start; j--) { 
    			if (tokens[j].type == '(') {
            if (bra_count == 0) {
              flag = false;
              break;
            }
            bra_count--;
          }
    			if (tokens[j].type == ')')
            bra_count++; 
    		}
    		if (!flag)
          continue;
    		if (tokens[i].pri <= pri_min) {
          op = i;
          pri_min = tokens[op].pri;
        }
      }
    	return op;
    }
    

    此函数根据之前数据结构中存储的优先级结构,来确定最后运算的符号,为优先级数字最小且不在括号中的符号,返回其index

    再求得主运算符之后,左右分别递归后运算即可。

  • 后续eval增加了对于解引用运算(*)和负号(-)的处理,因为无法在正则匹配中区分出来,所以需要在匹配完成后在进行符号的判断和优先级的修正,将这两个符号设为一个较高的优先级,在后续求得主运算符后,判断为这两个运算符时直接进行求值即可

      for (int i = 0; i < nr_token; i ++) {
    		if (tokens[i].type == '*' && (i == 0 || (tokens[i - 1].type != TK_DEX && tokens[i - 1].type != TK_HEX && tokens[i - 1].type != TK_REG && tokens[i - 1].type !=')') )) {
    			tokens[i].type = TK_DEREF;
          tokens[i].pri = 6;
    		}
    		if (tokens[i].type == '-' && (i == 0 || (tokens[i - 1].type != TK_DEX && tokens[i - 1].type != TK_HEX && tokens[i - 1].type != TK_REG && tokens[i - 1].type !=')') )) {
    			tokens[i].type = TK_MINUS;
          tokens[i].pri = 6;
    		}
    	}
    

    碰到的问题:

    ​ - 在解析类似于表达式p (1+2)*(3+4) 的时候出错,实现括号匹配解析的时候,不能将其归为BRA_SURROUNDED类型,会把这个表达式识别为不合法的表达式,因此增加了必须左侧有两个连续左括号的条件,但因此又导致了对于简单的表达式p (1+2) 回被认为是MATCH类型,进而求主运算符,导致错误;最后将BRA_SURROUNDED的条件设置为上述两种情况的并集,成功解决问题。

监视点

  • 监视点管理操作

    WP* wp_new (); // 申请新监视点
    void wp_free(WP * wp); // 释放监视点
    void wp_delete(int num); // 删除对应序号的监视点
    void wp_display(); //打印所有的监视点
    bool wp_check(); //监测监视点的值是否变化
    

    wp_new 从free链表中取一个结点给head链表。

    wp_free 函数是遍历head链表直到找出对应NO的结点,从head中删除,添加到free链表中。

    wp_chcek 函数是便利head链表中的监视点,求出其值后与上一次的值进行比较,打印出变化的监视点并返回false

  • 实现监视点

    修改cpu_exec.c,每执行完一条命令,调用wp_check() 函数,进行相应监视点的值的判断

    if (!wp_check()) {
      nemu_state.state = NEMU_STOP;
    }
    

必答题

  • 我选择ISA为:riscv-32

  • 理解基础设施:

    450200.5=4500(min)=75(h)

  • 查阅手册:

    • riscv32有哪几种指令格式?

    • LUI指令的行为是什么?

    • mstatus寄存器的结构是怎么样的?

  • 代码统计

    • nemu/目录下的所有.c和.h和文件总共有多少行代码?

      find . -name "*.c" -or -name "*.h" | xargs grep -Ev "^$" | wc -l
      #4432
      
    • 和框架代码相比, 你在PA1中编写了多少行代码?

      git checkout pa0
      find . -name "*.c" -or -name "*.h" | xargs grep -Ev "^$" | wc -l
      #4009
      

      增加了423行代码

    • 加入 make count 命令

      COUNT_L := $(shell  find . -name "*.h" -or -name "*.c" | xargs grep -Ev "^$$" | wc -l) 
      COUNT_ADD := $(shell expr $(COUNT_L) - 4009)
      ...
      count:
      	@echo $(COUNT_L) lines in nemu
      	@echo $(COUNT_ADD) lines added into the frame code     
      
  • 解释gcc中的-Wall-Werror有什么作用? 为什么要使用-Wall-Werror?

    -Wall,打开gcc的所有警告。
    -Werror,它要求gcc将所有的警告当成错误进行处理。

    发现代码中潜在的问题

实验总结

​ pa1的实验难度较小,基本没有设计到和具体指令集相关的内容,因为实习原因,平时时间较紧,导致pa1总的时间线比较长,希望pa2能够集中时间完成。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM