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能夠集中時間完成。
