Linux X86-64 assembly and printf
Linux (and Windows) x86-64 calling convention has the first few arguments not on the stack, but in registers instead
See http://www.x86-64.org/documentation/abi.pdf (page 20)
Specifically:
- If the class is MEMORY, pass the argument on the stack.
- If the class is INTEGER, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used.
- If the class is SSE, the next available vector register is used, the registers are taken in the order from %xmm0 to %xmm7.
- If the class is SSEUP, the eightbyte is passed in the next available eightbyte chunk of the last used vector register.
- If the class is X87, X87UP or COMPLEX_X87, it is passed in memory.
The INTEGER
class is anything that will fit in a general purpose register, so that's what you would use for string pointers as well.
Calling printf in x86_64 using GNU assembler
There are a number of issues with this code. The AMD64 System V ABI calling convention used by Linux requires a few things. It requires that just before a CALL that the stack be at least 16-byte (or 32-byte) aligned:
The end of the input argument area shall be aligned on a 16 (32, if __m256 is
passed on stack) byte boundary.
After the C runtime calls your main
function the stack is misaligned by 8 because the return pointer was placed on the stack by CALL. To realign to 16-byte boundary you can simply PUSH any general purpose register onto the stack and POP it off at the end.
The calling convention also requires that AL contain the number of vector registers used for a variable argument function:
%al is used to indicate the number of vector arguments passed to a function requiring a variable number of arguments
printf
is a variable argument function, so AL needs to be set. In this case you don't pass any parameters in a vector register so you can set AL to 0.
You also dereference the $format pointer when it is already an address. So this is wrong:
mov $format, %rbx
mov (%rbx), %rdi
This takes the address of format and places it in RBX. Then you take the 8 bytes at that address in RBX and place them in RDI. RDI needs to be a pointer to a string of characters, not the characters themselves. The two lines could be replaced with:
lea format(%rip), %rdi
This uses RIP Relative Addressing.
You should also NUL terminate your strings. Rather than use .ascii
you can use .asciz
on the x86 platform.
A working version of your program could look like:
# global data #
.data
format: .asciz "%d\n"
.text
.global main
main:
push %rbx
lea format(%rip), %rdi
mov $1, %esi # Writing to ESI zero extends to RSI.
xor %eax, %eax # Zeroing EAX is efficient way to clear AL.
call printf
pop %rbx
ret
Other Recommendations/Suggestions
You should also be aware from the 64-bit Linux ABI, that the calling convention also requires functions you write to honor the preservation of certain registers. The list of registers and whether they should preserved is as follows:
Any register that says Yes
in the Preserved across
function calls column are ones you must ensure are preserved across your function. Function main
is like any other C function.
If you have strings/data that you know will be read only you can place them in the .rodata
section with .section .rodata
rather than .data
In 64-bit mode: if you have a destination operand that is a 32-bit register, the CPU will zero extend the register across the entire 64-bit register. This can save bytes on the instruction encoding.
It is possible your executable is being compiled as position independent code. You may receive an error similar to:
relocation R_X86_64_PC32 against symbol `printf@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC
To fix this you'll have to call the external function printf
this way:
call printf@plt
This calls the external library function via the Procedure Linkage Table (PLT)
Understanding the printf function in assembly, nasm, x86. I don't know why this code isn't printing anything out
Let's go through this bit by bit.
In the main
function you have push qword 300
. I assume this is meant to pass an argument to sum
? If so... that's not what you are doing. I would recommend instead following the AMD64 calling convention and using mov rdi, 300
and then call sum
, which would expect to find its first argument in rdi
.
In any case, in sum
the first thing you do is push rbp
, which is pretty standard. Then you push rbx
. That's a callee-saved register, so fine. Then mov rbp, rsp
which is, again, pretty standard.
Now you free some stack with add rsp,24
, which I don't understand. The stack grows toward lower addresses, so to reserve some stack you would subtract from rsp
. But maybe that's not what you are doing?
Now it really gets confusing to me. You do mov rbx, [rbp]
, but since rbp
wasn't modified by the main
function, it isn't going to be pointing anywhere we want it to be. I can only assume this is some attempt to get at an argument on the stack?
Honestly, at this point I am stumped.
It is usual to start your function with the following boilerplate.
push rbp
mov rbp, rsp
The reason is to protect the stack. Now all references can be done with respect to rbp
, which never changes in your code (it always points to your stack frame). It is a callee-saved register, so we have to save it anyway, and do so on the stack just before setting it.
It is usual to end your function with the following boilerplate.
mov rsp, rbp
pop rbp
ret
This restores the stack pointer and rbp
to what they were on entry, as required by the AMD64 calling convention. There is a synonym for this: leave
. The following code is equivalent.
leave
ret
Now, when you use printf
(or any function that might make use of the xmm
registers) you have to also make sure the stack is aligned on a 16-byte boundary before you call. You can assume the stack is aligned on entry to main
. The push rbp
will mess it up (off by 8), but when you execute call printf
you will push 8 more bytes (the return address) and presto! The stack is aligned.
My last recommendation is to use the AMD64 calling convention and not use the stack. Put the argument in rdi
, and obtain the return value in rax
.
I can't offer more help without knowing what you are trying to do.
using printf before and inside a loop x86-64 assembly
The easiest way to protect a register from being accessed by a subroutine is to push
it. According to the ABI V calling convention printf
may change any register except RBX
, RBP
, R12–R15
. The registers you need to preserve are RAX
, RDX
, RSI
, RDI
, R8
and R11
(RCX
is no longer needed), so push
before the call to printf
and pop
them afterwards:
pushq %rax
pushq %rdx
pushq %rsi
pushq %rdi
pushq %r8
pushq %r11
movq $.LC1, %rdi
movq %rax, %rsi
movq $0, %rax
call printf
popq %r11
popq %r8
popq %rdi
popq %rsi
popq %rdx
popq %rax
Now, you can copy the block into the loop. For each printf, you have to think about what needs to be secured:
...
multInts:
pushq %rbp
movq %rsp, %rbp
#add code here for what the functions should do
pushq %rdx # Preserve registers
pushq %rdi
pushq %rsi
movq $.LC0, %rdi # Format string (no further values)
movq $0, %rax # No vector registers used
call printf # Call C function
popq %rsi # Restore registers
popq %rdi
popq %rdx
movq $0, %r8 #initialize index for array access in caller save reg
loop0:
cmpl %r8d, %edi #compare index to size
je exit0 #exit if equal
movslq (%rsi,%r8,4),%rax # Load a long into RAX
movslq (%rdx,%r8,4),%r11 # Load a long into R11
imulq %r11, %rax # RAX *= R11
pushq %rax # Preserve registers
pushq %rdx
pushq %rsi
pushq %rdi
pushq %r8
pushq %r11
movq $.LC1, %rdi # Format string
movq %rax, %rsi # Value
movq $0, %rax # No vector registers used
call printf # Call C function
popq %r11 # Restore registers
popq %r8
popq %rdi
popq %rsi
popq %rdx
popq %rax
incq %r8 #increment index
jmp loop0
exit0:
movq %rbp, %rsp
popq %rbp
ret
...
BTW: .string "%i \n"
will force printf
only to process the lower 32-bit of RDI
. Use .string %lli \n
instead.
Linking and calling printf from gas assembly
It is possible to use ld
, but not recommended: if you use libc functions, you need to initialise the C runtime. That is done automatically if you let the C compiler provide _start
and start your program as main
. If you use the libc but not the C runtime initialisation code, it may seem to work, but it can also lead to strange spurious failure.
If you start your program from main
(your second case) instead, it's as simple as doing gcc -o program program.s
where program.s
is your source file. On some Linux distributions you may also need to supply -no-pie
as your program is not written in PIC style (don't worry about this for now).
Note also that I recommend not mixing libc calls with raw system calls. Instead of doing a raw exit system call, call the C library function exit
. This lets the C runtime deinitialise itself correctly, including flushing any IO streams.
Now if you assemble and link your program as I said in the first paragraph, you'll notice that it might crash. This is because the stack needs to be aligned to a multiple of 16 bytes on calls to functions. You can ensure this alignment by pushing a qword of data on the stack at the beginning of each of your functions (remember to pop it back off at the end).
x86 Assembly - printf doesn't print without \n
The problem is that printf
by default just prints stuff into the stdout buffer. Things won't actually be printed until the buffer is flushed. The depends on the buffering mode of stdout, but, by default, it is line-buffered, which means it gets flushed every time you print a newline character.
To flush explicitly in C, you call fflush; you can do that in asm code with
pushl stdout
call fflush
addl $4, %esp
Alternately, you can call the stdlib exit
function (which flushes all I/O buffers before actually exiting), instead of using the _exit
system call, which does not.
Related Topics
Start-Stop-Daemon Quoted Arguments Misinterpreted
Grep Search All Files in Directory for String1 and String2
Strange Return Value "134" to Call Gawk in a Bash Script
Bash Script: Difference in Minutes Between Two Times
Rtnetlink Answers :No Such File or Directory Error
Shared Library Bottleneck on Numa MAChine
What Happens When a Signal Is Received While Already in a Signal Handler
Set Static Ip If Not Obtained from Dhcp (Script)
Jmeter - Could Not Find the Testplan Class
Sed: How to Delete Lines Matching a Pattern That Contains Forward Slashes
Delete Whitespace in Each Begin of Line of File, Using Bash
How to Prevent Out of Memory (Oom) Freezes on Linux
How to Calculate the Total Size of Certain Files Only, Recursive, in Linux