PA1 监视点与表达式求值扩展:结合 NEMU 源码实现 SDB Watchpoint
发布时间:2026/10/1 3:06:35来源:尧图网络
PA1 监视点与表达式求值扩展结合 NEMU 源码实现 SDB Watchpoint本文结合ysyx-workbench/nemu的实际代码讲解 PA1 中监视点与表达式求值扩展的完整实现。重点包括expr()表达式求值、w/d/info w命令、监视点链表、和 CPU 执行循环的配合。摘要监视点的本质是保存一个表达式 保存表达式的上一次值 每条指令执行后重新计算表达式 如果值发生变化就暂停客户程序在 NEMU 中监视点对应以下代码nemu/src/monitor/sdb/ ├── expr.c ├── sdb.c ├── sdb.h └── watchpoint.cCPU 执行循环则位于nemu/src/cpu/cpu-exec.c完整调用关系可以概括为w EXPR - cmd_w - new_wp - expr 计算初始值 每条指令执行后 - check_watchpoints - expr 重新计算 - 如果值改变NEMU_STOP关键词NEMU、SDB、PA1、watchpoint、expr、riscv32、调试器、ysyx一、监视点的功能讲义要求监视一个表达式的值何时发生变化。例如w $a0表示监视寄存器a0。w *0x80000000表示监视内存地址0x80000000处的值。程序执行时只要表达式的值发生了变化NEMU 就应该暂停Watchpoint 1 changed: 0x00000000 - 0x00000001二、监视点的数据结构文件nemu/src/monitor/sdb/sdb.h代码#define MAX_WATCHPOINTS 32 typedef enum { WP_EXPRESSION, WP_BREAKPOINT } WPType; typedef struct watchpoint { int NO; WPType type; char expr[128]; word_t last_value; struct watchpoint *next; } WP;字段含义字段作用NO监视点编号type表达式监视点或断点expr表达式字符串last_value上一次求值结果next链表指针监视点使用一个静态池管理static WP wp_pool[MAX_WATCHPOINTS] {}; static WP *head NULL, *free_ NULL; void init_wp_pool() { int i; for (i 0; i MAX_WATCHPOINTS - 1; i) { wp_pool[i].NO i; wp_pool[i].next wp_pool[i 1]; } wp_pool[MAX_WATCHPOINTS - 1].NO MAX_WATCHPOINTS - 1; wp_pool[MAX_WATCHPOINTS - 1].next NULL; head NULL; free_ wp_pool; }其中head 已使用的监视点/断点链表 free_ 空闲监视点链表三、设置监视点cmd_w文件nemu/src/monitor/sdb/sdb.c代码static int cmd_w(char *args) { if(args NULL) { printf(Usage: w expr\n); return 0; } new_wp(args); return 0; }输入w $a0将调用new_wp($a0);四、创建监视点new_wp文件nemu/src/monitor/sdb/watchpoint.c代码WP* new_wp(char *exp_str) { if (free_ NULL) { printf(No free watchpoint!\n); return NULL; } WP *wp free_; free_ free_-next; wp-type WP_EXPRESSION; strncpy(wp-expr, exp_str, sizeof(wp-expr)-1); wp-expr[sizeof(wp-expr)-1] \0; bool succ; wp-last_value expr(exp_str, succ); if (!succ) { printf(Invalid expression: %s\n, exp_str); wp-next free_; free_ wp; return NULL; } wp-next head; head wp; printf(Watchpoint %d: %s 0x%08x\n, wp-NO 1, exp_str, wp-last_value); return wp; }流程从 free_ 取出一个 WP 保存表达式字符串 调用 expr() 计算初始值 如果表达式非法归还 WP 如果合法插入 head 链表示例w $a0 Watchpoint 1: $a0 0x00000000五、删除和查看监视点删除cmd_dstatic int cmd_d(char *args) { if (args NULL) { printf(Usage: d N\n); return 0; } int no; if (sscanf(args, %d, no) ! 1) { printf(Usage: d N\n); return 0; } free_wp(no); return 0; }删除实现void free_wp(int no) { WP **p head; while (*p ! NULL) { if ((*p)-NO 1 no) { WP *tmp *p; *p (*p)-next; tmp-next free_; free_ tmp; printf(Delete watchpoint %d success\n, no); return; } p (*p)-next; } printf(Watchpoint %d not found\n, no); }查看info wif (strcmp(args, r) 0 || strcmp(args, reg) 0) { isa_reg_display(); } else if (strcmp(args, w) 0) { display_watchpoints(); }void display_watchpoints() { if (head NULL) { printf(No watchpoint or breakpoint\n); return; } printf(Num\tType\t\tInfo\n); for (WP *p head; p ! NULL; p p-next) { if (p-type WP_BREAKPOINT) { printf(%d\tbreakpoint\t0x%08x\n, p-NO 1, p-last_value); } else { printf(%d\twatchpoint\t%s 0x%08x\n, p-NO 1, p-expr, p-last_value); } } }六、监视点检查check_watchpoints代码bool check_watchpoints() { bool change false; for (WP *p head; p ! NULL; p p-next) { if (p-type ! WP_EXPRESSION) continue; bool succ; word_t now expr(p-expr, succ); if (!succ) continue; if (now ! p-last_value) { printf(Watchpoint %d changed: 0x%08x - 0x%08x\n, p-NO 1, p-last_value, now); p-last_value now; change true; } } return change; }每一步遍历所有监视点 重新计算表达式 如果当前值 ! 上一次值 - 打印变化 - 更新 last_value - 返回 true七、监视点如何与 CPU 执行循环结合文件nemu/src/cpu/cpu-exec.c执行循环static void execute(uint64_t n) { Decode s; for (; n 0; n--) { if (nemu_state.state ! NEMU_RUNNING) { break; } if (check_breakpoints(cpu.pc)) { delete_bp_by_addr(cpu.pc); nemu_state.state NEMU_STOP; break; } exec_once(s, cpu.pc); g_nr_guest_inst; trace_and_difftest(s, cpu.pc); IFDEF(CONFIG_DEVICE, device_update()); } }监视点检查在static void trace_and_difftest(Decode *_this, vaddr_t dnpc) { ... if(check_watchpoints()) { nemu_state.state NEMU_STOP; } }因此执行一条指令 - check_watchpoints() - 如果表达式变化进入 NEMU_STOP八、表达式求值expr()监视点依赖表达式求值。文件nemu/src/monitor/sdb/expr.c支持十进制数 十六进制数 寄存器$a0、$sp、$pc - * / ! 括号 解引用*地址1. 正则规则static struct rule { const char *regex; int token_type; } rules[] { { , TK_NOTYPE}, {, TK_AND}, {, TK_EQ}, {!, TK_INEQ}, {\\, }, {\\-, -}, {\\*, *}, {\\/, /}, {0x([0-9A-Fa-f]), TK_HEX}, {[0-9], TK_DEC}, {\\$([$a-z0-9]), TK_RNAME}, {\\(, (}, {\\), )} };2. Token 化typedef struct token { int type; char str[64]; } Token; #define TOKEN_SIZE 128make_token()中增加了边界检查if (nr_token TOKEN_SIZE) { printf(Too many tokens\n); return false; } if (substr_len (int)sizeof(tokens[nr_token].str)) { printf(Token too long\n); return false; }3. 运算符优先级static int precedence(int type) { switch (type) { case TK_AND: return 0; case TK_EQ: case TK_INEQ: return 1; case : case -: return 2; case *: case /: return 3; default: return -1; } }4. 根运算符选择static int get_prime(int p, int q) { int pos -1; int min_prec 100; int bracket 0; for (int i p; i q; i) { if (tokens[i].type () { bracket; } else if (tokens[i].type )) { bracket--; } else if (bracket 0 Is_op(tokens[i].type)) { int prec precedence(tokens[i].type); if (pos -1 || prec min_prec) { pos i; min_prec prec; } } } return pos; }这样p 12*3会得到正确结果1 (2 * 3) 75. 递归求值static word_t eval(int p, int q, bool *success) { ... if (tokens[p].type TK_DEC) { return (word_t)strtoul(tokens[p].str, NULL, 10); } else if (tokens[p].type TK_HEX) { return (word_t)strtoul(tokens[p].str 2, NULL, 16); } else if (tokens[p].type TK_RNAME) { bool ok false; word_t value isa_reg_str2val(tokens[p].str 1, ok); if (!ok) { *success false; return 0; } return value; } ... }除零保护case /: if (v2 0) { printf(Division by zero\n); *success false; return 0; } return v1 / v2;九、$0寄存器问题expr.c会把$0变成0而原来的regs[0]是$0所以需要在nemu/src/isa/riscv32/reg.c中单独处理if (strcmp(s, 0) 0) { return cpu.gpr[0]; }测试p $0 $0 0x00000000 (0)十、调试示例1. 查看寄存器info r2. 设置监视点w $a0输出Watchpoint 1: $a0 0x000000003. 查看监视点info w输出Num Type Info 1 watchpoint $a0 0x000000004. 继续运行c如果 $a0 发生变化Watchpoint 1 changed: 0x00000000 - 0x000000015. 删除监视点d 1十一、常见问题与改进方向1. 表达式非法不应导致崩溃当前实现通过success返回失败而不是直接assertbool ok true; word_t result eval(0, nr_token - 1, ok); *success ok; return ok ? result : 0;这样p 1会提示表达式错误而不是让 NEMU 崩溃。2. 除零保护if (v2 0) { printf(Division by zero\n); *success false; return 0; }3. Token 缓冲区边界Token.str和TOKEN_SIZE都有边界检查避免长表达式越界。4. 监视点表达式合法性new_wp()会先调用expr()if (!succ) { printf(Invalid expression: %s\n, exp_str); wp-next free_; free_ wp; return NULL; }非法表达式不会被加入监视点链表。5. 尚可继续扩展一元负号p -1比较运算 位运算 | ^ 表达式溢出提示监视点性能优化十二、讲义要求与代码对应表讲义要求你的代码表达式求值nemu/src/monitor/sdb/expr.c监视点结构WP、WPType设置监视点cmd_w→new_wp删除监视点cmd_d→free_wp查看监视点cmd_info→display_watchpoints检查监视点变化check_watchpoints暂停执行nemu_state.state NEMU_STOP寄存器表达式isa_reg_str2val除零保护eval中/分支表达式错误处理expr的success参数十三、总结监视点与表达式求值的完整链路是expr() - 解析表达式 - 计算值 new_wp() - 保存表达式 - 保存初始值 check_watchpoints() - 每条指令后重新计算 - 值变化则暂停 cpu-exec.c - 检测 NEMU_STOP - 返回 SDB一句话总结expr()负责把表达式变成值watchpoint负责保存这个值并在每条指令后重新计算如果值发生变化就让 NEMU 停下来。这就是 PA1 监视点与表达式求值扩展的核心实现。
网站建设高端定制企业官网