// $(NEMU_HOME)/src/cpu/cpu-exec.c voidcpu_exec(uint64_t n) { g_print_step = (n < MAX_INST_TO_PRINT); switch (nemu_state.state) { case NEMU_END: case NEMU_ABORT: printf("Program execution has ended. To restart the program, exit NEMU and run again.\n"); return; default: nemu_state.state = NEMU_RUNNING; }
switch (nemu_state.state) { case NEMU_RUNNING: nemu_state.state = NEMU_STOP; break;
case NEMU_END: case NEMU_ABORT: Log("nemu: %s at pc = " FMT_WORD, (nemu_state.state == NEMU_ABORT ? ANSI_FMT("ABORT", ANSI_FG_RED) : (nemu_state.halt_ret == 0 ? ANSI_FMT("HIT GOOD TRAP", ANSI_FG_GREEN) : ANSI_FMT("HIT BAD TRAP", ANSI_FG_RED))), nemu_state.halt_pc); // fall through case NEMU_QUIT: statistic(); } }
static void execute(uint64_t n)
这个函数也是个壳子,真正的CPU还要看exec_once()函数,如果调用它,CPU会执行一个周期
1 2 3 4 5 6 7 8 9 10 11
// $(NEMU_HOME)/src/cpu/cpu-exec.c staticvoidexecute(uint64_t n) { Decode s; for (;n > 0; n --) { exec_once(&s, cpu.pc); g_nr_guest_inst ++; trace_and_difftest(&s, cpu.pc); if (nemu_state.state != NEMU_RUNNING) break; IFDEF(CONFIG_DEVICE, device_update()); } }
这里声明了一个译码结构体变量struct Decode,趁机解释一波:
1 2 3 4 5 6 7
typedefstructDecode { vaddr_t pc; vaddr_t snpc; // static next pc vaddr_t dnpc; // dynamic next pc ISADecodeInfo isa; IFDEF(CONFIG_ITRACE, char logbuf[128]); } Decode;
staticvoiddecode_operand(Decode *s, int *rd, word_t *src1, word_t *src2, word_t *imm, int type) { uint32_t i = s->isa.inst.val; int rs1 = BITS(i, 19, 15); int rs2 = BITS(i, 24, 20); *rd = BITS(i, 11, 7); switch (type) { case TYPE_I: src1R(); immI(); break; case TYPE_U: immU(); break; case TYPE_S: src1R(); src2R(); immS(); break; case TYPE_R: src1R(); src2R(); break; case TYPE_B: src1R(); src2R(); immB(); break; case TYPE_J: immJ(); break; } }
Step 3 执行指令语义
将指令行为copy到代码中即。最后通过goto语句跳转出来
实现指令
在实现这些指令的行为时,需要严格遵循riscv32的指令规范,并且要注意类型转换和符号扩展
首先介绍三个常用的宏
1 2 3
#define BITMASK(bits) ((1ull << (bits)) - 1) // make bitmask like 1111...111 len of mask is `bits` #define BITS(x, hi, lo) (((x) >> (lo)) & BITMASK((hi) - (lo) + 1)) // similar to x[hi:lo] in verilog #define SEXT(x, len) ({ struct { int64_t n : len; } __x = { .n = x }; (uint64_t)__x.n; }) // signed extended from x[len-1:0] to x[63:0]
宏BITMASK(bits)返回bits位unsigne long long类型的掩码
宏BITS(x, hi, lo)对x进行从高位hi到低位lo的切片,类似verilog中的x[hi:lo],返回值的类型同样时unsigne long long
宏SEXT(x, len)将输入x的最低len位进行符号扩展至uint64_t
下面解释一下SEXT()宏:
1 2 3 4 5 6 7 8
#define SEXT(x, len) ({ struct { int64_t n : len; } __x = { .n = x }; (uint64_t)__x.n; })
int64_t n : len 这是结构中的位域,表示只取n的后len位,对应于verilog的[len-1:0]位,然后会将len位的数据进行符号位扩展至int64_t,符号位为n[len-1]的数据。 最后强制类型转换为uint64_t
# Building add-run [riscv32-nemu] # Building am-archive [riscv32-nemu] + CC src/platform/nemu/trm.c In file included from /home/lyq/Desktop/ysyx-workbench/abstract-machine/am/include/am.h:4, from /home/lyq/Desktop/ysyx-workbench/abstract-machine/am/src/platform/nemu/trm.c:1: /opt/riscv64-linux/lib/gcc/riscv64-unknown-linux-gnu/13.2.0/include/stdint.h:9:16: fatal error: stdint.h: No such file or directory 9 | # include_next <stdint.h> | ^~~~~~~~~~ compilation terminated. make[2]: *** [/home/lyq/Desktop/ysyx-workbench/abstract-machine/Makefile:110: /home/lyq/Desktop/ysyx-workbench/abstract-machine/am/build/riscv32-nemu/src/platform/nemu/trm.o] Error 1 make[1]: *** [/home/lyq/Desktop/ysyx-workbench/abstract-machine/Makefile:129: am] Error 2 test list [1 item(s)]: add [ add] ***FAIL***
这啥?找不到stdint.h头文件?遂STFW,在Stack Exchange中发现了解决方案:
One way to fix this error is: restrict gcc in using stdint-gcc.h
This can be done by adding c compiler flag
-ffreestanding
to gcc
For more info on what is freestanding visit here and implied -fno-builtin visit here
Linux kernel的目录结构下一般都会存在Kconfig和Makefile两个文件。分布在各级目录下的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文件相关的内核配置菜单。在内核配置make menuconfig时(执行make menuconfig时会出现内核的配置界面),从Kconfig中读出配置菜单,用户配置完后保存到.config(在顶层目录下生成)中。在内核编译时,主Makefile调用这个.config,就知道了用户对内核的配置情况。
ifeq ($(wildcard .config),) $(warning$(COLOR_RED)Warning: .config does not exists!$(COLOR_END)) $(warning$(COLOR_RED)To build the project, first run 'make menuconfig'.$(COLOR_END)) endif
staticstruct { constchar *name; constchar *description; int (*handler) (char *); } 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 },
/* TODO: Add more commands */ // si [N] { "si", "Let the program step through N instructions and then pause execution", cmd_si}, // info r / w { "info", "Print the status of program", cmd_info}, // p Expr { "p", "Evalute the expression", cmd_p}, // x N Expr { "x", "Evalute the expression and take the reuslt as the begin address, print the continous N bytes as Hex", cmd_x}, // w Expr { "w", "Watch the expression, if the expression change, stop the program", cmd_w}, // d N { "d", "Delete the watchpoint N", cmd_d}, // history { "history", "show history command", cmd_history} };
/* extract the first token as the command */ char *cmd = strtok(str, " "); if (cmd == NULL) { continue; } // just read the "Enter"
/* treat the remaining string as the arguments, * which may need further parsing */ char *args = cmd + strlen(cmd) + 1; if (args >= str_end) { args = NULL; }
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; } // if enter "q", cme_q will return -1 break; } }
if (i == NR_CMD) { printf("Unknown command '%s'\n", cmd); } } }
// $(NEMU_HOME)/src/monitor/sdb/sdb.c staticintcmd_help(char *args) { /* extract the first argument */ char *arg = strtok(NULL, " "); int i;
if (arg == NULL) { /* no argument given */ for (i = 0; i < NR_CMD; i ++) { printf("%s - %s\n", cmd_table[i].name, cmd_table[i].description); } } else { for (i = 0; i < NR_CMD; i ++) { if (strcmp(arg, cmd_table[i].name) == 0) { printf("%s - %s\n", cmd_table[i].name, cmd_table[i].description); return0; } } printf("Unknown command '%s'\n", arg); } return0; }
while make was building the targetruntests, one of the commands that it ran exited with an error code of1. In POSIX (and make),any exit code other than 0 is considered a failure; only 0 means that the command succeeded.
// $(NEMU_HOME)/src/monitor/sdb/sdb.c staticintcmd_si(char *args) { char *num_str = strtok(NULL, " "); int num = 0; if (num_str != NULL) { Assert(sscanf(num_str, "%d", &num), "The input step num is not a number"); } else { /* no argument given, the default num of step is 1 */ num = 1; } cpu_exec(num); return0; }
cmd_info r 打印寄存器
打印寄存器的功能实现在info命令当中,并需要参数r来指定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// $(NEMU_HOME)/src/monitor/sdb/sdb.c staticintcmd_info(char *args) { char *arg = strtok(NULL, " "); if (strcmp(arg, "r") == 0) { /* info r : print the status of Rigister File*/ isa_reg_display(); } elseif (strcmp(arg, "w") == 0) { /* info w : print the info of watch point*/ print_watchpoint(); } else { printf("command info need argument r or w\n"); } return0; }
staticboolmake_token(char *e) { int position = 0; int i; regmatch_t pmatch; nr_token = 0;
while (e[position] != '\0') { /* Try all rules one by one. */ for (i = 0; i < NR_REGEX; i ++) { if (regexec(&re[i], e + position, 1, &pmatch, 0) == 0 && pmatch.rm_so == 0) { char *substr_start = e + position; int substr_len = pmatch.rm_eo;
// Log("match rules[%d] = \"%s\" at position %d with len %d: %.*s", // i, rules[i].regex, position, substr_len, substr_len, substr_start);
position += substr_len;
switch (rules[i].token_type) { case TK_NOTYPE: break; case'+': tokens[nr_token++].type = '+'; break; case'-': tokens[nr_token++].type = '-'; break; case'*': tokens[nr_token++].type = '*'; break; case'/': tokens[nr_token++].type = '/'; break; case'(': tokens[nr_token++].type = '('; break; case')': tokens[nr_token++].type = ')'; break; case TK_EQ: tokens[nr_token++].type = TK_EQ; break; case TK_NEQ: tokens[nr_token++].type = TK_NEQ; break; case TK_DEC: tokens[nr_token].type = TK_DEC; Assert(substr_len < 32, "length of int is too long (> 31)"); strncpy(tokens[nr_token].str, substr_start, substr_len); tokens[nr_token++].str[substr_len] = '\0'; break; default: TODO(); break; } break; } }
if (i == NR_REGEX) { printf("no match at position %d\n%s\n%*.s^\n", position, e, position, ""); returnfalse; } } nr_token--; returntrue; }
{"\\+", '+'}, // plus (the first \ is to escape the second \ in C, the second \ is to escape the + in regex) {"\\-", '-'}, // minus {"\\*", '*'}, // mutiply {"\\/", '/'}, // divide
{"\\(", '('}, // left bracket {"\\)", ')'}, // right bracket
{"[1-9]+[0-9]*", TK_DEC}, // decimal integer };
#define NR_REGEX ARRLEN(rules)
staticregex_t re[NR_REGEX] = {};
voidinit_regex() { int i; char error_msg[128]; int ret;
for (i = 0; i < NR_REGEX; i ++) { ret = regcomp(&re[i], rules[i].regex, REG_EXTENDED); if (ret != 0) { regerror(ret, &re[i], error_msg, 128); panic("regex compilation failed: %s\n%s", error_msg, rules[i].regex); } } }
// $(NEMU_HOME)/src/monitor/sdb/expr.c staticboolcheck_parentheses(int p, int q) { // Step 1, check if the entire expr is surrounded by () if (!(tokens[p].type == '(' && tokens[q].type == ')')) returnfalse; // Step 2, check if the expr is closed by () int diff = 0; for (int i = p; i <= q; i++) { if (tokens[i].type == '(') diff++; if (tokens[i].type == ')') diff--; if (diff < 0) returnfalse; } if (diff != 0) returnfalse; returntrue; }
// $(NEMU_HOME)/src/monitor/sdb/expr.c staticintget_op_priority(int op) { switch(op) { case TK_NEG: case TK_DEREF: return2; case'*': case'/': return3; case'+': case'-': return4; case TK_EQ: case TK_NEQ: return7; case TK_L_AND: return11; case TK_L_OR: return12; default: return0; } }
staticintfind_main_op(int p, int q) { int op = -1; int last_priority = 0; for (int i = p; i <= q; i++) { int op_type = tokens[i].type; int op_priority = get_op_priority(op_type); // Log("[%d] Priority:%d", op_type, op_priority); // if the token is not op type, it can not be the main op if (op_priority == 0) continue; // if the op is surrounded by bracket, it can not be the main op if (!surrounded_by_bracket(i, p, q)) continue; // Consider the op type priority and the sequence priority between this op and last op // in case the op priority is different, the low priority op will be the domain op if (op_priority > last_priority) { last_priority = op_priority; op = i; } // in case the priority is the same, the right one will be the domain op elseif (op_priority == last_priority) { op = i; } } Assert(op >= 0, "Find No main op"); return op; }
检查运算符是否被括号包围
static bool surrounded_by_bracket(int x, int p, int q)用于检查位于x位置的运算符在p~q的表达式中是否被括号包围
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// $(NEMU_HOME)/src/monitor/sdb/expr.c staticboolsurrounded_by_bracket(int x, int p, int q) { int left_bracket = 0; int right_bracket = 0; for (int i = x - 1; i >= p; i--) { if (tokens[i].type == '(') left_bracket++; if (tokens[i].type == ')') left_bracket--; } for (int i = x + 1; i <= q; i++) { if (tokens[i].type == ')') right_bracket++; if (tokens[i].type == '(') right_bracket--; } // Log("left_bracket = %d, right_bracket = %d\n", left_bracket, right_bracket); if (left_bracket == 0 && right_bracket == 0) returntrue; Assert(left_bracket == right_bracket, "The bracket is not closed!"); returnfalse; }