编译器构造:从零手写汇编与反汇编程序(一)
前言:
这个秋季,三碗饭会开始更新自己在秋季课程的笔记,主要包括Introduction to ML,可能还会有Complier Construction 以及 Computer Graphics.
本文将介绍Complier Construction中关于模拟器,汇编以及反汇编的知识,在下一章我们会从0写一个简易模拟器,以及汇编,反汇编程序。这门课如果没有兴趣,会非常无聊非常无聊,当然我一开始也觉得挺无聊的,毕竟搞AI Agent比这个有意思多,但我还是会尽量以简单的描述(避免复杂的解释),以及有意思的例子去讲这些知识。
1: 概要
了解下之后我们会完成的三个内容。
汇编器:
将人类可读的汇编代码(.uma)转换为机器码(.um)
相当于"翻译官",把人类语言翻译成机器语言
反汇编器
将机器码(.um)转换回汇编代码(.uma)
相当于"解码器",把机器语言翻译回人类语言
主要用于调试和验证
模拟器:
执行机器码(.um)
相当于"虚拟CPU",实际运行程序
负责程序的实际执行
2. 指令类型定义
enum class BinIns {HALT, // 停止程序IN, // 输入OUT, // 输出ADD, // 加法SUB, // 减法MUL, // 乘法DIV, // 除法LDC, // 加载常数INVALID // 无效指令
};
- 使用枚举类定义所有支持的指令类型
- 每个指令都有一个对应的数值(从0开始)
- enum class提供类型安全,避免与其他枚举混淆
3. 指令结构体
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. 汇编器类
class Assembler {
private:// 存储名称std::vector<std::string> assIns;// 记录每个指令需要的参数个数std::map<BinIns, int> insArgNum;// 指令名称到操作码的映射std::map<std::string, BinIns> assToBin;
4.1 初始化函数
void initInstructions() {assIns = {"HALT", "IN", "OUT", "ADD", "SUB", "MUL", "DIV", "LDC"};
}
- 初始化所有支持的指令名称
- 这些是用户在汇编代码中使用的指令
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表示需要一个参数
void initMapping() {assToBin["HALT"] = BinIns::HALT;assToBin["IN"] = BinIns::IN;assToBin["OUT"] = BinIns::OUT;// 其他指令映射
}
- 建立指令文本到操作码的映射
- 用于将汇编代码转换为机器指令
4.2 汇编单行代码
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 汇编整个程序
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 输出二进制格式
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 辅助函数
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. 使用示例
// 创建汇编器
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}]
完整代码块
#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
#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
; 打印 ; 测试程序
; 计算 (3 + 4) * 2LDC 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
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 = 75. `OUT`:- 操作码:2(OUT)- 动作:输出当前值(7)6. `LDC 2`:- 操作码:7(LDC)- 参数:2- 动作:将2加载到寄存器7. `MUL`:- 操作码:5(MUL)- 动作:7 * 2 = 148. `OUT`:- 操作码:2(OUT)- 动作:输出当前值(14)9. `HALT`:- 操作码:0(HALT)- 动作:停止程序
如果执行这个程序,会依次输出:
程序运行结果
如果执行这个程序,会依次输出:
3 (第一个OUT的结果)
7 (第二个OUT的结果,3+4)
14 (第三个OUT的结果,7*2)
参考:
https://www.cnblogs.com/unixfy/p/3357784.html
https://blog.csdn.net/yuqian123455/category_8798607.html