As most of you know by now, I’m hard at work on the x64 edition of my assembly book, to be called X64 Assembly Language Step By Step. I’m working on the chapter where I discuss calling functions in libc from assembly language. The 2009 edition of the book was pure 32-bit x86. Parameters were passed to libc functions mostly by pushing them on the stack, which required cleaning up the stack after each call, etc.
Calling conventions in x64 are radically different. The first six parameters to any function are passed in registers. (More than six and you have to start pushing them on the stack.) The first parameter goes in RDI, the second in RSI, the third in RDX, and so on. When a function returns a single value, that value is passed back in RAX. This allows a lot more to be done without fooling with the stack.
Below is a short example program that makes four calls to libc functions: Two calls to puts(),
a call to time
, and a call to ctime
. Here’s the makefile for the program:
showtime: showtime.o gcc showtime.o -o showtime -no-pie showtime.o: showtime.asm nasm -f elf64 -g -F dwarf showtime.asm -l showtime.lst
I’ve used this makefile for other example programs that call libc functions, and they all work. So take a look:
section .data timemsg db "The timestamp is: ",0 timebuf db 28,0 ; not useed yet time1 dq 0 ; time_t stored here. section .bss section .text extern time extern ctime extern puts global main main: push rbp ; Prolog mov rbp,rsp mov rdi,timemsg ; Put address of message in rdi call puts ; call libc function puts xor rax,rax ; Zero rax call time ; time returns time_t value in rax mov [time1],rax ; Save time_t value to var time1 mov rdi,time1 ; Copy pointer to time_t value to rdi call ctime ; Returns ptr to the date string in rax mov rdi,rax ; Copy pointer to string into rdi call puts ; Print ctime's output string mov rsp,rbp ; Epilog pop rbp ret ; Return from main()
Not much to it. There are four sections, not counting the prolog and epilog: The program prints an intro message using puts
, then fetches the current time in time_t
format, then uses ctime
to convert the time_t
value to the canonical human-readable format, and finally displays the date string. All done.
So what’s the problem? When the program hits the second puts
call, it hangs, and I have to hit ctrl-z to break out of it. That’s peculiar enough, given how many times I’ve successfully used puts
, time
, and ctime
in short examples.
The program assembles and links without problems, using the makefile shown above the program itself. I’ve traced it in a debugger, and all the parameters passed into the functions and their return values are as they should be. Even in a debugger, when the code calls the second instance of puts
, it hangs.
Ok. Now here’s the really weird part: If you comment out one of the two puts
calls (it doesn’t matter which one) the program doesn’t hang. One of the lines of text isn’t displayed but the calls to time
and ctime
work normally.
I’ve googled the crap out of this problem and haven’t come up with anything useful. My guess is that there’s some stack shenanigans somewhere, but all the register values look fine in the debugger, and the pointer passed back in rax by ctime
does indeed point to the canonical null-terminated text string. The prolog creates the stack frame, and the epilog destroys it. My code doesn’t push anything between the prolog and epilog. All it does is make four calls into libc. It can successfully make three calls into libc…but not four.
Do you have to clean up the stack somehow after a plain vanilla x64 call into libc? That seems unlikely. And if so, why doesn’t the problem happen when the other three calls take place?
Hello, wall. Anybody got any suggestions?