简介:本资源是《指令系统实现详解》实验配套学习材料,面向计算机体系结构初学者、高校计算机专业学生及对底层指令执行机制感兴趣的开发者,聚焦解决指令取指、译码与执行全流程的实践理解难题。压缩包为单文件PDF,大小1.46MB,内容完整覆盖实验概述、框架代码解析(含fetchInstruction/decode等核心函数说明)、寄存器管理、多类指令(ADD/SUB/MOV/POP/PUSH/JMP等)的细节实现逻辑,以及打印变量、栈帧调试等关键环节。文中深入剖析了译码模板复用策略、有符号立即数扩展、单字节跳转偏移计算、奇偶校验与零标志更新等易错点,并附带清晰的代码组织说明与调试建议。目前已有1047人学习下载,适合用于课程实验复盘、汇编与CPU微架构入门实践及指令集开发能力筑基。
1. 指令系统实现:从取指-译码-执行闭环到可调试、可验证的完整教学级CPU模拟器
你有没有试过,在调试一条ADD R1, R2, #5指令时,程序在decode()函数里突然跳进一个宏展开的黑匣子,寄存器值对不上、PC没更新、栈帧打印全是乱码?这不是编译器bug,而是指令系统实现中「流程断点不可见」导致的典型翻车——而本方案要解决的,正是这个让无数学生卡在实验第二周的硬骨头:把抽象的“取指→译码→执行”三步模型,落地为每一步都可设断点、每条指令都可单步追踪、每个寄存器更新都可回溯的完整可运行框架。它不是教科书里的状态机图,而是一套经过某高校计算机组成原理课程连续三年迭代验证的实战模板:含清晰分层的源码结构(fetch/decode/exec/print四大职责分离)、带符号表解析的变量/栈帧打印模块、支持立即数符号扩展与大小端兼容的底层内存读写封装,以及最关键的——所有未实现指令统一 fallback 到UNIMPLEMENTED()并强制终止,避免静默崩溃。适合正在做「指令系统设计」「CPU模拟器课设」「汇编器原理实践」或「计算机体系结构毕设」的同学直接复用架构、替换指令集、接入自定义外设。
2. 总体架构设计:四层解耦结构与五大核心组件的职责划分
本方案采用严格分层设计,将指令生命周期拆解为四个正交模块,每个模块只依赖下层接口、不感知上层逻辑。这种结构让调试不再需要全局 grep,而是能精准定位到fetch.c查 PC 更新、decoder.h看模板匹配、exec_add.c跟算术逻辑——真正实现「改一行代码,只影响一个功能点」。所有源码组织在src/目录下,按功能划分为core/(核心流程)、reg/(寄存器文件)、mem/(内存模拟)、debug/(打印与调试)、inst/(指令实现)五个子目录,无交叉引用,编译时可独立开关模块。
2.1 取指模块:PC驱动的字节流控制器
取指是整个指令流水线的源头,其核心职责是:根据当前PC值从内存读取指令字节,并自动更新PC指向下一指令起始地址。本方案中,fetchInstruction()函数承担此任务,它不解析指令内容,只负责“搬运”:
// src/core/fetch.c int fetchInstruction(uint8_t *buf, uint32_t pc) { // 1. 从内存读取1字节(本实验所有指令均为单字节opcode) uint8_t opcode = mem_read_byte(pc); // 2. 将opcode存入缓冲区首字节 buf[0] = opcode; // 3. 更新PC:单字节指令则+1;若后续需读立即数,则由译码层决定总长度 // 注意:此处仅更新基础PC,复杂指令的PC偏移由decode返回值修正 return 1; // 返回本次取指消耗的字节数 }关键设计说明:
buf是调用方分配的缓冲区(通常为uint8_t inst_buf[4]),用于暂存指令原始字节;- 返回值
int表示本次取指实际读取的字节数,该值将被译码层用于计算下一条指令的 PC 起始位置;mem_read_byte()是内存抽象层接口,屏蔽了真实内存映射细节,便于后续接入 ROM/RAM 模拟;- 不在此处处理多字节指令:PC 更新逻辑完全交给
decode()的返回值驱动,避免取指层与指令格式强耦合。
2.2 译码模块:模板化指令识别与操作数提取引擎
译码层是连接硬件语义与软件实现的翻译官。本方案摒弃手写 switch-case 的易错模式,采用 C 宏模板 + 函数指针表的双重保障机制。所有译码逻辑集中在decoder.h,通过预定义宏生成类型安全的译码函数:
// src/core/decoder.h // 定义译码模板:READ_IMM8_REG8 → 读1字节立即数 + 1字节寄存器编号 #define DECODE_READ_IMM8_REG8(inst_buf, pc, imm_out, reg_out) do { \ (imm_out) = (int8_t)mem_read_byte((pc) + 1); /* 有符号扩展 */ \ (reg_out) = mem_read_byte((pc) + 2); /* 寄存器索引 */ \ } while(0) // 指令译码函数表(按opcode索引) typedef struct { const char* name; // 汇编助记符 int (*decode)(uint8_t*, uint32_t, void*); // 译码函数指针 int (*exec)(void*); // 执行函数指针 } inst_entry_t; // 全局译码表(示例片段) inst_entry_t inst_table[256] = { [0x01] = {"ADD", decode_add, exec_add}, // ADD Rn, Rm, #imm [0x02] = {"SUB", decode_sub, exec_sub}, // SUB Rn, Rm, #imm [0x03] = {"MOV", decode_mov, exec_mov}, // MOV Rn, #imm [0xFF] = {"UNIMPLEMENTED", NULL, exec_unimplemented} };关键设计说明:
DECODE_READ_IMM8_REG8宏封装了立即数符号扩展逻辑((int8_t)强制转换),避免开发者手动处理补码;inst_table是稀疏数组,未定义 opcode 默认指向UNIMPLEMENTED,保证非法指令必报错;decode_add()等函数接收inst_buf和pc,从中提取操作数并填充到void*参数(通常是exec_context_t*结构体),实现数据传递解耦;- 所有译码模板均以
pc为基准偏移,而非依赖全局PC变量,确保多线程/重入安全。
2.3 执行模块:寄存器文件驱动的状态变更中心
执行层是纯逻辑计算单元,它不访问内存、不修改PC、不调用IO,只做一件事:根据译码结果,读取寄存器值,执行运算,写回结果寄存器。本方案将寄存器抽象为regFile全局数组,所有指令执行函数通过统一接口访问:
// src/reg/register.h extern uint32_t regFile[16]; // 16个通用寄存器 R0-R15 // src/inst/exec_add.c int exec_add(void* ctx) { exec_context_t* ec = (exec_context_t*)ctx; // 1. 从译码结果中获取操作数(Rm, Rn, imm) uint32_t rm_val = regFile[ec->rm]; uint32_t rn_val = regFile[ec->rn]; int32_t imm_val = ec->imm; // 2. 执行加法(带溢出标志更新) uint64_t result64 = (uint64_t)rn_val + (uint64_t)imm_val; uint32_t result32 = (uint32_t)result64; // 3. 写回目标寄存器 regFile[ec->rd] = result32; // 4. 更新状态寄存器(NZCV) update_flags_nzcv(result32, result64 >> 32); return 0; // 成功 }关键设计说明:
exec_context_t是译码与执行间的数据载体,包含rd/rn/rm/imm/flags等字段,由译码函数填充;- 所有算术运算使用
uint64_t中间类型,避免32位截断导致的溢出误判;update_flags_nzcv()是独立函数,集中处理零标志(Z)、负标志(N)、进位(C)、溢出(V)的更新逻辑;- 禁止在 exec 函数中调用
mem_read/write或printf,保证执行层纯粹性,便于后续接入性能分析工具。
2.4 调试模块:符号表驱动的变量与栈帧可视化系统
调试能力决定项目交付质量。本方案将调试功能模块化为debug/目录,核心是两个可复用函数:print_variable()和print_stack_frame(),它们不依赖具体指令集,而是基于 ELF 符号表解析:
// src/debug/debug.c // 根据变量名查找符号并打印值 void print_variable(const char* var_name) { symbol_t* sym = find_symbol_by_name(var_name); if (!sym) { printf("ERROR: variable '%s' not found in symbol table\n", var_name); return; } // 读取变量所在内存地址的值(支持int/char/ptr等基础类型) uint32_t addr = sym->st_value; switch (sym->st_info & STT_TYPE_MASK) { case STT_OBJECT: if (sym->st_size == 4) { printf("%s = 0x%08x (%d)\n", var_name, mem_read_word(addr), mem_read_word(addr)); } else if (sym->st_size == 1) { printf("%s = 0x%02x (%d)\n", var_name, mem_read_byte(addr), mem_read_byte(addr)); } break; default: printf("%s: unsupported symbol type %d\n", var_name, sym->st_info & STT_TYPE_MASK); } } // 打印当前栈帧(含函数名、参数、局部变量) void print_stack_frame(uint32_t sp, uint32_t pc) { // 1. 根据PC查找所属函数名(遍历 .symtab 中 FUNC 类型符号) const char* func_name = find_function_name(pc); // 2. 读取栈顶3个字(返回地址、参数1、参数2) uint32_t ret_addr = mem_read_word(sp); uint32_t arg1 = mem_read_word(sp + 4); uint32_t arg2 = mem_read_word(sp + 8); printf("=== Stack Frame for %s ===\n", func_name ? func_name : "unknown"); printf("SP: 0x%08x | PC: 0x%08x | RetAddr: 0x%08x\n", sp, pc, ret_addr); printf("Args: %u, %u\n", arg1, arg2); }关键设计说明:
find_symbol_by_name()遍历.symtab段,比对st_name字符串索引,需配合.strtab解析真实名称;print_stack_frame()假设 AAPCS 栈布局(SP 指向栈顶,参数压栈顺序),可通过宏STACK_LAYOUT_AAPCS切换;- 所有内存读取走
mem_read_*接口,天然支持调试时注入内存快照;- 打印函数不修改任何状态,可安全插入任意指令执行前后进行观测。
2.5 指令集模块:可插拔式指令实现与扩展规范
指令实现位于src/inst/,每个指令对应独立.c文件(如exec_add.c,exec_push.c),遵循统一契约:
- 函数名格式:
exec_<mnemonic>(小写,下划线分隔); - 参数:
void* ctx(指向exec_context_t); - 返回:
0成功,-1错误; - 禁止全局变量,所有状态通过
ctx传递; - 必须调用
update_pc()更新程序计数器(除非是跳转指令)。
新增指令只需三步:
- 在
decoder.h的inst_table[]中添加新条目; - 编写
exec_<name>.c实现执行逻辑; - 在
Makefile的INST_OBJS变量中加入对应.o文件。
扩展性设计:
- 支持条件执行:
exec_bne.c中检查NZCV标志后决定是否跳转;- 支持寄存器间接寻址:
exec_ldr.c中mem_read_word(regFile[rm] + imm);- 支持伪指令:
exec_nop.c本质是空操作,但保留 opcode 占位便于调试对齐。
3. 关键实现细节:从立即数扩展到栈帧解析的七处硬核落点
指令系统不是拼凑函数,而是对计算机底层行为的精确建模。以下七处实现细节,是本方案经多次答辩验证的「高光落点」,每一处都对应一个必须亲手敲过的代码块,且附带真实调试场景说明。
3.1 有符号立即数的单字节扩展:为什么(int8_t)0xFF等于-1
在ADD R0, R1, #-1这类指令中,机器码中的立即数0xFF必须被解释为-1,而非255。错误处理会导致所有减法指令结果翻倍。本方案在译码层强制使用int8_t类型转换:
// src/decoder/decode_add.c int decode_add(uint8_t* inst_buf, uint32_t pc, void* ctx) { exec_context_t* ec = (exec_context_t*)ctx; // 关键:立即数必须有符号扩展! ec->imm = (int8_t)mem_read_byte(pc + 1); // pc+1 是立即数位置 // 寄存器编号直接读取(无符号) ec->rn = mem_read_byte(pc + 2) & 0x0F; // 低4位为Rn ec->rm = mem_read_byte(pc + 2) >> 4; // 高4位为Rm ec->rd = mem_read_byte(pc + 3); // 目标寄存器 return 4; // ADD指令共4字节:opcode + imm + Rn/Rm + Rd }调试验证:在 GDB 中对
decode_add设断点,执行p/x $rax查看ec->imm值。若未加(int8_t),0xFF显示为0x000000ff(255);加后显示为0xffffffff(-1),证明符号扩展生效。
3.2 PC相对跳转的地址计算:BNE label如何算出目标地址
条件跳转指令BNE的机器码包含一个1字节有符号偏移量,需将其扩展为32位并加到当前PC上。难点在于:PC在取指后已指向下一条指令,因此跳转目标 = 当前PC + 偏移量 × 2(因ARM Thumb模式下PC对齐到半字)。本方案在exec_bne.c中实现:
// src/inst/exec_bne.c int exec_bne(void* ctx) { exec_context_t* ec = (exec_context_t*)ctx; // 1. 获取当前PC(注意:这是取指后的PC,已+2) uint32_t current_pc = get_pc(); // 从全局寄存器或上下文读取 // 2. 读取1字节偏移量并符号扩展 int32_t offset = (int8_t)ec->imm; // 3. 计算目标地址:PC + offset * 2(Thumb模式) uint32_t target_pc = current_pc + (offset << 1); // 4. 检查N/Z标志:仅当Z=0时跳转 if ((get_flags() & FLAG_Z) == 0) { set_pc(target_pc); // 强制更新PC return 0; // 跳转成功 } return 0; // 不跳转,继续顺序执行 }关键点:
offset << 1等价于offset * 2,是 Thumb 指令集的硬性要求;若忽略此步,跳转地址会偏移一半,导致程序跑飞。
3.3 PUSH指令的栈操作:先更新SP再写内存的原子性保障
PUSH {R0-R3}需将多个寄存器压栈,其行为是:SP = SP - 4×n,然后依次将 R3,R2,R1,R0 存入 [SP],[SP+4],[SP+8],[SP+12]。错误实现(如先写内存再减SP)会导致栈溢出覆盖。本方案在exec_push.c中严格按序执行:
// src/inst/exec_push.c int exec_push(void* ctx) { exec_context_t* ec = (exec_context_t*)ctx; uint32_t sp = get_sp(); uint32_t n = ec->reg_count; // 压栈寄存器数量 // 1. 原子性更新SP:一次减去总空间 uint32_t new_sp = sp - (n * 4); set_sp(new_sp); // 2. 从高编号寄存器开始写入(栈向下增长) for (int i = n - 1; i >= 0; i--) { uint32_t reg_val = regFile[ec->regs[i]]; mem_write_word(new_sp + (i * 4), reg_val); } return 0; }验证方法:在
main()中初始化R0=0x1111,R1=0x2222,R2=0x3333,R3=0x4444,执行PUSH {R0-R3}后,用mem_read_word(sp)检查0x4444是否在栈顶。
3.4 寄存器文件的位域安全访问:为什么regFile[15]不能直接当 CPSR 用
ARM 架构中 R15 是 PC,但某些指令(如MRS) 需读取 CPSR(当前程序状态寄存器)。本方案在register.h中明确定义:
// src/reg/register.h extern uint32_t regFile[16]; // R0-R14 extern uint32_t cpsr; // 分离的CPSR寄存器 // 读取CPSR的专用函数(非直接访问regFile[15]) static inline uint32_t get_cpsr(void) { return cpsr; } // 写入CPSR(需校验保留位) static inline void set_cpsr(uint32_t val) { // 清除保留位(bit 20-31),仅保留 NZCV、I/F、T、M[4:0] cpsr = (val & 0xF0000000) | (val & 0x000000FF); }血泪经验:曾有学生直接
regFile[15] = 0x80000000试图置位 T-bit,结果因未屏蔽保留位导致 CPU 进入未定义状态。分离 CPSR 是规避此类风险的强制约定。
3.5 符号表解析的字符串解引用:.strtab中的变量名如何被print_variable()找到
ELF 文件的.symtab段存储符号结构体,其中st_name是一个索引值,需查.strtab才能得到真实字符串。本方案在debug/symbol.c中实现安全查找:
// src/debug/symbol.c // 全局符号表和字符串表指针(由loader初始化) extern uint8_t* symtab_start; extern uint8_t* strtab_start; extern size_t symtab_size; symbol_t* find_symbol_by_name(const char* name) { size_t sym_count = symtab_size / sizeof(Elf32_Sym); for (size_t i = 0; i < sym_count; i++) { Elf32_Sym* sym = (Elf32_Sym*)(symtab_start + i * sizeof(Elf32_Sym)); // 1. 跳过未定义符号(st_name == 0) if (sym->st_name == 0) continue; // 2. 从.strtab中读取字符串 const char* sym_name = (const char*)(strtab_start + sym->st_name); // 3. 字符串比较(安全,.strtab以\0结尾) if (strcmp(sym_name, name) == 0) { return sym; } } return NULL; }关键防护:
strcmp()前不校验sym->st_name是否越界.strtab大小,故在 loader 初始化时必须确保strtab_start和strtab_size正确,否则strcmp会触发段错误。
3.6 栈帧中函数名的逆向查找:如何从 PC 值定位到.symtab中的 FUNC 符号
print_stack_frame()需根据当前 PC 找到所属函数名,这要求遍历.symtab中所有STT_FUNC类型符号,并检查 PC 是否落在st_value到st_value+st_size区间内:
// src/debug/debug.c const char* find_function_name(uint32_t pc) { size_t sym_count = symtab_size / sizeof(Elf32_Sym); for (size_t i = 0; i < sym_count; i++) { Elf32_Sym* sym = (Elf32_Sym*)(symtab_start + i * sizeof(Elf32_Sym)); // 仅检查函数符号 if (ELF32_ST_TYPE(sym->st_info) != STT_FUNC) continue; // 检查PC是否在函数地址范围内 if (pc >= sym->st_value && pc < sym->st_value + sym->st_size) { return (const char*)(strtab_start + sym->st_name); } } return NULL; }性能提示:此函数在调试时调用,非实时路径,无需优化;若用于性能分析,应构建函数地址索引表(B+树)。
3.7 大端模式兼容:mem_read_word()如何应对字节序反转
虽然本实验默认小端,但为未来扩展,mem_read_word()封装了字节序适配:
// src/mem/memory.c uint32_t mem_read_word(uint32_t addr) { uint8_t bytes[4]; bytes[0] = mem_read_byte(addr + 0); // LSB bytes[1] = mem_read_byte(addr + 1); bytes[2] = mem_read_byte(addr + 2); bytes[3] = mem_read_byte(addr + 3); // MSB #ifdef __BIG_ENDIAN__ return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; #else return (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]; #endif }验证方式:在
main.c中写uint32_t test = 0x12345678; mem_write_word(0x1000, test);,然后mem_read_word(0x1000)应返回0x12345678,无论编译选项。
4. 避坑指南:指令系统开发中必踩的五处深坑与自救方案
指令系统开发最耗时的往往不是写代码,而是排查那些「看起来没错,但就是不工作」的玄学问题。以下是本方案团队在三次课程实践中总结的五大高频翻车点,每一条都来自真实调试记录,附带现象、根因与可立即执行的解决方案。
4.1 现象:ADD R0, R1, #5执行后R0值为0,而非R1+5
原因:译码函数中未正确提取R1的寄存器编号,或exec_add()中读取了错误的regFile索引。常见错误包括:
- 将
inst_buf[2]当作Rn,但实际Rn存储在inst_buf[1]; - 使用
regFile[inst_buf[2]]时未屏蔽高4位,导致索引超出0-15范围(如inst_buf[2]=0x81,直接取regFile[129]越界); exec_context_t结构体字段未初始化,ec->rn为随机值。
解决:
- 在
decode_add()开头添加printf("DEBUG: rn=%d, rm=%d, imm=%d\n", ec->rn, ec->rm, ec->imm);; - 确保所有寄存器索引操作使用
& 0x0F掩码(如ec->rn = inst_buf[1] & 0x0F); - 在
exec_add()开头assert(ec->rn < 16 && ec->rm < 16),编译时加-D_GNU_SOURCE启用 assert。
4.2 现象:程序执行到BNE loop后无限循环,但Z标志明明为0
原因:exec_bne()中读取的Z标志来自旧状态,未在ADD执行后及时更新。根本原因是update_flags_nzcv()调用位置错误——它必须在每条影响标志的指令(ADD/SUB/CMP)执行后立即调用,而非在exec_bne()中重复检查。
解决:
- 删除
exec_bne()中所有get_flags()调用; - 在
exec_add()、exec_sub()等函数末尾强制调用update_flags_nzcv(); - 为
exec_bne()添加前置检查:if ((get_flags() & FLAG_Z) == 0) { ... },确保读取的是最新标志。
4.3 现象:print_variable("counter")输出counter = 0x00000000,但 gdb 显示其值为42
原因:find_symbol_by_name()返回了.bss段的符号,但.bss未初始化,mem_read_word()读到全零。实际变量值在.data段,而符号表中存在同名的.bss和.data条目,find_symbol_by_name()返回了第一个匹配项(通常是.bss)。
解决:
- 修改
find_symbol_by_name(),增加对st_shndx的检查:if (sym->st_shndx != SHN_UNDEF && (sym->st_info & STT_TYPE_MASK) == STT_OBJECT) { // 优先返回已定义的符号(.data/.rodata),跳过SHN_UNDEF(.bss) } - 在 linker script 中确保
.bss符号不进入.symtab,或使用--discard-all链接选项。
4.4 现象:PUSH {R0,R1}后栈内存中R0和R1值互换
原因:exec_push()中写入顺序错误。ARM AAPCS 规定 PUSH 时高编号寄存器先入栈(即R1在R0下方),但代码中按i=0到i=n-1顺序写入,导致R0被写入更低地址。
解决:
- 严格按
for (int i = n-1; i >= 0; i--)降序循环; - 在循环内添加
printf("PUSH: reg[%d]=0x%08x to addr 0x%08x\n", ec->regs[i], regFile[ec->regs[i]], new_sp + (i*4));验证地址顺序; - 对比 ARM ARM 文档中 PUSH 指令的内存布局图。
4.5 现象:编译通过,但运行时报Segmentation fault,GDB 显示崩溃在mem_read_byte(0xdeadbeef)
原因:mem_read_byte()未做地址边界检查,而fetchInstruction()传入了非法 PC(如0xdeadbeef)。这通常源于跳转指令计算错误,或UNIMPLEMENTED指令未正确终止。
解决:
- 在
mem_read_byte()开头添加:if (addr >= MEM_SIZE) { fprintf(stderr, "FATAL: memory read out of bounds: 0x%08x (max=0x%08x)\n", addr, MEM_SIZE); exit(1); } - 确保所有
exec_unimplemented()函数中调用exit(1),而非return; - 在
main()循环中添加if (get_pc() >= MEM_SIZE) { panic("PC overflow"); }。
5. 验收与答辩:一份可演示、可讲解、可复现的交付清单
验收不是交一份能跑的代码,而是向导师证明你真正理解了指令系统的每一个齿轮如何咬合。本方案提供一套完整的交付物组合,覆盖「现场演示」「文档答辩」「代码审查」三大场景,每项都可独立验证,且全部基于你本地环境可复现。
5.1 现场演示 checklist:5分钟内完成的黄金动作链
准备一个demo.sh脚本,确保导师双击即可启动演示。脚本必须包含以下 5 个原子动作,每个动作后暂停 10 秒供导师观察:
| 步骤 | 命令 | 预期输出 | 验证点 |
|---|---|---|---|
| 1. 启动调试器 | ./cpu_sim --debug | CPU initialized. PC=0x00000000 | 确认模拟器加载成功 |
| 2. 单步执行 ADD | step | EXEC: ADD R0, R1, #5 → R0=0x00000005 | 寄存器更新正确 |
| 3. 查看变量 | var counter | counter = 0x0000002A (42) | 符号表解析准确 |
| 4. 打印栈帧 | stack | `=== Stack Frame for main === SP: 0x00002000 | RetAddr: 0x00001004` |
| 5. 触发错误 | step(在 UNIMPLEMENTED 指令处) | FATAL: UNIMPLEMENTED instruction 0xFF at PC=0x00001008 | 错误处理机制生效 |
答辩技巧:演示时不要背诵输出,而是边操作边解释:“您看到这里
R0从0变成5,是因为exec_add()读取了R1的值0,加上立即数5,再写回R0——这完整复现了硬件中 ALU 的加法通路”。
5.2 文档答辩核心页:三张图讲清你的设计哲学
导师最关注的不是代码行数,而是你能否用一张图说清架构。准备以下三张图,嵌入答辩 PPT(每张图配 30 字以内说明):
图1:四层数据流图
![fetch→decode→exec→debug]
指令字节从内存出发,经取指获取 opcode,译码提取操作数,执行修改寄存器,调试层消费状态——各层仅依赖下层输出,无反向调用。
图2:寄存器文件内存布局图[R0][R1]...[R14][PC][CPSR]
16 个通用寄存器线性排列,PC 与 CPSR 分离存储,避免 R15 语义混淆;所有访问走regFile[]数组,无宏魔术。
图3:符号表解析流程图.symtab→st_name→.strtab→strcmp()→mem_read_word()
变量名查找是标准 ELF 流程:符号索引 → 字符串表解引用 → 内存地址读取 —— 你实现了工业级兼容性。
5.3 代码审查重点:导师必问的三个灵魂问题
代码提交前,务必自测以下三点,它们是答辩时导师提问率 100% 的问题:
**Q1:exec_add()中为什么用uint64_t中间类型?直接uint32_t相加不行吗?
本文还有配套的精品资源,点击获取