这个秋季,三碗饭会开始更新自己在秋季课程的笔记,主要包括Introduction to ML,可能还会有Complier Construction 以及 Computer Graphics.
本文将介绍Complier Construction中关于模拟器,汇编以及反汇编的知识,在下一章我们会从0写一个简易模拟器,以及汇编,反汇编程序。这门课如果没有兴趣,会非常无聊非常无聊,当然我一开始也觉得挺无聊的,毕竟搞AI Agent比这个有意思多,但我还是会尽量以简单的描述(避免复杂的解释),以及有意思的例子去讲这些知识。
1. 概要
了解下之后我们会完成的三个内容。
汇编器:
将人类可读的汇编代码(.uma)转换为机器码(.um)
相当于"翻译官",把人类语言翻译成机器语言
反汇编器
将机器码(.um)转换回汇编代码(.uma)
相当于"解码器",把机器语言翻译回人类语言
主要用于调试和验证
模拟器:
执行机器码(.um)
相当于"虚拟CPU",实际运行程序
负责程序的实际执行
2. 核心组件
2.1 指令结构
反汇编器使用与汇编器相同的指令结构:
cpp
enum class BinIns {
HALT, // 停止程序
IN, // 输入
OUT, // 输出
ADD, // 加法
SUB, // 减法
MUL, // 乘法
DIV, // 除法
LDC, // 加载常数
INVALID // 无效指令
};
struct Instruction {
BinIns op; // 操作码
int arg; // 参数
};
2.2 主要数据结构
反汇编器类中包含两个关键的映射表:
cpp
class Disassembler {
private:
// 指令映射表(从二进制到汇编)
std::map<BinIns, std::string> binToAss;
// 指令参数个数表
std::map<BinIns, int> insArgNum;
};
binToAss
: 存储二进制操作码到汇编指令字符串的映射insArgNum
: 记录每个指令需要的参数个数
3. 核心功能实现
3.1 初始化映射表
cpp
void initMapping() {
binToAss[BinIns::HALT] = "HALT";
binToAss[BinIns::IN] = "IN";
binToAss[BinIns::OUT] = "OUT";
binToAss[BinIns::ADD] = "ADD";
binToAss[BinIns::SUB] = "SUB";
binToAss[BinIns::MUL] = "MUL";
binToAss[BinIns::DIV] = "DIV";
binToAss[BinIns::LDC] = "LDC";
}
3.2 初始化参数个数表
cpp
void initArgNumbers() {
insArgNum[BinIns::HALT] = 0;
insArgNum[BinIns::IN] = 0;
insArgNum[BinIns::OUT] = 0;
insArgNum[BinIns::ADD] = 0;
insArgNum[BinIns::SUB] = 0;
insArgNum[BinIns::MUL] = 0;
insArgNum[BinIns::DIV] = 0;
insArgNum[BinIns::LDC] = 1;
}
3.3 单条指令反汇编
cpp
std::string disassembleLine(const Instruction& inst) {
if (inst.op == BinIns::INVALID) {
return "; INVALID INSTRUCTION";
}
auto it = binToAss.find(inst.op);
if (it == binToAss.end()) {
return "; UNKNOWN INSTRUCTION";
}
std::string result = it->second;
if (insArgNum[inst.op] > 0) {
result += " " + std::to_string(inst.arg);
}
return result;
}
这个函数是反汇编器的核心,它:
- 检查指令的有效性
- 查找对应的汇编指令字符串
- 如果指令需要参数,添加参数值
- 返回完整的汇编指令字符串
3.4 程序反汇编
cpp
std::vector<std::string> disassembleProgram(const std::vector<Instruction>& program) {
std::vector<std::string> lines;
for (const auto& inst : program) {
lines.push_back(disassembleLine(inst));
}
return lines;
}
3.5 Main函数
cpp
#include "../include/assembler.hpp"
#include "../include/disassembler.hpp"
#include <fstream>
#include <vector>
#include <string>
// 从文件读取程序
std::vector<std::string> readFile(const std::string& filename) {
std::vector<std::string> lines;
std::ifstream file(filename);
if (!file) {
throw std::runtime_error("Cannot open file: " + filename);
}
std::string line;
while (std::getline(file, line)) {
lines.push_back(line);
}
return lines;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cout << "Usage: " << argv[0] << " <input.asm> <mode>" << std::endl;
std::cout << "Mode: asm - assemble, dis - disassemble" << std::endl;
return 1;
}
try {
// 读取源文件
auto sourceCode = readFile(argv[1]);
std::string mode = argv[2];
if (mode == "asm") {
// 显示源代码
std::cout << "Source code:" << std::endl;
for (const auto& line : sourceCode) {
std::cout << line << std::endl;
}
std::cout << std::endl;
// 创建汇编器并汇编代码
Assembler assembler;
auto program = assembler.assembleProgram(sourceCode);
// 显示汇编结果
std::cout << "Assembled code:" << std::endl;
assembler.outputBinary(program);
}
else if (mode == "dis") {
// 创建汇编器和反汇编器
Assembler assembler;
Disassembler disassembler;
// 先汇编代码(这里假设输入是汇编代码)
auto program = assembler.assembleProgram(sourceCode);
// 显示反汇编结果
std::cout << "Disassembled code:" << std::endl;
disassembler.outputAssembly(program);
}
else {
std::cout << "Invalid mode. Use 'asm' for assembly or 'dis' for disassembly." << std::endl;
return 1;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
4. 测试方式
4.1 命令行接口
程序支持两种模式:
bash
./assembler <input.asm> asm # 汇编模式
./assembler <input.asm> dis # 反汇编模式
5 测试结果
assembly
你的文件目录 .\build\assembler.exe test/test_disasm.asm dis
Disassembled code:
LDC 42
IN
ADD
OUT
LDC 10
MUL
OUT
LDC 2
DIV
SUB
OUT
HALT
5.1 指令集说明
指令 | 参数 | 功能描述 |
---|---|---|
LDC | 数值 | 将一个常数加载到栈中 |
IN | 无 | 从输入读取一个数字到栈中 |
ADD | 无 | 将栈顶的两个数字相加 |
OUT | 无 | 输出栈顶的结果 |
MUL | 无 | 将栈顶的两个数字相乘 |
DIV | 无 | 将栈顶的两个数字相除 |
SUB | 无 | 将栈顶的两个数字相减 |
HALT | 无 | 停止程序执行 |
5.1 指令序列
assembly
LDC 42 ; 加载常数42
IN ; 读取用户输入
ADD ; 相加
OUT ; 输出
LDC 10 ; 加载常数10
MUL ; 相乘
OUT ; 输出
LDC 2 ; 加载常数2
DIV ; 相除
SUB ; 相减
OUT ; 输出
HALT ; 结束
5.2 执行步骤
- 将常数42和用户输入的数字相加
- 输出第一次计算结果
- 将上一步结果乘以10
- 输出第二次计算结果
- 将上一步结果除以2
- 减去最初的输入值
- 输出最终结果
- 程序结束
5.3 示例执行
假设用户输入值为8,程序的执行过程如下:
步骤 | 操作 | 计算过程 | 结果 |
---|---|---|---|
1 | 加法 | 42 + 8 | 50 |
2 | 输出 | 输出50 | 50 |
3 | 乘法 | 50 * 10 | 500 |
4 | 输出 | 输出500 | 500 |
5 | 除法 | 500 / 2 | 250 |
6 | 减法 | 250 - 8 | 242 |
7 | 输出 | 输出242 | 242 |
8 | 结束 | 程序终止 | - |
5.4 栈操作说明
每个操作都会影响栈的状态:
LDC
: 将数值压入栈顶IN
: 将输入值压入栈顶ADD/MUL/DIV/SUB
: 弹出栈顶两个值,计算后将结果压回栈顶OUT
: 输出栈顶值(但不弹出)HALT
: 终止程序执行