Search code examples
linuxassemblyreturnx86-64fasm

Why am I getting a segfault when I `ret'? (FASM)


Through some manipulation I have narrowed it down to a problem with the ret op. I know call pushes the return address to the stack; is it illegal to pop it and push it back?

format ELF64 executable 3

entry start

segment readable executable

start:
    pop rcx         ; argc
    mov [argc],cl       ; int -> ASCII
    add [argc],'0'
    push 1 argc 1
    call sys_write

    mov rdi,0
    mov rax,60
    syscall

sys_write: ; (fd,*buf,count)
    pop r11
    pop rdx rsi rdi
    mov rax,1
    syscall
    push r11
    ret

segment readable writable

argc rb 1

Output is:

$ ./prog
1Segmentation fault
$ _

Solution

  • The syscall instruction clobbers the contents of R11 with the RFLAGS register, which means that after making a syscall the value you stored in R11 is overwritten.

    A solution might be to simply select an appropriate register that goes unmodified by a syscall. According to Intel's instruction reference manual (http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-manual-325462.pdf, page 4-668 Vol. 2B) syscall overwrites RCX, RIP, R11, and RFLAGS as part of its operation, but the OS is of course able to restore RIP and RFLAGS.

    This leaves plenty of other options. A good choice would be a register that a function call is allowed to clobber in the standard user-space calling convention, but that Linux system calls will leave unmodified. R8 fits the bill. Since you aren't using the standard x86-64 System V function-calling convention, RBX or RBP would be even better choices (smaller machine-code size since they don't need a REX prefix).