Reputation: 47
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
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.
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
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