In Printing Command Line Arguments, we saw how to define and call functions in x86-64 Linux Assembly.
Now I wanted to know how to provide arguments to a function and return values. In other words, when this is done in a higher level language, how is it translated at the Assembly code level?
The answer is pretty straightforward. In x86-64 Assembly, there is a convention defining which registers are defined to be used for the first, second, third and so forth.
Argument number | Register |
---|---|
Arg 1 | $ rdi |
Arg 2 | $ rsi |
Arg 3 | $ rdx |
Arg 4 | $ rcx |
Arg 5 | $ r8 |
Arg 6 | $ r9 |
Arg 7 | stack |
All further arguments are also pushed on to the stack.
Return values are expected to be stored on in register $rax
.
Here is an example of code that utilized the conventional registers to use return values and pass arguments to print out a programs command line arguments:
section .data
section .text
global _start
_start:
call .getNumberOfArgs ; expects return value in $rax
mov rdi, rax
call .printNumberOfArgs ; expects value to be in 1st argument, i.e. $rdi
call .exit
.getNumberOfArgs:
pop rbx ; this is the address of the calling fxn. Remove it from the stack for a moment
pop rax ; get argc from stack
push rbx ; return address of calling fxn to stack
ret
; expects value to be in 1st argument, i.e. $rdi
.printNumberOfArgs:
pop rbx ; this is the address of the calling fxn. Remove it from the stack for a moment
add rdi, 48 ; convert number of args to ascii (only works if < 10)
push rdi ; push the ascii converted argc to stack
mov rsi, rsp ; store value of rsp, i.e. pointer to ascii argc to param2 of sys_write fxn
mov rdx, 8 ; param3 of sys_write fxn is the number of bits to print
push rbx ; return the address of the calling fxn to top of stack.
call .print
; clean up the number that was pushed onto the stack. Retaining the return address currently on top of stack
pop rbx
pop rcx
push rbx
ret
.print: ; print expects the calling location to be at the top of the stack
mov rax, 1
mov rdi, 1
syscall
ret ; return to location pointed to at top of stack
.exit:
mov rax, 60
mov rdi, 0
syscall
There are some additional well-defined calling conventions are helpful to follow. See here.