如何减少汇编可执行文件的冗余代码?

5
我有一份可以在Gas, NASMYASM上运行的多平台Hello World代码,并且我希望将它们对应的可执行文件从76KB缩小到更合理的大小,因为一个基本的Hello World C程序会导致80KB的可执行文件,而汇编应该要小得多。我认为大部分可执行文件都是由于链接器选项中的垃圾填充而产生的。
LIBS=c:/strawberry/c/i686-w64-mingw32/lib/crt2.o -Lc:/strawberry/c/i686-w64-mingw32/lib -lmingw32 -lmingwex -lmsvcrt

ld ld -o $(EXECUTABLE) hello.o $(LIBS)

hello.exe
Hello World!

代码:

.data

msg: .ascii "Hello World!\0"

.text

.global _main

_main:

pushl $msg
call _puts

leave
movl $0, %eax
ret

如果我在LIBS中删除任何选项,则链接过程将失败,或者生成的可执行文件在运行时会引发Windows错误。因此,逻辑上应该用更简单的东西(如sys_write)替换puts调用,但我不知道如何在多平台上实现这一点。在线文档很少,说要使用int 0x80来调用内核,但这仅在Linux中有效,而在Windows中无效,我希望我的汇编代码是多平台的。

你可以使用条件汇编,例如在NASM中,这样就可以在Linux中使用int 80h,而对于Windows,则可以使用Windows API调用(我不知道Windows API)。如果要创建真正小的ELF可执行文件,一个有关在Linux上创建非常小的ELF可执行文件的旋风教程可能会很有趣(但在多平台汇编编程方面可能帮助不大)。 - nrz
在C语言中,一个Hello World程序通常需要80K的空间,这是由于C运行时库而不是代码大小导致的。如果您不链接CRT,则您的EXE应该只有5-10K的大小。 - BitBank
@BitBank,谢谢!你能帮我重写代码,使其不依赖于C运行时吗? - mcandre
@nrz,Teensy是正确的想法,但你说得对,这还不够。使用ld ... -nostdlib会导致可执行文件与之前完全相同的文件大小。而使用ld ... -nostartfiles会导致在运行hello.exe时出现奇怪的错误:process_begin: CreateProcess(NULL, hello.exe, ...) failed. - mcandre
5个回答

2
您的程序膨胀主要来自于C运行时库。在Windows中,如果您编写自己的“微小”CRT,则一个简单的hello world程序可以小于5K。这里有一个项目链接,解释了如何将您的EXE缩小到最小可能的大小的所有细节: http://www.codeproject.com/Articles/15156/Tiny-C-Runtime-Library

1

对于Windows系统,您可以调用本地的Win32 API函数,例如GetStdHandle()WriteFile(),以直接写入stdout。

对于类Unix系统,您可以使用文件描述符1调用write()系统调用来写入stdout。

具体如何执行这些操作取决于您使用的汇编器和操作系统。


你能否提供一个代码示例,可以使用某些汇编程序进行组装,并在Unix和Windows中生成可工作的可执行文件? - mcandre
根据定义,你不能编写“跨平台”的汇编代码,至少如果你想以任何方式与操作系统本身交互的话。正如你所看到的,这些示例明显非常不同。 - Greg Hewgill
HLA。LLVM。我在帖子开头列出的NASM和YASM代码都是多平台的。这个人甚至编写了一个汇编语言的GUI应用程序,可以在Windows和Linux上运行。http://www.dreamincode.net/forums/topic/292403-nasm-cross-os-app-for-linuxwindows-using-gtk/ - mcandre
@Gunner,为什么在调用C库函数后你必须要调整esp - mcandre
@Gunner,你说当你构建此代码时会得到2048字节。你使用的是哪种开发工具链?你使用了哪些汇编器、链接器和编译器? - mcandre
显示剩余8条评论

0

由于几乎所有的C函数都使用CDECL调用约定,即调用者调整堆栈而不是被调用者(函数)。

如果您现在不学会如何正确地做事情,就会遇到麻烦,需要更加努力地查找错误。

尝试这个:

    push    szLF
    push    esp
    push    fmtint2
    call    printf
    add     esp, 4 * 3

    push msg
    call puts 

    push    szLF
    push    esp
    push    fmtint2
    call    printf
    add     esp, 4 * 3

运行它并注意您调用puts之前和之后的数字。它们不同,对吗?嗯,它们应该是相同的。现在添加:

    add     esp, 4

在调用puts之后再次运行它。现在数字是相同的吗?这意味着您有一个平衡的堆栈指针,并且该函数使用CDECL调用约定。


0

你应该能够动态链接到C运行时库,而不是静态地包含它。我不知道如何在Linux中实现,但在Windows中,你可以使用msvcrt.dll


谢谢,这会减小程序的大小,但我更愿意在可执行文件中包含所有必要的组件,当然除了那些默认安装在操作系统中的部分,比如内置的Windows DLL。 - mcandre
自Windows 2000以来,msvcrt.dll已经内置于Windows中。 - Jens Björnhager
你能帮我重写Makefile中的链接命令,以便动态地完成这个任务吗? - mcandre
抱歉,我不熟悉链接器,但如果您删除库引用会发生什么? - Jens Björnhager
如果我移除 -L... 标记,程序仍然可以构建,但是生成的可执行文件在运行时会在 Windows 上弹出“hello.exe 已停止工作”的窗口。移除任何 -lXYZ 标记都会由于缺少某些 C lib 依赖项而导致链接器错误。 - mcandre

0
汇编膨胀很可能来自于C库依赖,特别是对于puts。重构代码以打印Hello World而不使用C调用很可能需要特定于操作系统的汇编代码,因为Unix标准涉及调用内核的中断,而Windows则有其自己类似VB的API来处理这些任务。
我确实找到了一个解决方案,可以创建小型可执行文件,同时仍然保持平台无关性。通常,C预处理器指令可以解决问题,但我不确定哪些汇编语言甚至具有预处理器语法。但是,通过使用受控的、包含的汇编代码文件,可以实现类似的效果。一组包装器代码文件可以处理特定于操作系统的汇编代码,而一个包含的汇编文件则完成其余工作。一个简单的Makefile可以运行相应的构建控制台命令,在所需的平台上引用相应的包装器代码。
例如,我能够快速构建FASM代码,以这种方式工作。(虽然我还没有告诉它实际上如何绕过puts,使用更少的膨胀代码。)无论如何,这是进步。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接