监视点的功能是监视一个表达式的值何时发生变化. 如果你从来没有使用过监视点, 请在GDB中体验一下它的作用.
简易调试器允许用户同时设置多个监视点, 删除监视点, 因此我们最好使用链表将监视点的信息组织起来. 框架代码中已经定义好了监视点的结构体(在nemu/include/monitor/watchpoint.h中):
typedef struct watchpoint {
int NO;
struct watchpoint *next;
/* TODO: Add more members if necessary */
} WP;
但结构体中只定义了两个成员: NO表示监视点的序号, next就不用多说了吧. 为了实现监视点的功能, 你需要根据你对监视点工作原理的理解在结构体中增加必要的成员. 同时我们使用"池"的数据结构来管理监视点结构体, 框架代码中已经给出了一部分相关的代码(在nemu/src/monitor/debug/watchpoint.c中):
static WP wp_pool[NR_WP];
static WP *head, *free_;
代码中定义了监视点结构的池wp_pool, 还有两个链表head和free_, 其中head用于组织使用中的监视点结构, free_用于组织空闲的监视点结构, init_wp_pool()函数会对两个链表进行了初始化.
实现了监视点池的管理之后, 我们就可以考虑如何实现监视点的相关功能了. 具体的, 你需要实现以下功能:
当用户给出一个待监视表达式时, 你需要通过new_wp()申请一个空闲的监视点结构, 并将表达式记录下来.
每当cpu_exec()执行完一条指令, 就对所有待监视的表达式进行求值(你之前已经实现了表达式求值的功能了),
比较它们的值有没有发生变化, 若发生了变化, 程序就因触发了监视点而暂停下来,
你需要将nemu_state变量设置为NEMU_STOP来达到暂停的效果.
最后输出一句话提示用户触发了监视点, 并返回到ui_mainloop()循环中等待用户的命令.
使用info w命令来打印使用中的监视点信息, 至于要打印什么, 你可以参考GDB中info watchpoints的运行结果.
使用d命令来删除监视点, 你只需要释放相应的监视点结构即可.
断点的功能是让程序暂停下来, 从而方便查看程序某一时刻的状态. 事实上, 我们可以很容易地用监视点来模拟断点的功能:
其中ADDR为设置断点的地址. 这样程序执行到ADDR的位置时就会暂停下来.
调试器设置断点的工作方式和上述通过监视点来模拟断点的方法大相径庭. 事实上, 断点的工作原理, 竟然是三十六计之中的"偷龙转凤"! 如果你想揭开这一神秘的面纱, 你可以阅读这篇文章. 了解断点的工作原理之后, 可以尝试思考下面的两个问题.