1. NEMU PA1实验概览与核心目标
NEMU(NJU Emulator)是南京大学计算机系统基础课程的核心实验平台,PA1作为第一个编程作业,承担着从理论到实践的关键过渡作用。这个实验看似简单,却包含了计算机系统最基础的三个能力模块:单步执行机制、表达式求值系统和完整实验框架的实现。我在实际完成这个实验时发现,很多同学(包括当年的我自己)容易陷入"只求通过测试用例"的误区,而忽略了这些基础模块在整个计算机体系中的重要意义。
单步执行不仅仅是实现一个简单的指令步进功能,它实际上是现代调试器的雏形。在真实的CPU中,单步执行依赖于硬件断点和陷阱标志位的配合,而在NEMU中我们需要用软件模拟这一机制。表达式求值则直接关联到编译器的前端处理流程,特别是当涉及中缀表达式转后缀表达式时,这个算法会在后续的编译器实验中反复出现。至于完整实验实现,它教会我们如何在一个已有框架中添加新功能,这种能力在参与开源项目或企业级开发时尤为重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 单步执行机制的实现细节
2.1 基础执行流程改造
NEMU默认的执行模式是连续运行直到遇到断点或程序结束。要实现单步执行,首先需要理解CPU执行指令的底层循环。在cpu-exec.c文件中,我们可以找到主执行循环:
c复制void cpu_exec(uint64_t n) {
for (;n > 0; n--) {
exec_wrapper();
}
}
改造方案是在每次执行exec_wrapper()后检查单步执行标志位。我建议添加一个全局变量is_single_step和对应的设置函数:
c复制static bool is_single_step = false;
void set_single_step(bool flag) {
is_single_step = flag;
}
然后在主循环中加入中断检查逻辑:
c复制void cpu_exec(uint64_t n) {
for (;n > 0; n--) {
exec_wrapper();
if (is_single_step) {
is_single_step = false;
break;
}
}
}
注意:这里不能直接返回,因为需要保证CPU状态的一致性。我在第一次实现时犯了这个错误,导致寄存器状态没有正确保存。
2.2 断点与单步的协同处理
真正的挑战在于处理单步执行与断点的交互。当同时存在断点和单步执行请求时,应该优先响应哪个?经过多次测试,我发现最合理的处理顺序是:
- 执行当前指令
- 检查断点触发条件
- 检查单步执行标志
- 更新PC寄存器
这个顺序确保了即使单步执行被触发,断点检查仍然有效。实现代码大致如下:
c复制void exec_wrapper() {
DecodeExecState s;
s.is_jmp = false;
s.seq_pc = cpu.pc;
// 实际指令执行
isa_exec_once(&s);
// 更新PC(除非是跳转指令)
if (!s.is_jmp) {
cpu.pc += s.width;
}
// 先检查断点
if (check_breakpoints(cpu.pc)) {
return;
}
// 再检查单步执行
if (is_single_s
