v3nd3774
v3nd3774

Reputation: 47

Assembly relocation truncated to fit

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`

Upvotes: 2

Views: 5153

Answers (2)

Michael Petch
Michael Petch

Reputation: 47553

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 */

Upvotes: 5

Ross Ridge
Ross Ridge

Reputation: 39551

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.

Upvotes: 4

Related Questions