为什么我用 GCC 可以编译的最小 exe 是 67KB?

问题描述 投票:0回答:2

我想制作一个非常小的编译后的exe,它是用C编写的。但我能得到的最小的是67KB。我正在使用 MinGW。 我尝试不使用任何头文件,并且编译时没有错误:

//no header
void main() {
 write(1, "Hello world!", 12);
}

如果我构建并运行它,GCC 不会显示任何错误,但它也是 67KB。

c gcc mingw compiler-optimization
2个回答
7
投票

我刚刚在 x86_64 Linux 中尝试过这个,在这个级别上它可能与 MinGW 没有太大区别,尽管你永远不知道。

基本上,问题是,即使没有从 C 库中提取任何内容(除非引用它),CRT“startfiles”确实引用了一小部分内容,而这些内容又引用了其他一些内容,并且“Hello world”最终成为看起来很糟糕。这不是一个值得解决的问题,因为所有实际程序无论如何都会引用这些核心函数。 启动文件的源代码是可用的,而且非常小,并且编译器允许您覆盖标准文件(如果您选择),因此优化它们并不是一件大事。它们是用汇编代码编写的,但您可以通过简单地删除行来删除大部分无关的垃圾。

但是,有一个技巧可以将启动文件完全排除在外:

#include <unistd.h> void _start (void) { write(1,"Hello world!", 12); _exit(0); }

编译:
gcc -nostartfiles t.c -s -static


它有效(偶然,见下文),并给我一个

1792 字节的文件大小

为了比较,你的原始代码使用相同的编译器给出了 738624 字节,当我删除

-static

时,它下降到 4400 字节,但这就是作弊! (我的代码实际上在没有

-static
的情况下变得更大,因为动态链接器元数据超过了
write
_exit
的代码)。

“偶然”部分是程序现在“没有堆栈指针”初始化。同样,对于所有其他全局状态,启动文件通常会处理。碰巧,在 x86_64 Linux 上,这不是一个致命问题(只是不要在生产中这样做,对吧?)但是,当我使用

-m32

尝试时,我在 write 中遇到了分段错误。 可以通过添加自己的初始化来解决该问题,但代码将不再是

as
可移植的(它还不是绝对可移植的)。或者,直接调用 write 系统调用。
    

我知道这是老问题,但我也有同样的问题。大尺寸也是默认启用 RELRO 和默认最大页面大小 64K 的结果。 使用

gcc -Wl,-z,max-page-size=0x1000 -s -Wl,-z,norelro main.c && sstrip -z a.out

3
投票

使用

gcc -nostartfiles start.c -Wl,-z,max-page-size=0x1000,-z,norelro && sstrip -z a.out

编译的空 _start 函数文件会产生 164 字节的二进制文件。

经过一些实验,我制作了相同的程序,但更小:

#include <unistd.h> #include <sys/syscall.h> static const char str[] = "Hello world!"; void _start(){ syscall(SYS_write, 1, str, 12); syscall(SYS_exit, 0); }

使用 

gcc -nostartfiles start.c -Wl,-z,max-page-size=0x1000,-z,norelro -static -Os && sstrip -z a.out

生成的二进制文件在 ARM 上为 353 字节。添加

-mthumb
后变为349字节。

如果进行完整汇编,那么您将获得 144 字节的可执行文件。相同的命令行,但使用 .S 文件。

#include <sys/syscall.h>

.global _start
_start:
mov r7, $SYS_write                                 mov r0, $1
add r1, pc, $(hw - . - 8)
mov r2, $(end - hw)
svc #0
mov r7, $SYS_exit
svc #0

hw:
.ascii "Hello world!\n"
end:
.align 4
    

© www.soinside.com 2019 - 2024. All rights reserved.