2

Hi I've been trying to write a simple hello world program in assembly and compile it into a .o file, then link it with the standard C library to create a .exe so that I can view the disassembly for 'puts' on my system using gdb -tui. I am using Cygwin with the following utility versions (got these with as --version && ld --version). I am trying to do all this on Windows 8 x64.

as version 2.25

ld version 2.25

test.asm

I've seen several assembly standards on the internet while learning x86 assembly. I think the one I am writing here is GAS.

.extern puts
_start:
    mov $msg, %rdi
    call puts
    xor %rax, %rax
    ret
msg: 
    .ascii "hello world"

assembler

I can assemble the above file no problem, the as utility doesn't give me a warning or any errors, here is the way I call the as utility.

as test.asm -o test.o

linker

Here is where I am having trouble, the following command is how I think I should link the object file with the standard c library.

ld test.o -o test.exe -lc

This command produces the following errors, which I've been stumped by. I've tried to find the answer in other posts and through google, but maybe I'm missing something.

test.o:fake:(.text+0x3): relocation truncated to fit: R_X86_64_32S against `.text`
/usr/lib/libc.a(t-d000957.o):fake:(.text+0x2): undefined reference to `__imp_puts`
/usr/lib/libc.a(t-d000957.o):fake:(.text+0x2): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `__imp_puts`
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
v3nd3774
  • 55
  • 2
  • 8
  • If you wish to use C library you should use entry point `main` and `gcc` for linking. – Jester Dec 30 '15 at 00:06
  • I tried your suggestion, unfortunately it gave me some new errors. Here's what I did, I replaced `_start` with `main`, and replaced `ld test.o -o test.exe -lc` with `gcc test.o -o test.exe`. That produced the following errors `relocation truncated to fit: R_X86_64_32S against '.text' libcygwin.a(libcmain.o): In function 'main': ` , ` /libcmain.c:39: undefined reference to WinMain` , ` /libcmain.c:39:(.text.startup+0x7f): relocation truncated to fit: R_X86_64_PC32 against undefined symbol 'WinMain' ` , ` collect2: error: ld returned 1 exit status ` . – v3nd3774 Dec 30 '15 at 00:36
  • Then apparently cygwin requires `WinMain`, so try that. – Jester Dec 30 '15 at 00:39
  • I just tried changing `main` to `WinMain`, with no change in the errors in the OP. *Note* It looks like I can use `gcc test.asm -o test.o -c` to produce the object file, I feel that the problem comes up when I run it through the linker `ld`. Changing `main` to `WinMain` is what you suggested right? – v3nd3774 Dec 30 '15 at 00:50
  • Change `_start` to `WinMain`, also add `.global WinMain` to export it. – Jester Dec 30 '15 at 01:08

2 Answers2

5

Code with some comments. You seem to have been borrowing from Linux 64-bit calling convention by passing first parameter in RDI. On windows you pass the first parameter in RCX. See the 64-bit calling convention for Microsoft Windows. You need to use movabsq to move the 64-bit address of a label into a 64-bit register. You also should make sure you align the stack properly (16-byte boundary); allocate at least 32 bytes for shadow space; and I've added a stack frame.

.extern puts
.global main
.text
main:
    push %rbp
    mov %rsp, %rbp         /* Setup stack frame */
    subq $0x20, %rsp       /* Allocate space for 32 bytes shadow space 
                              no additional bytes to align stack are needed
                              since return address and rbp on stack maintain
                              16 byte alignment */ 
    movabsq $msg, %rcx     /* Loads a 64 bit register with a label's address
                              Windows 64-bit calling convention
                              passes param 1 in rcx */
    call puts
    xor %rax, %rax         /* Return value */
    mov %rbp, %rsp
    pop %rbp               /* Remove current stackframe */
    ret

.data
msg:
    .asciz "hello world"   /* Remember to zero terminate the string */

Rename your assembler file with a .s extension instead of .asm and assemble and link with:

gcc -o test.exe test.s

Without renaming .asm to .s you may find GCC on Cygwin will confuse your assembler file with a linker script.


Version without Stack Frame

