From 504d270947a333177d8fa2bad68df35a4736855a Mon Sep 17 00:00:00 2001 From: xinyangli Date: Thu, 8 Feb 2024 20:07:03 +0800 Subject: [PATCH] pa1.3: watchpoint support --- nemu/.gitignore | 2 + nemu/Makefile | 5 +- nemu/configs/rv32_defconfig | 74 +++++++++++++++ nemu/flake.nix | 15 ++++ nemu/include/debug.h | 10 ++- nemu/src/cpu/cpu-exec.c | 5 ++ nemu/src/isa/riscv32/reg.c | 13 ++- nemu/src/monitor/sdb/.gitignore | 2 - nemu/src/monitor/sdb/addrexp.l | 15 +++- nemu/src/monitor/sdb/addrexp.y | 16 +++- nemu/src/monitor/sdb/sdb.c | 38 ++++++-- nemu/src/monitor/sdb/sdb.h | 4 + nemu/src/monitor/sdb/watchpoint.c | 145 ++++++++++++++++++++++++++---- nemu/tests/expr_test.c | 55 +++++++++++- 14 files changed, 363 insertions(+), 36 deletions(-) create mode 100644 nemu/configs/rv32_defconfig delete mode 100644 nemu/src/monitor/sdb/.gitignore diff --git a/nemu/.gitignore b/nemu/.gitignore index 77fe490..36e223b 100644 --- a/nemu/.gitignore +++ b/nemu/.gitignore @@ -7,6 +7,8 @@ build/ .config .config.old .envrc +.metals/ +.vscode/ compile_commands.json ### C ### diff --git a/nemu/Makefile b/nemu/Makefile index 03c8a4e..4c7ed77 100644 --- a/nemu/Makefile +++ b/nemu/Makefile @@ -48,6 +48,7 @@ CFLAGS_BUILD += $(if $(CONFIG_CC_ASAN),-fsanitize=address,) CFLAGS_TRACE += -DITRACE_COND=$(if $(CONFIG_ITRACE_COND),$(call remove_quote,$(CONFIG_ITRACE_COND)),true) CFLAGS += $(CFLAGS_BUILD) $(CFLAGS_TRACE) -D__GUEST_ISA__=$(GUEST_ISA) LDFLAGS += $(CFLAGS_BUILD) +INC_PATH += $(NEMU_HOME)/src/isa/$(GUEST_ISA)/local-include # Include rules for menuconfig include $(NEMU_HOME)/scripts/config.mk @@ -60,8 +61,6 @@ else include $(NEMU_HOME)/scripts/native.mk endif -.PHONY: test - include $(NEMU_HOME)/tests/Makefile all-tests: TEST_OBJS = $(filter-out $(OBJ_DIR)/src/nemu-main.o, $(OBJS)) all-tests: CFLAGS += $(shell pkg-config --cflags check) @@ -70,3 +69,5 @@ all-tests: $(TEST_SRCS:%.c=$(OBJ_DIR)/%) test: all-tests @$(OBJ_DIR)/tests/expr_test + +.PHONY: test \ No newline at end of file diff --git a/nemu/configs/rv32_defconfig b/nemu/configs/rv32_defconfig new file mode 100644 index 0000000..aa26803 --- /dev/null +++ b/nemu/configs/rv32_defconfig @@ -0,0 +1,74 @@ +# +# Automatically generated file; DO NOT EDIT. +# NEMU Configuration Menu +# +# CONFIG_ISA_x86 is not set +# CONFIG_ISA_mips32 is not set +CONFIG_ISA_riscv=y +# CONFIG_ISA_loongarch32r is not set +CONFIG_ISA="riscv32" + +# +# ISA-dependent Options for riscv +# +# CONFIG_RV64 is not set +# CONFIG_RVE is not set +# end of ISA-dependent Options for riscv + +CONFIG_ENGINE_INTERPRETER=y +CONFIG_ENGINE="interpreter" +CONFIG_MODE_SYSTEM=y +CONFIG_TARGET_NATIVE_ELF=y +# CONFIG_TARGET_SHARE is not set +# CONFIG_TARGET_AM is not set + +# +# Build Options +# +CONFIG_CC_GCC=y +# CONFIG_CC_GPP is not set +# CONFIG_CC_CLANG is not set +CONFIG_CC="gcc" +# CONFIG_CC_O0 is not set +# CONFIG_CC_O1 is not set +CONFIG_CC_O2=y +# CONFIG_CC_O3 is not set +CONFIG_CC_OPT="-O2" +# CONFIG_CC_LTO is not set +# CONFIG_CC_DEBUG is not set +CONFIG_CC_ASAN=y +# end of Build Options + +# +# Testing and Debugging +# +CONFIG_TRACE=y +CONFIG_TRACE_START=0 +CONFIG_TRACE_END=10000 +CONFIG_ITRACE=y +CONFIG_ITRACE_COND="true" +# CONFIG_DIFFTEST is not set +CONFIG_DIFFTEST_REF_PATH="none" +CONFIG_DIFFTEST_REF_NAME="none" +# end of Testing and Debugging + +# +# Memory Configuration +# +CONFIG_MBASE=0x80000000 +CONFIG_MSIZE=0x8000000 +CONFIG_PC_RESET_OFFSET=0 +# CONFIG_PMEM_MALLOC is not set +CONFIG_PMEM_GARRAY=y +CONFIG_MEM_RANDOM=y +# end of Memory Configuration + +# CONFIG_DEVICE is not set + +# +# Miscellaneous +# +CONFIG_TIMER_GETTIMEOFDAY=y +# CONFIG_TIMER_CLOCK_GETTIME is not set +CONFIG_RT_CHECK=y +# end of Miscellaneous diff --git a/nemu/flake.nix b/nemu/flake.nix index 47bb216..325421f 100644 --- a/nemu/flake.nix +++ b/nemu/flake.nix @@ -50,6 +50,21 @@ libllvm ]; + configurePhase = '' + echo NEMU_HOME=$NEMU_HOME + echo pwd=$(pwd) + mkdir -p $(pwd)/kconfig + WORK_DIR=$(pwd) obj=$(pwd)/kconfig make --trace -e -f scripts/config.mk WORK_DIR=$(pwd) obj=$(pwd)/kconfig rv32_defconfig + ''; + + installPhase = '' + BUILD_DIR=$out make install + ''; + + checkPhase = '' + BUILD_DIR=$out make test + ''; + NEMU_HOME = src; meta = with lib; { diff --git a/nemu/include/debug.h b/nemu/include/debug.h index 924bcf1..087da4d 100644 --- a/nemu/include/debug.h +++ b/nemu/include/debug.h @@ -21,7 +21,15 @@ #include #define Log(format, ...) \ - _Log(ANSI_FMT("[%s:%d %s] " format, ANSI_FG_BLUE) "\n", \ + _Log(ANSI_FMT("[INFO] %s:%d %s() ", ANSI_FG_BLUE) format "\n", \ + __FILE__, __LINE__, __func__, ## __VA_ARGS__) + +#define Warning(format, ...) \ + _Log(ANSI_FMT("[WARNING] %s:%d %s() ", ANSI_FG_YELLOW) format "\n", \ + __FILE__, __LINE__, __func__, ## __VA_ARGS__) + +#define Error(format, ...) \ + _Log(ANSI_FMT("[ERROR] %s:%d %s() ", ANSI_FG_RED) format "\n", \ __FILE__, __LINE__, __func__, ## __VA_ARGS__) #define Assert(cond, format, ...) \ diff --git a/nemu/src/cpu/cpu-exec.c b/nemu/src/cpu/cpu-exec.c index 0c244ea..1f2940f 100644 --- a/nemu/src/cpu/cpu-exec.c +++ b/nemu/src/cpu/cpu-exec.c @@ -31,6 +31,7 @@ static uint64_t g_timer = 0; // unit: us static bool g_print_step = false; void device_update(); +bool wp_eval_all(); static void trace_and_difftest(Decode *_this, vaddr_t dnpc) { #ifdef CONFIG_ITRACE_COND @@ -77,6 +78,10 @@ static void execute(uint64_t n) { exec_once(&s, cpu.pc); g_nr_guest_inst ++; trace_and_difftest(&s, cpu.pc); + if (wp_eval_all()) { + puts(s.logbuf); + break; + } if (nemu_state.state != NEMU_RUNNING) break; IFDEF(CONFIG_DEVICE, device_update()); } diff --git a/nemu/src/isa/riscv32/reg.c b/nemu/src/isa/riscv32/reg.c index 6a90e96..4630df9 100644 --- a/nemu/src/isa/riscv32/reg.c +++ b/nemu/src/isa/riscv32/reg.c @@ -36,5 +36,16 @@ void isa_reg_display() { } word_t isa_reg_str2val(const char *s, bool *success) { - return 0; + assert(s); + int i; + for (i = 0; i < 32 && strcmp(s, regs[i]) != 0; i++) + ; + + if (i == 32) { + *success = false; + return 0; + } + *success = true; + + return gpr(i); } diff --git a/nemu/src/monitor/sdb/.gitignore b/nemu/src/monitor/sdb/.gitignore deleted file mode 100644 index 4fccbf4..0000000 --- a/nemu/src/monitor/sdb/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -addrexp_lex.h -addrexp.h \ No newline at end of file diff --git a/nemu/src/monitor/sdb/addrexp.l b/nemu/src/monitor/sdb/addrexp.l index 137d042..d814ee3 100644 --- a/nemu/src/monitor/sdb/addrexp.l +++ b/nemu/src/monitor/sdb/addrexp.l @@ -1,5 +1,8 @@ %{ + #include #include + static bool success = false; + void yyerror(word_t *result, const char *err); %} %option noyywrap @@ -7,7 +10,15 @@ 0[xX][0-9a-fA-F]+ { yylval = strtoul(yytext, NULL, 16); return HEX_NUMBER; } [0-9]+ { yylval = strtoul(yytext, NULL, 10); return NUMBER; } -[+\-*/()] { return *yytext; } +$[asgprt$][0-9pa][0-9]? { + yylval = isa_reg_str2val(yytext + 1, &success); + if(!success) { + yyerror(NULL, "Failed to convert reg to value"); + return YYerror; + } + return REGISTER; +} +[+\-*/<=()] { return *yytext; } [ \t] { } -. { printf("Unexpected character: %s\n", yytext); } +. { printf("Unexpected character: %s\n", yytext); return YYerror; } %% diff --git a/nemu/src/monitor/sdb/addrexp.y b/nemu/src/monitor/sdb/addrexp.y index 9e296ba..8e7df9a 100644 --- a/nemu/src/monitor/sdb/addrexp.y +++ b/nemu/src/monitor/sdb/addrexp.y @@ -1,19 +1,23 @@ %code requires { #include + #include #include #include extern int yylex(void); } %{ #include + #include #include #include void yyerror(word_t *result, const char *err) { - fprintf(stderr, "Error: %s\n", err); + Error("%s", err); } %} %token NUMBER HEX_NUMBER +%token REGISTER +%locations %start input %define api.value.type { word_t } %parse-param { uint32_t *result } @@ -27,6 +31,12 @@ input expression : number { $$ = $1; } + | expression '>' '=' expression { $$ = ($1 >= $4); } + | expression '<' '=' expression { $$ = ($1 <= $4); } + | expression '=' '=' expression { $$ = ($1 == $4); } + | expression '!' '=' expression { $$ = ($1 == $4); } + | expression '>' expression { $$ = ($1 > $3); } + | expression '<' expression { $$ = ($1 < $3); } | expression '+' expression { $$ = $1 + $3; } | expression '-' expression { $$ = $1 - $3; } | expression '*' expression { $$ = $1 * $3; } @@ -38,10 +48,12 @@ expression $$ = $1 / $3; } | '-' number { $$ = -$2; } + | '*' expression { $$ = vaddr_read($2, WORD_BYTES); } | '(' expression ')' { $$ = $2; } number - : NUMBER + : REGISTER + | NUMBER | HEX_NUMBER %% diff --git a/nemu/src/monitor/sdb/sdb.c b/nemu/src/monitor/sdb/sdb.c index 1212193..3b56172 100644 --- a/nemu/src/monitor/sdb/sdb.c +++ b/nemu/src/monitor/sdb/sdb.c @@ -16,6 +16,8 @@ #include "sdb.h" #include "common.h" #include "sys/types.h" +#include +#include #include #include #include @@ -23,15 +25,15 @@ #include #include #include -#include -#include static int is_batch_mode = false; // command handlers static int cmd_help(char *args); static int cmd_c(char *args); +static int cmd_p(char *args); static int cmd_q(char *args); +static int cmd_w(char *args); static int cmd_x(char *args); static int cmd_si(char *args); static int cmd_info(char *args); @@ -53,8 +55,10 @@ static struct CommandTable { {"help", "Display information about all supported commands", cmd_help, NULL, 0}, {"c", "Continue the execution of the program", cmd_c, NULL, 0}, + {"p", "Print expression result", cmd_p, NULL, 0}, {"q", "Exit NEMU", cmd_q, NULL, 0}, {"x", "Examine content of physical memory address", cmd_x, NULL, 0}, + {"w", "Break when expression is changed", cmd_w, NULL, 0}, {"si", "Execute next [n] program line", cmd_si, NULL, 0}, {"info", "Print information of registers or watchpoints", cmd_info, cmd_info_table, ARRLEN(cmd_info_table)}, @@ -125,17 +129,17 @@ static word_t parse_uint(const char *arg, bool *success) { } } -static vaddr_t parse_expr(const char *arg, bool *success) { +word_t parse_expr(const char *arg, bool *success) { if (arg == NULL) { puts("Invalid expr argument."); *success = false; return 0; } else { - vaddr_t addr; + word_t res; yy_scan_string(arg); - *success = !yyparse(&addr); + *success = !yyparse(&res); yylex_destroy(); - return addr; + return res; } } @@ -144,6 +148,22 @@ static int cmd_c(char *args) { return 0; } +static int cmd_p(char *args) { + char *arg = strtok(NULL, ""); + bool res = false; + + word_t result = parse_expr(arg, &res); + if (!res) + goto wrong_usage; + printf("%s: %u\n", arg, result); + return 0; + +wrong_usage: + printf("Invalid argument for command p: %s\n", arg); + printf("Usage: p [EXPR: ]\n"); + return 0; +} + static int cmd_q(char *args) { nemu_state.state = NEMU_QUIT; return -1; @@ -181,6 +201,12 @@ static int cmd_info_w(char *args) { return 0; } +static int cmd_w(char *args) { + char *expr = strtok(NULL, ""); + wp_add(expr); + return 0; +} + static int cmd_x(char *args) { char *arg = strtok(NULL, " "); bool res = false; diff --git a/nemu/src/monitor/sdb/sdb.h b/nemu/src/monitor/sdb/sdb.h index e94846c..883f1b6 100644 --- a/nemu/src/monitor/sdb/sdb.h +++ b/nemu/src/monitor/sdb/sdb.h @@ -18,4 +18,8 @@ #include +word_t parse_expr(const char *arg, bool *success); +int wp_add(char * expr); +int wp_remove_by_number(int number); + #endif diff --git a/nemu/src/monitor/sdb/watchpoint.c b/nemu/src/monitor/sdb/watchpoint.c index 922c8f4..aa48d78 100644 --- a/nemu/src/monitor/sdb/watchpoint.c +++ b/nemu/src/monitor/sdb/watchpoint.c @@ -1,36 +1,38 @@ /*************************************************************************************** -* Copyright (c) 2014-2022 Zihao Yu, Nanjing University -* -* NEMU is licensed under Mulan PSL v2. -* You can use this software according to the terms and conditions of the Mulan PSL v2. -* You may obtain a copy of Mulan PSL v2 at: -* http://license.coscl.org.cn/MulanPSL2 -* -* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -* -* See the Mulan PSL v2 for more details. -***************************************************************************************/ + * Copyright (c) 2014-2022 Zihao Yu, Nanjing University + * + * NEMU is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan + *PSL v2. You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY + *KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + *NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * + * See the Mulan PSL v2 for more details. + ***************************************************************************************/ #include "sdb.h" +#include +#include #define NR_WP 32 typedef struct watchpoint { int NO; struct watchpoint *next; - - /* TODO: Add more members if necessary */ - + word_t val; + char *expr; } WP; static WP wp_pool[NR_WP] = {}; -static WP *head = NULL, *free_ = NULL; +static WP *head = NULL, *tail = NULL, *free_ = NULL; +static int wp_count = 0; void init_wp_pool() { int i; - for (i = 0; i < NR_WP; i ++) { + for (i = 0; i < NR_WP; i++) { wp_pool[i].NO = i; wp_pool[i].next = (i == NR_WP - 1 ? NULL : &wp_pool[i + 1]); } @@ -39,5 +41,110 @@ void init_wp_pool() { free_ = wp_pool; } -/* TODO: Implement the functionality of watchpoint */ +static WP *wp_new() { + if (free_ == NULL) { + Error("wp_pool: Watchpoint pool not initialized or is full."); + return NULL; + } + + WP *ret = free_; + free_ = free_->next; + ret->NO = 0; + ret->next = NULL; + return ret; +} + +static void wp_delete(WP *wp) { + Assert(wp, "Failed to delete watchpoint from pool."); + wp->next = free_; + free_ = wp; +} + +int wp_add(char * expr) { + WP *wp = wp_new(); + if (wp == NULL) { + Error("watchpoint: Failed to add watchpoint, pool is full."); + goto failed_create; + } + + wp->NO = wp_count++; + if (tail == NULL) { + head = wp; + tail = wp; + } else { + tail->next = wp; + tail = wp; + } + + bool success = false; + wp->val = parse_expr(expr, &success); + if (!success) { + Error("Failed to parse given expression `%s`", expr); + goto failed_create; + } + + int len = strlen(expr); + wp->expr = malloc((len + 1) * sizeof(char)); + if (wp->expr == NULL) { + Error("Failed to allocate memory for expression"); + goto failed_create; + } + strncpy(wp->expr, expr, len + 1); + wp->expr[len] = '\0'; + return 0; + +failed_create: + wp_delete(wp); + return 1; +} + +int wp_remove_by_number(int number) { + WP *target_prev; + // Find previous node of target number + for (target_prev = head; target_prev != NULL && target_prev->next->NO != number; target_prev = target_prev->next) ; + if (target_prev == NULL) { + Error("Watchpoint not found, you can check current watchpoints with `info w`"); + return 1; + } + WP *target = target_prev->next; + target_prev->next = target->next; + if (target == head) { + head = target->next; + } else if (target == tail) { + tail = target_prev; + } + wp_delete(target); + return 0; +} + +static bool wp_check_change(WP* wp) { + bool success = false; + word_t result; + + result = parse_expr(wp->expr, &success); + if (!success) { + panic("Failed to evaluate expression `%s`", wp->expr); + } + if (result != wp->val) { + wp->val = result; + return true; + } + return false; +} + +/* + Check if watchpoint value changed after execution +*/ +bool wp_eval_all() { + WP *wp; + bool value_change = false; + for (wp = head; wp != NULL; wp = wp->next) { + int prev_val = wp->val; + if (wp_check_change(wp)) { + printf("Watchpoint %d: %s\n %u -> %u\n", wp->NO, wp->expr, prev_val, wp->val); + value_change = true; + } + } + return value_change; +} diff --git a/nemu/tests/expr_test.c b/nemu/tests/expr_test.c index 87c1882..d581cf2 100644 --- a/nemu/tests/expr_test.c +++ b/nemu/tests/expr_test.c @@ -1,5 +1,6 @@ +#include "macro.h" #include "sys/types.h" -#include "unistd.h" +#include #include #include #include @@ -9,6 +10,8 @@ #include #include #include +#include +#include char buf[65536] = {}, ref_buf[65536] = {}; static char code_buf[65536 + 128] = {}; // a little larger than `buf` @@ -147,6 +150,12 @@ struct { {"-0x1", 0xFFFFFFFFU}, {"0--1", 0x1}, {"0--0x1", 0x1}, +}, reg_exprs[] = { + {"$ra", 0x1}, + {"0x2 + 4*-$a7", 0xFFFFFFBEU}, + {"0x1831/$gp + 13", 2077U}, + {"$$0 == 123", 0}, + {"$$0 == 0", 1}, }; START_TEST(test_expr_negative_operand) { yy_scan_string(exprs[_i].expr); @@ -160,6 +169,47 @@ START_TEST(test_expr_negative_operand) { } END_TEST +extern const char *regs[]; +START_TEST(test_expr_plain_register) { + int i, j, result; + char buf[30] = {}; + uint32_t value; + // NOTE: need to fix this if want to support more arch + buf[0] = '$'; + for (i = 0; i < 32; i++) { + ck_assert(strncpy(buf + 1, regs[i], 10) != NULL); + gpr(i) = i; + yy_scan_string(buf); + result = yyparse(&value); + yylex_destroy(); + ck_assert_msg(result == 0, "expr = %s\n", buf); + + ck_assert(value == i); + for (j = 1; j < 10; j++) { + buf[j] = '\0'; + } + } + +} +END_TEST + +START_TEST(test_expr_register) { + int i; + uint32_t value; + for (i = 0; i < 32; i++) { + gpr(i) = i; + } + + yy_scan_string(reg_exprs[_i].expr); + ck_assert(!yyparse(&value)); + yylex_destroy(); + + ck_assert_msg(value == reg_exprs[_i].reference, + "\n\texpr = %s\n\t(addr = %u) != (reference = %u)\n", reg_exprs[_i].expr, + value, reg_exprs[_i].reference); +} +END_TEST + Suite *expr_suite(void) { Suite *s; TCase *tc_core; @@ -170,6 +220,9 @@ Suite *expr_suite(void) { tcase_add_loop_test(tc_core, test_expr_random_100, 0, 20); tcase_add_loop_test(tc_core, test_expr_negative_operand, 0, sizeof(exprs) / sizeof(exprs[0])); + tcase_add_loop_test(tc_core, test_expr_register, 0, + sizeof(reg_exprs) / sizeof(reg_exprs[0])); + tcase_add_test(tc_core, test_expr_plain_register); suite_add_tcase(s, tc_core); return s;