如何仅用Windows内置工具生成输出Hello World的.exe并自制简易编译器
针对你的简易编译器开发问题的解决方案
一、将词法分析结果转换为机器码
你的核心需求是把print("Hello world!")这类高级指令转成x86_64机器码,且不依赖第三方汇编器,以下是具体步骤:
1. 映射高级指令到Win32 API调用
print本质是向控制台输出字符串,对应Win32的WriteConsoleA API,需要先通过GetStdHandle获取标准输出句柄,所以指令序列大致是:
1. 获取标准输出句柄:GetStdHandle(STD_OUTPUT_HANDLE) 2. 调用WriteConsoleA输出字符串 3. 退出程序
2. 手动编码x86_64机器码
x86_64指令由操作码、ModR/M字节、立即数等组成,需参考Intel x86_64指令集手册编码。比如:
mov rcx, 0xFFFFFFF5(设置GetStdHandle的参数,STD_OUTPUT_HANDLE的值)对应的机器码是48 C7 C9 F5 FF FF FFcall qword ptr [rip+0x0](调用导入表中的GetStdHandle)对应的机器码是FF 15 00 00 00 00
3. 处理动态链接(关键)
Windows API地址会随系统版本/ASLR随机化变化,不能硬编码地址,必须通过PE文件的导入表让系统加载时自动解析API地址:
- 定义导入描述符指向
kernel32.dll - 写入导入名称表(INT)和导入地址表(IAT),分别存储API名称和系统填充的实际地址
二、生成可执行文件并运行机器码
Windows仅识别PE(Portable Executable)格式的可执行文件,你需要用C++手动构造PE结构并写入磁盘:
1. 构造PE文件结构
PE文件包含以下核心部分,你需要用二进制字节数组逐个构建:
- DOS头:以
MZ开头,指定PE头的偏移位置 - PE头:标记文件为x86_64架构,指定节数量等信息
- 可选头:定义入口点、镜像基址、节对齐方式等
- 节表:定义代码段(
.text)、数据段(.data)的位置和属性 - 节数据:存放机器码、字符串、导入表等实际内容
2. 用C++生成PE文件示例
以下是极简的C++代码,直接生成可打印Hello world!的PE文件:
#include <fstream> #include <vector> int main() { std::vector<unsigned char> pe_data; // 1. DOS头 pe_data.insert(pe_data.end(), { 0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); // 2. PE头+可选头(简化版) pe_data.insert(pe_data.end(), { 0x50, 0x45, 0x00, 0x00, 0x64, 0x86, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x02, 0x20, 0x0B, 0x02, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); // 3. 节表 pe_data.insert(pe_data.end(), { '.', 't', 'e', 'x', 't', 0, 0, 0, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, '.', 'd', 'a', 't', 'a', 0, 0, 0, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00 }); // 4. .text节机器码 pe_data.insert(pe_data.end(), { 0x48, 0xC7, 0xC9, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x15, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xC1, 0x48, 0xC7, 0xC2, 0x30, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC0, 0x0D, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x00, 0xFF, 0x15, 0x08, 0x00, 0x00, 0x00, 0x48, 0x31, 0xC0, 0xC3 }); while (pe_data.size() < 0xA0 + 0x20) pe_data.push_back(0x90); // 5. .data节数据(字符串+导入表) pe_data.insert(pe_data.end(), { 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', 0, 0, 0, 0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', 0, 0, 0, 0, 0x00, 0x00, 'G', 'e', 't', 'S', 't', 'd', 'H', 'a', 'n', 'd', 'l', 'e', 0, 0x00, 0x00, 'W', 'r', 'i', 't', 'e', 'C', 'o', 'n', 's', 'o', 'l', 'e', 'A', 0 }); // 写入exe文件 std::ofstream out("hello.exe", std::ios::binary); out.write(reinterpret_cast<const char*>(pe_data.data()), pe_data.size()); out.close(); return 0; }
3. 编译并运行
- 用Windows自带的
cl.exe(需打开Developer Command Prompt)编译上述代码:cl /EHsc pe_generator.cpp - 运行生成的
pe_generator.exe,会输出hello.exe - 直接双击或在cmd中运行
hello.exe即可看到输出
4. 调试验证
- 用Windows自带的
dumpbin.exe检查PE结构:dumpbin /headers hello.exe - 若程序崩溃,可通过事件查看器的“Windows日志->应用程序”查看错误信息
内容的提问来源于stack exchange,提问作者GoogleCoder
相关产品推荐
相关产品推荐

