Search code examples
assemblygcclinkermingwld

MinGW: Linking with -nostdlib produces invalid executable


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.

The code

; test.asm
global _main
extern _ExitProcess@4
section .text
_main:
    push    0
    call    _ExitProcess@4

Failed attempts

  • nasm -fwin32 test.asm & gcc -s test.obj
    Creates a functioning executable, but also contains all of the CRT startup code I'm trying to avoid.
  • nasm -fwin32 test.asm & gcc -s -nostartfiles test.obj
    Creates an invalid executable.
  • nasm -fwin32 test.asm & gcc -s -nostdlib test.obj
    Creates an invalid executable.
  • nasm -fwin32 test.asm & ld test.obj -e _main
    Creates an invalid executable.

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.

Environment

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

Solution

  • 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 (but data 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