0

I'm trying to write x86-64 assembly to run on current windows systems (in my case good-old windows 11). I first tried a basic program based on this post:

; ----------------------------------------------------------------------------
; Hello_world.asm
;
; This is a Win64 console program that writes "Hello, World" on one line and
; then exits.  It needs to be linked with a C library.
; ----------------------------------------------------------------------------

    global  _main
    extern  printf

    section .text
_main:
    push    message
    call    printf
    add     esp, 4
    ret
message:
    db  'Hello, World', 10, 0

When I go to assemble and link I get the following error(s):

nasm -fwin64 Hello_world.asm

gcc Hello_world.obj

Error:

Hello_world.obj:Hello_world.asm:(.text+0x1): relocation truncated to fit: IMAGE_REL_AMD64_ADDR32 against `.text'
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../lib/libmingw32.a(lib64_libmingw32_a-crtexewin.o): in function `main':
C:/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexewin.c:70: undefined reference to `WinMain'
collect2.exe: error: ld returned 1 exit status

How do I fix this?

  • 1
    Windows programs really start with a `WinMain` function, not a `main` function. You need to tell the linker that you're building a console program which uses the `main` function. There are plenty of tutorials and documentation online of you search a little (just entering `gcc undefined reference to WinMain` in your favorite search engine should be enough to find them). – Some programmer dude Jun 14 '23 at 16:08
  • 4
    Note that your program is essentially 32-bit, so using it as 64-bit code is going to give other problems at runtime when you get it to link. – harold Jun 14 '23 at 16:12
  • @Some programmer dude Cool. That fixed one of the two errors. It is still throwing the error about the "relocation truncated to fit: IMAGE_REL_AMD64_ADDR32 against `.text." – Algorithm Ben Jun 14 '23 at 16:22
  • Is there a better call to assemble and link it? – Algorithm Ben Jun 14 '23 at 16:28
  • 2
    That's 32-bit code, build it with `nasm -fwin32` and link with `gcc -m32`. Or rewrite it to use the 64-bit calling convention and avoid 32-bit absolute addressing. (e.g. `lea rcx, [rel message]`. As in [32-bit absolute addresses no longer allowed in x86-64 Linux?](https://stackoverflow.com/q/43367427) - PIC techniques are the same, but build commands are different on Windows vs. Linux) – Peter Cordes Jun 14 '23 at 17:42
  • @Someprogrammerdude GCC can detect if you are using `WinMain` or `main`. The error that OP is getting is because he is decorating the symbol as `_main`, but targetting 64-bit which doesn't decorate names. – DarkAtom Jun 15 '23 at 16:51
  • @AlgorithmBen the `IMAGE_REL_AMD64_ADDR32` comes from the fact that you are pushing an absolute address on the stack, which you should never do in 64-bit code (only through a register or memory operand). There is no instruction in x86-64 which pushes a full 64-bit immediate value. So what happens is that the assembler silently ignores this and chooses a 32-bit sign extended immediate value (which is wrong). The linker can't deal with the 32-bit relocation in 64-bit mode, so it throws an error. – DarkAtom Jun 15 '23 at 16:56

1 Answers1

0

The code you've written is 32-bit, but you are assembling and linking it for 64-bit. I will explain how to create both a 32-bit and a 64-bit application.

Creating a 32-bit application on Windows

Your code is almost correct, but you need to decorate the printf too (make it _printf).

    global  _main
    extern  _printf

    section .text
_main:
    push    message
    call    _printf
    add     esp, 4
    ret
message:
    db  'Hello, World', 10, 0

Assembling this code is easy:

nasm -f win32 Hello_world.asm

Linking it is a bit tricky using the MinGW toolchain, because the default compiler is 64-bit only (if you use -m32 you will get dozens of undefined references).

Fortunately, they provide a separate set of 32-bit tools here. If you manage to install it correctly into the PATH variable, you should be able to use it normally, like this:

i686-w64-mingw32-gcc Hello_world.obj

Creating a 64-bit application on Windows

The calling convention is different in 64-bit mode. In Windows, the Microsoft x64 calling convention is used. The first 4 parameters to functions are passed in the RCX, RDX, R8 and R9 registers. This means that our message should go in RCX.

You also need to allocate 32 bytes of stack space (they are designed for compilers so that they spill the parameters to the stack, but they are always required to be present, regardless of whether they are used or not).

Finally, the ABI dictates that, when executing the CALL instruction, the stack needs to be aligned at 16 bytes. Therefore, we must allocate 8 more bytes for alignment. So 40 in total.

Also note that for 64-bit code, the names don't need to be decorated.

    global  main
    extern  printf

    section .text
main:
    sub     rsp, 40
    mov     rcx, message
    call    printf
    add     rsp, 40
    ret
message:
    db  'Hello, World', 10, 0

Assembling and linking:

nasm -f win64 Hello_world.asm
gcc Hello_world.obj
DarkAtom
  • 2,589
  • 1
  • 11
  • 27
  • *which a debugger can use to spill the 4 register arguments to the stack.* - No, a debugger must not modify memory above RSP in the program being debugged! A debug *build* or a variadic function would include asm instructions that store the incoming register args to the shadow space. An optimized build might also spill something to that space if necessary, perhaps one of the incoming args. – Peter Cordes Jun 15 '23 at 19:29
  • Thanks for the clarification! It makes perfect sense that a debugger can't touch the program's memory (unless explicitly told to). I've never looked into the M$ calling convention too deeply. – DarkAtom Jun 15 '23 at 19:34
  • Windows x64 appears to be optimized for variadic functions. Besides having shadow space where args can be spilled contiguous with the stack args, every arg goes in exactly one 8-byte slot, either by value or by pointer if wider than 8 bytes. x86-64 SysV makes variadic functions more complicated, but is otherwise more efficient in most ways. – Peter Cordes Jun 15 '23 at 19:40
  • I've seen Q&As like [How to set function arguments in assembly during runtime in a 64bit application on Windows?](https://stackoverflow.com/q/49283616) where people wanted to build an array of args based on runtime-variable type information. IDK if that kind of insanity is more common in Windows programs, perhaps from historical 32-bit where stack args were just a flat buffer in memory so this kind of hackiness could be done easily in asm. – Peter Cordes Jun 15 '23 at 19:42
  • For completeness, the `main` function also needs exception unwind codes. – Raymond Chen Jun 15 '23 at 21:54