This code is similar to the code above but the stack frame is removed. The RBP/RSP prologue and epilogue code has been removed in this version. We still have to align the stack. Since we are no longer pushing RBP on the stack, we need to allocate 32 bytes of shadow space on the stack and an additional 8 bytes to put the stack back into 16 byte alignment. This alignment and shadow space allocation needs to be done before making calls to other functions (Like the Win32 API and the C library) from within our function(s). Failure to set up the stack properly may result in calls to other functions mysteriously segfaulting or behaving unexpectedly. The 64-bit Windows calling convention covers this at the link I provided previously at the beginning of this answer.

The modified code:

.extern puts
.global main
.text
main:
    subq $0x28, %rsp       /* Allocate space for 32 bytes shadow space
                              Additional 8 bytes to align stack are needed
                              since 8 byte return address on stack no longer
                              makes the stack 16 byte aligned. 32+8=0x28 */
    movabsq $msg, %rcx     /* Loads a 64 bit register with a label's address
                              Windows 64-bit calling convention
                              passes param 1 in rcx */
    call puts
    xor %rax, %rax         /* Return value */
    addq $0x28, %rsp       /* Restore stack pointer to state it was in prior
                              to executing this function so that `ret` won't fail */
    ret

.data
msg:
    .asciz "hello world"   /* Remember to zero terminate the string */
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • @RossRidge : I've modified the comment to match what I wrote in the answer. I had copy and pasted from some other code, and it was ambiguous. And yes, I was your upvoter. – Michael Petch Dec 30 '15 at 01:49
  • Thanks Michael! I got an executable running, I had a segmentation fault error but upon further inspection I found that terminating the program with `ret` was causing the issue, after replacing this line with `call ExitProcess` the issue was solved and the executable is working as it's supposed to. Thanks I've been struggling with this fora while! Are there any resources (books, articles) besides the windows calling conventions you recommend to read? – v3nd3774 Dec 30 '15 at 02:36
  • 1
    I ran your code after following the instructions in your post and I recieved the same error. I tried swapping line `mov %rbp, %rsp` with line `mov %rsp, %rbp` and that removed the segmentation fault error I was getting. Thanks Michael! – v3nd3774 Dec 30 '15 at 03:05
  • @MichaelPetch: He's right, you have the args reversed in your version with a stack frame. The prologue *should* `mov` to `%rbp`, and the epilogue should `mov` to `%rsp` (and restore the old `%rbp`, which it does). – Peter Cordes Dec 30 '15 at 04:38
  • 1
    @PeterCordes AH you know I didn't even see that. I had ripped some code out of an intel assembly syntax and pasted it into my answer and added %s. I didn't even notice that was what was being referred to (that I forgot to swap around the operands) – Michael Petch Dec 30 '15 at 04:54
  • @v3nd3774 : please see my updated code. I did in fact have the operands reversed when I copied some NASM code into my answer, added %s to the register names and didn't swap the operands around. I didn't even notice you were suggesting I may have had them backwards. Sorry about that. – Michael Petch Dec 30 '15 at 05:00
  • You don't *need* `movabs`, and it's a poor choice vs. `lea msg(%rip), %rcx` so it wouldn't be my first recommendation. [How to load address of function or label into register](https://stackoverflow.com/q/57212012) – Peter Cordes Jun 25 '21 at 22:23
4

There are a number of problems with what you've written and the attempts you've made to fix it. The first is, like Jester says, if you're going to use C library function your entry point should be named main. This gives the C runtime library a chance to initialize itself before it calls main. When you changed the entry point to main you didn't also declare it global. That meant the linker couldn't find it and that why you got the error about not finding WinMain. Because of how Cygwin's runtime library is written is end up looking for a few different symbols as the entry point, WinMain is one of them and what it ends up complaining about not finding. However, unless you're writing Win32 application you should use main.

Finally, the relocation truncated to fit: R_X86_64_32S against '.text' message comes from the mov $msg, %rdi instruction. The GNU assembler interprets this instruction as only taking a 32-bit immediate operand on the left, however msg is a 64-bit address so it ends being "truncated to fit". The solution is either to use movabs $msg,%rdi, which uses a 64-bit immediate, or better yet, lea msg(%rip),%rdi which uses RIP relative addressing.

Ross Ridge
  • 38,414
  • 7
  • 81
  • 112