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