1

I want to write programs in SysV ABI x86_64 assembly and so far I have passed the arguments in registers quite randomly.

But I just saw on this forum, that there is a standard for this. We must pass RDI, RSI, RDX and RCX (int that exact order).

Now I am asking myself two questions.

First, are not ESI and EDI supposed to be used only during operations on strings? What happens if I want to pass an integer as an argument and not a string?

Secondly, what if I need to pass a 32-bit argument and not a 64-bit argument? For example, if I want to create an identifier for the system call write, I would write this:

;; void write(int fd, const void *buf, size_t count);
;; Inputs   :  ESI = offset string, EDX = number of characters to write, EBX = file descriptor
;; Outputs  :  <none>
;; Clobbers :  <none>
write:
    mov ecx, esi

    mov eax, 4
    int 0x80

    ret

But with the standard, how can I move the values from 64-bit registers to 32-bit registers? Because I can't do that:

mov ecx, rdi ; impossible
janw
  • 8,758
  • 11
  • 40
  • 62
  • related: [What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?](https://stackoverflow.com/q/46087730) - a normal write wrapper would be `mov eax, 1` / `syscall`, because the x86-64 Linux system-calling convention is close to the x86-64 System V function-calling convention, matching for the first 3 args. – Peter Cordes Aug 07 '20 at 16:19

1 Answers1

3

In general rdi and rsi can be treated as general purpose registers, i.e. you can use them for arbitrary arithmetic and memory operations. They have certain special meanings, as they are also used as index registers for string operations. However, the architecture does not care whether you store a string pointer or another arbitrary 64-bit number in there.


Regarding passing 32-bit values, you can simply access the lower 32-bit portion of your source register:

mov ecx, edi

This only moves the least-significant 32 bits to ecx. Note that it does not really make a difference if you pass the entire 64 bits instead - if the callee only accesses the 32-bit sub register ecx, the result is the same:

mov rcx, rdi
; ...
; use ecx

A small note regarding the example code in the question: You seem to be using a 32-bit style system call in a 64-bit environment. This may break if the pointer to buf does not fit into 32 bits. A 64-bit version of the write system call would look like this:

write:
    ; syscall number
    mov rax, 1

    ; all other arguments are already in the right registers
    syscall

    ret

Further information:

janw
  • 8,758
  • 11
  • 40
  • 62
  • 1
    Perhaps worth pointing out the bug in @NathanCasabieille's code - pointers are 64-bit, you don't want to truncate them to 32-bit and you don't normally want to use `int 0x80` in 64-bit code. [What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?](https://stackoverflow.com/q/46087730). Or maybe that would just be clutter that distracts from the intended question, and is already answered elsewhere. – Peter Cordes Aug 07 '20 at 16:22
  • 1
    @PeterCordes These are both valid points. I have added a paragraph and a 64-bit example at the end - this shouldn't clutter the answer itself, and may be helpful for potential future readers. :) – janw Aug 07 '20 at 16:57