一、安装nasm并编写代码
安装nasm添加PATH环境变量
在终端输入nasm -v看到版本即为安装成功

依旧从简单的输出开始,编写如下代码:
asm
global main //定义全局符号
extern puts //外部函数
section .text //text节区
main: //符号main的开始,之后程序链接时指定从这开始,OEP将会指向这
sub rsp, 28h ; 影子空间
mov rcx, message ; rcx传第一个参数
call puts ; 调用puts
add rsp, 28h ; 删除影子空间
ret
message: ;变量符号
db 'Hello', 0 ;\0结尾的字符串
对于一个可执行文件生成过程:
高级语言-->c语言-->预处理define等宏-->编译为汇编代码-->汇编为obj文件-->链接为可执行文件如exe等
使用命令进行汇编,其中O1表示优化,为可选参数
asm
nasm -f win64 hello.asm -o hello.obj -O1
此时生成的是.obj文件



二、使用gcc.exe或link.exe链接
1.使用gcc链接为例
可以使用电脑上的gcc.exe链接或link.exe链接(在安装一些软件时会附带安装一些gcc,如编译工具和编辑器等)。但是链接后都太大
这里以gcc为例
- mconsole
指定 Windows 子系统为控制台程序。这个选项告诉链接器生成一个控制台应用程序(入口函数为 main 或 wmain,启动时会自动分配一个控制台窗口)。与之相对的是 -mwindows(GUI 程序,没有控制台窗口)。
asm
gcc hello.obj -o hello.exe -mconsole -lmsvcrt -v
- lmsvcrt
链接时去查找并链接 msvcrt 库。具体来说:
-l 选项表示链接一个库,库文件的实际名称在 Unix 风格下是 lib.a 或 .lib。
msvcrt 对应 Microsoft Visual C++ 运行时库的导入库(例如 MinGW 提供的 libmsvcrt.a),该库用于动态链接到系统自带的 msvcrt.dll(Windows 上最基础的 C 运行时)。
加上这个选项后,程序运行时会使用 MSVC 的 C 运行时(如 printf、malloc 等函数的实现),这对于由 MSVC 编译出的 .obj 文件来说是自然且必要的,否则可能出现符号未定义或运行时冲突。
不显式指定 -lmsvcrt,大多数情况下 GCC(MinGW)仍会自动链接,通常不会出错。但这取决于你具体的 MinGW 发行版和配置
可以使用-v参数查看详细信息,这里我用的是strawberry_per的gcc

大至约64kB,很大一部分原因是因为静态链接了C函数库
查看发现主要是许多节区大小占据大量空间,一方面节区数量多,另一方面文件对齐导致PE文件臃肿

2.链接后大体积原因
为什么会这样?
- 链接器默认引入了一大堆库(包括静态库)

即便有"动态链接 C 库"(-lmsvcrt),这些库里有几个是默认静态链接的:
libgcc.a、libgcc_eh.a ------ GCC 的低级运行时(除法和模运算、异常展开、栈回溯等)
- libmingw32.a: MinGW 提供的启动辅助代码
- libmingwex.a : MinGW 扩展数学函数等
- libmoldname.a : 一些符号别名
这些静态库的体积会被直接嵌入你的 hello.exe
- 工具链实际上是 UCRT 版本,却链接了 msvcrt

这是一个 UCRT 版本的 MinGW(名字里有 ucrt),默认运行时应该是 UCRT(libucrt.a),但链接命令里硬编码的是 -lmsvcrt。
可能导致两种 CRT 的支持代码可能都被部分引入,增大体积 - 调试信息------最大的隐形杀手
hello.obj 由 MSVC 生成,很有可能带有调试信息节(.debugS、.debugS、.debugS、.debugT)。GCC 链接时没有加 -s 或 -Wl,--strip-all,这些调试节会被完整拷贝到 hello.exe 里,让体积成倍增长 - 没有优化体积和去除无用节
链接命令里没有:
- -Os(优化体积)
- -s(去除符号表)
- -Wl,--gc-sections(垃圾收集未用到的节)
- -fno-ident(去掉 GCC 版本注释节)
- -fno-exceptions(如果是 C++,去异常表)
| 体积来源 | 大约贡献 | 可优化? |
|---|---|---|
| 静态链接的 libgcc / libmingwex | 20~100 KB | 可以强制动态链接 libgcc(-shared-libgcc),但需附带 DLL |
| CRT 启动文件 (crt2.o 等) | 3~10 KB | 不可避免 |
| MSVC 的 .obj 调试信息 | 可能 >100 KB | 用 strip hello.exe 或编译时 gcc -s 去除 |
| 重复的库 / 多余符号 | 1~5 KB | 加 -Wl,--gc-sections 配合 -ffunction-sections 编译源文件(对 .obj 无效,因为是预编译的) |
| PE 对齐与元数据 | 1~2 KB | 基本不可减 |
3.去除符号表后的效果
加上-s去除符号表后仅剩余22kB
以下为符号表去除前后的区别


三、使用golink链接
GoLink是一款专注于提高开发效率和系统可维护性的链接管理工具
为实现极小的大小,这里下载一个golink.exe ,同样添加环境变量
bash
golink /entry:main /console kernel32.dll msvcrt.dll hello.obj
其中/entry指定入口点,/console指定控制台应用,之后两dll指定不要静态链接kernel32.dll和msvcrt.dll( Microsoft Visual C++ 运行时库)
此时即可运行程序,仅1.5kB,连文件中的一页4k都不到

调试到入口点,可以看到仅几条机器指令

此时可以看到只有一个start符号的函数

节区数据

最后效果
