Reputation: 87391
I have the following C program:
#include <windows.h>
void __cdecl mainCRTStartup() {
DWORD bw;
HANDLE hfile = GetStdHandle(STD_OUTPUT_HANDLE);
WriteFile(hfile, "Hello, World!\r\n", 15, &bw, 0);
ExitProcess(0); /* Needed for successful (0) exit. */
}
I compile it with GCC 4.8.2, using the following command line:
i686-w64-mingw32-gcc -s -Os -fno-ident -fno-stack-protector -fomit-frame-pointer \
-fno-unwind-tables -fno-asynchronous-unwind-tables -falign-functions=1 \
-mpreferred-stack-boundary=2 -falign-jumps=1 -falign-loops=1 -mconsole \
-nostdlib -nodefaultlibs -nostartfiles -o h.exe h.c -lkernel32
The generated .exe file is 2048 bytes long. How can I make it smaller using MinGW, preferably at most 1024 bytes, or (even better) at most 512 bytes?
I'd prefer a solution without writing assembly code, but I'm also interested in assembly solutions.
I've tried -Wl,-N
to decrease the number of sections (segments), but that caused a segfault when running the .exe in Wine.
This article suggests that 480 bytes is possible. It uses the following settings:
#pragma comment(linker, "/FILEALIGN:16")
#pragma comment(linker, "/ALIGN:16")// Merge sections
#pragma comment(linker, "/MERGE:.rdata=.data")
#pragma comment(linker, "/MERGE:.text=.data")
#pragma comment(linker, "/MERGE:.reloc=.data")
#pragma optimize("gsy", on)
Unfortunately these #pragma
s don't work with MinGW GCC. Are there equivalents?
In here I was able to find the GCC flags
-Wl,--section-alignment,16,--file-alignment,16
which bring down the .exe size to 752 bytes. The .exe seems to work in Wine.
By modifying the linker script I was able to merge .data
and .rdata
, and go down to 736 bytes. I'm using these GCC flags in addition to those above: -Wl,--section-alignment,16,--file-alignment,16,-T,tinygccpe.scr
.
I'm still looking for the MinGW equivalent of /MERGE
.
This question is similar, but it doesn't attempt to go below 9000 bytes.
I'm also looking for a strip
tool (the strip
command in MinGW doesn't reduce the .exe size any further) which can remove the DOS stub (which is between offsets 0x40 and 0x80, it contains This program cannot be run in DOS mode.
, we could save 64 bytes). This code can remove it, but it also breaks all the absolute offsets in the .exe. Unfortunately the linker ld
in MinGW isn't able to remove the DOS stub, it's hardcoded in the file bfd/peXXigen.c
, just above the NT_SIGNATURE
.
Is it possible to strip more headers from the .exe, i.e. headers which the loader doesn't use?
Upvotes: 9
Views: 4776
Reputation: 87391
This question has extensive online literature, starting from about 1995.
Each version of 32-bit and 64-bit Windows has a different set of rules on what header values they accept in PE .exe executables. For example, Windows 7 accepts .exe files with 0 sections, section alignment 4, file alignment 4, and other versions of Windows (e.g. Windows XP and the most recent Windows 10 in 2020) reject these files.
It's possible to create working .exe files smaller than 2048 bytes though. Example:
Here is why it is unlikely that a portable Win32 PE .exe hello-world shorter than 584 bytes will be released:
The .exe files smaller than 268 bytes work only on Windows versions earlier than Windows XP, and they don't work on 64-bit Windows systems.
Related literature:
Upvotes: 5