I've spent the last couple of hours trying to link a simple x86 assembly program without any of the CRT initialization code using MinGW. I want the executable to only contain the _main
method listed below and a single import of the ExitProcess
kernel function.
Opening the various generated files in a disassembler reveals that _main
is indeed the entry point and the import table contains an import of ExitProcess
(without the name decorations) from KERNEL32.dll
, however Windows won't load the executable, claiming that "%1 is not a valid Win32 application."
One curious thing I noticed is that when I open the file in Resource Hacker, add a small embedded resource and save, the resulting file executes without issues. This leads me to believe that it's not an issue with the linking step itself, but rather with the file header, since that should be the only part changing in the process. Deleting the embedded resource and saving again causes Resource Hacker to crash and restores the executable to its previous broken state.
; test.asm
global _main
extern _ExitProcess@4
section .text
_main:
push 0
call _ExitProcess@4
nasm -fwin32 test.asm & gcc -s test.obj
nasm -fwin32 test.asm & gcc -s -nostartfiles test.obj
nasm -fwin32 test.asm & gcc -s -nostdlib test.obj
nasm -fwin32 test.asm & ld test.obj -e _main
All of the commands execute successfully with no console output. I've also tried adding various combinations of -lkernel32
and -Wl,-e_main
to the gcc
calls, none of which seemed to have any effect.
C:\Projects\asm>systeminfo | findstr /B /C:"OS Name" /C:"OS Version"
OS Name: Microsoft Windows 10 Home
OS Version: 10.0.19041 N/A Build 19041
C:\Projects\asm>nasm --version
NASM version 2.15.05 compiled on Aug 28 2020
C:\Projects\asm>gcc --version
gcc (MinGW.org GCC Build-2) 9.2.0
C:\Projects\asm>ld --version
GNU ld (GNU Binutils) 2.32
As requested, I'm going to post this as an answer, but with a big caveat:
While this fixes the problem as posted, I don't really understand why. I'm not aware of (and can't find) any Windows PE requirement that you must have an rdata section, which suggests this is fixing things "by accident."
Also, while I'm using
rdata
here,pdata
also works (butdata
does not).
With that in mind, you can solve the problem by adding an rdata section. So changing the code as follows solves the problem, allowing the executable to run as expected:
; test.asm
section .rdata
db 9
section .text
extern _ExitProcess@4
global _main
_main:
push 0
call _ExitProcess@4