前言:
这个秋季,三碗饭会开始更新自己在秋季课程的笔记,主要包括Introduction to ML,可能还会有Complier Construction 以及 Computer Graphics.
本文将介绍Complier Construction中关于模拟器,汇编以及反汇编的知识,在下一章我们会从0写一个简易模拟器,以及汇编,反汇编程序。这门课如果没有兴趣,会非常无聊非常无聊,当然我一开始也觉得挺无聊的,毕竟搞AI Agent比这个有意思多,但我还是会尽量以简单的描述(避免复杂的解释),以及有意思的例子去讲这些知识。
1: 概要
了解下之后我们会完成的三个内容。
汇编器:
将人类可读的汇编代码(.uma)转换为机器码(.um)
相当于"翻译官",把人类语言翻译成机器语言
反汇编器
将机器码(.um)转换回汇编代码(.uma)
相当于"解码器",把机器语言翻译回人类语言
主要用于调试和验证
模拟器:
执行机器码(.um)
相当于"虚拟CPU",实际运行程序
负责程序的实际执行
2. 指令类型定义
cpp
enum class BinIns {
HALT, // 停止程序
IN, // 输入
OUT, // 输出
ADD, // 加法
SUB, // 减法
MUL, // 乘法
DIV, // 除法
LDC, // 加载常数
INVALID // 无效指令
};
- 使用枚举类定义所有支持的指令类型
- 每个指令都有一个对应的数值(从0开始)
- enum class提供类型安全,避免与其他枚举混淆
3. 指令结构体
cpp
struct Instruction {
BinIns op; // 操作码:表示指令类型
int arg; // 参数:存储指令的参数值
// 构造函数:创建新指令时使用
Instruction(BinIns op_ = BinIns::HALT, int arg_ = 0)
: op(op_), arg(arg_) {}
};
- op:存储指令类型(如ADD, SUB等)
- arg:存储指令参数(如LDC 3中的3)
- 默认构造函数创建HALT指令
4. 汇编器类
cpp
class Assembler {
private:
// 存储名称
std::vector<std::string> assIns;
// 记录每个指令需要的参数个数
std::map<BinIns, int> insArgNum;
// 指令名称到操作码的映射
std::map<std::string, BinIns> assToBin;
4.1 初始化函数
cpp
void initInstructions() {
assIns = {
"HALT", "IN", "OUT", "ADD", "SUB",
"MUL", "DIV", "LDC"
};
}
- 初始化所有支持的指令名称
- 这些是用户在汇编代码中使用的指令
cpp
void initArgNumbers() {
insArgNum[BinIns::HALT] = 0; // HALT不需要参数
insArgNum[BinIns::IN] = 0; // IN不需要参数
insArgNum[BinIns::OUT] = 0; // OUT不需要参数
insArgNum[BinIns::ADD] = 0; // ADD不需要参数
insArgNum[BinIns::SUB] = 0; // SUB不需要参数
insArgNum[BinIns::MUL] = 0; // MUL不需要参数
insArgNum[BinIns::DIV] = 0; // DIV不需要参数
insArgNum[BinIns::LDC] = 1; // LDC需要1个参数
}
- 定义每个指令需要的参数个数
- 0表示不需要参数,1表示需要一个参数
cpp
void initMapping() {
assToBin["HALT"] = BinIns::HALT;
assToBin["IN"] = BinIns::IN;
assToBin["OUT"] = BinIns::OUT;
// 其他指令映射
}
- 建立指令文本到操作码的映射
- 用于将汇编代码转换为机器指令
4.2 汇编单行代码
cpp
Instruction assembleLine(const std::string& line) {
std::istringstream iss(line); // 创建字符串流
std::string op; // 存储指令名
iss >> op; // 读取指令名
// 转换为大写(这样halt和HALT都能识别)
for (char& c : op) {
c = toupper(c);
}
// 跳过空行和注释
if (op.empty() || op[0] == ';') {
return Instruction(BinIns::INVALID);
}
// 查找指令是否存在
auto it = assToBin.find(op);
if (it == assToBin.end()) {
std::cerr << "Unknown instruction: " << op << std::endl;
return Instruction(BinIns::INVALID);
}
BinIns binOp = it->second; // 获取操作码
int arg = 0; // 默认参数值
// 如果指令需要参数,读取参数
if (insArgNum[binOp] > 0) {
iss >> arg;
}
return Instruction(binOp, arg); // 创建并返回指令
}
4.3 汇编整个程序
cpp
std::vector<Instruction> assembleProgram(const std::vector<std::string>& lines) {
std::vector<Instruction> program; // 存储生成的指令
for (const auto& line : lines) { // 处理每一行
if (!line.empty() && line[0] != ';') { // 跳过空行和注释
auto inst = assembleLine(line); // 汇编这一行
if (inst.op != BinIns::INVALID) { // 如果是有效指令
program.push_back(inst); // 添加到程序中
}
}
}
return program; // 返回完整的程序
}
4.4 输出二进制格式
cpp
void outputBinary(const std::vector<Instruction>& program) {
for (const auto& inst : program) {
// 输出操作码
std::cout << "Op: " << static_cast<int>(inst.op)
<< " (" << getBinString(static_cast<int>(inst.op), 8) << ")";
// 如果指令有参数,输出参数
if (insArgNum[inst.op] > 0) {
std::cout << "\tArg: " << inst.arg
<< " (" << getBinString(inst.arg, 32) << ")";
}
std::cout << std::endl;
}
}
4.5 辅助函数
cpp
std::string getBinString(int n, int bits) {
std::string result(bits, '0'); // 创建指定长度的字符串,初始全为'0'
// 从右到左填充二进制位
for (int i = bits - 1; i >= 0 && n > 0; i--, n /= 2) {
result[i] = (n % 2) + '0'; // 转换为字符'0'或'1'
}
return result;
}
- 将数字转换为二进制字符串
- bits指定输出的位数
- 用于格式化输出
5. 使用示例
cpp
// 创建汇编器
Assembler assembler;
// 汇编一行代码
Instruction inst = assembler.assembleLine("LDC 42");
// 结果:操作码 = LDC (7), 参数 = 42
// 汇编一个程序
std::vector<std::string> program = {
"LDC 3",
"OUT",
"HALT"
};
auto binary = assembler.assembleProgram(program);
// 结果:[{LDC,3}, {OUT,0}, {HALT,0}]
完整代码块
c
#ifndef ASSEMBLER_HPP
#define ASSEMBLER_HPP
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <sstream>
// 指令类型枚举
enum class BinIns {
HALT, // 停止程序
IN, // 输入
OUT, // 输出
ADD, // 加法
SUB, // 减法
MUL, // 乘法
DIV, // 除法
LDC, // 加载常数
INVALID // 无效指令
};
// 指令结构
struct Instruction {
BinIns op; // 操作码
int arg; // 参数
Instruction(BinIns op_ = BinIns::HALT, int arg_ = 0)
: op(op_), arg(arg_) {}
};
class Assembler {
private:
// 指令集
std::vector<std::string> assIns;
// 指令参数个数表
std::map<BinIns, int> insArgNum;
// 指令映射表
std::map<std::string, BinIns> assToBin;
public:
Assembler() {
initInstructions();
initArgNumbers();
initMapping();
}
// 汇编一行代码
Instruction assembleLine(const std::string& line) {
std::istringstream iss(line);
std::string op;
iss >> op;
// 转换为大写
for (char& c : op) {
c = toupper(c);
}
// 跳过注释
if (op.empty() || op[0] == ';') {
return Instruction(BinIns::INVALID);
}
// 查找指令
auto it = assToBin.find(op);
if (it == assToBin.end()) {
std::cerr << "Unknown instruction: " << op << std::endl;
return Instruction(BinIns::INVALID);
}
BinIns binOp = it->second;
int arg = 0;
// 如果指令需要参数,读取参数
if (insArgNum[binOp] > 0) {
iss >> arg;
}
return Instruction(binOp, arg);
}
// 汇编整个程序
std::vector<Instruction> assembleProgram(const std::vector<std::string>& lines) {
std::vector<Instruction> program;
for (const auto& line : lines) {
if (!line.empty() && line[0] != ';') {
auto inst = assembleLine(line);
if (inst.op != BinIns::INVALID) {
program.push_back(inst);
}
}
}
return program;
}
// 输出二进制格式
void outputBinary(const std::vector<Instruction>& program) {
for (const auto& inst : program) {
std::cout << "Op: " << static_cast<int>(inst.op)
<< " (" << getBinString(static_cast<int>(inst.op), 8) << ")";
if (insArgNum[inst.op] > 0) {
std::cout << "\tArg: " << inst.arg
<< " (" << getBinString(inst.arg, 32) << ")";
}
std::cout << std::endl;
}
}
private:
// 初始化指令集
void initInstructions() {
assIns = {
"HALT", "IN", "OUT", "ADD", "SUB",
"MUL", "DIV", "LDC"
};
}
// 初始化参数个数
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;
}
// 初始化指令映射
void initMapping() {
assToBin["HALT"] = BinIns::HALT;
assToBin["IN"] = BinIns::IN;
assToBin["OUT"] = BinIns::OUT;
assToBin["ADD"] = BinIns::ADD;
assToBin["SUB"] = BinIns::SUB;
assToBin["MUL"] = BinIns::MUL;
assToBin["DIV"] = BinIns::DIV;
assToBin["LDC"] = BinIns::LDC;
}
// 获取二进制字符串表示
std::string getBinString(int n, int bits) {
std::string result(bits, '0');
for (int i = bits - 1; i >= 0 && n > 0; i--, n /= 2) {
result[i] = (n % 2) + '0';
}
return result;
}
};
#endif // ASSEMBLER_HPP
Main
c
#include "../include/assembler.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 != 2) {
std::cout << "Usage: " << argv[0] << " <input.asm>" << std::endl;
return 1;
}
try {
// 读取源文件
auto sourceCode = readFile(argv[1]);
// 显示源代码
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);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
测试文件
test
cpp
; 打印 ; 测试程序
; 计算 (3 + 4) * 2
LDC 3 ; 加载数字3
OUT ; 输出当前值(用于调试)
LDC 4 ; 加载数字4
ADD ; 3 + 4
OUT ; 输出当前值(用于调试)
LDC 2 ; 加载数字2
MUL ; (3 + 4) * 2
OUT ; 输出结果
HALT ; 停止程序
具体输出:
Op: 7 (00000111) Arg: 3 (00000000000000000000000000000011) ; LDC 3
Op: 2 (00000010) ; OUT
Op: 7 (00000111) Arg: 4 (00000000000000000000000000000100) ; LDC 4
Op: 3 (00000011) ; ADD
Op: 2 (00000010) ; OUT
Op: 7 (00000111) Arg: 2 (00000000000000000000000000000010) ; LDC 2
Op: 5 (00000101) ; MUL
Op: 2 (00000010) ; OUT
Op: 0 (00000000) ; HALT
cpp
1. `LDC 3`:
- 操作码:7(LDC)
- 参数:3
- 动作:将3加载到寄存器
2. `OUT`:
- 操作码:2(OUT)
- 动作:输出当前值(3)
3. `LDC 4`:
- 操作码:7(LDC)
- 参数:4
- 动作:将4加载到寄存器
4. `ADD`:
- 操作码:3(ADD)
- 动作:3 + 4 = 7
5. `OUT`:
- 操作码:2(OUT)
- 动作:输出当前值(7)
6. `LDC 2`:
- 操作码:7(LDC)
- 参数:2
- 动作:将2加载到寄存器
7. `MUL`:
- 操作码:5(MUL)
- 动作:7 * 2 = 14
8. `OUT`:
- 操作码:2(OUT)
- 动作:输出当前值(14)
9. `HALT`:
- 操作码:0(HALT)
- 动作:停止程序
如果执行这个程序,会依次输出:
程序运行结果
如果执行这个程序,会依次输出:
3 (第一个OUT的结果)
7 (第二个OUT的结果,3+4)
14 (第三个OUT的结果,7*2)
参考: