Linux X86-64 Assembly and Printf

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:

  1. If the class is MEMORY, pass the argument on the stack.
  2. If the class is INTEGER, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used.
  3. If the class is SSE, the next available vector register is used, the registers are taken in the order from %xmm0 to %xmm7.
  4. If the class is SSEUP, the eightbyte is passed in the next available eightbyte chunk of the last used vector register.
  5. 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:

Sample Image

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



Leave a reply



Submit