编译器构造:从零手写汇编与反汇编程序(一)

前言:

这个秋季,三碗饭会开始更新自己在秋季课程的笔记,主要包括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)

参考:

https://www.cnblogs.com/unixfy/p/3357784.html

https://blog.csdn.net/yuqian123455/category_8798607.html

相关推荐
Just_Paranoid3 小时前
【WorkManager】无法在 Direct Boot 模式下初始化
android·jetpack·usermanager·workmanager·directboot
前端小超超3 小时前
如何配置capacitor 打包的安卓app固定竖屏展示?
android·前端·gitee
顾林海3 小时前
探秘Android JVM TI:虚拟机背后的"隐形管家"
android·面试·性能优化
刘大国4 小时前
<android>反编译魔改安卓系统应用并替换
android
恋猫de小郭5 小时前
Flutter Riverpod 3.0 发布,大规模重构下的全新状态管理框架
android·前端·flutter
纤瘦的鲸鱼5 小时前
MySQL慢查询
android·adb
郭庆汝5 小时前
模型部署:(三)安卓端部署Yolov8-v8.2.99目标检测项目全流程记录
android·yolo·目标检测·yolov8
fatiaozhang95275 小时前
中国移动云电脑一体机-创维LB2004_瑞芯微RK3566_2G+32G_开启ADB ROOT安卓固件-方法3
android·xml·adb·电脑·电视盒子·刷机固件
JCBP_6 小时前
QT(3)
开发语言·汇编·c++·qt·算法