Functions
RAM is divided in 4 segments, 1 of them is the stack.
The Stack
The stack is a segment of memory allocated to store data, usually to story data and retrieve them back temporarily. Top of the stack is referenced as rsp
and the bottom as rbp
.
push
Copy address to top of stack
push rax
pop
Move item to top of the stack
pop rax
Stack uses Last-iin First-Out (LIFO) . Meaning we can only pop out the last element pushed into the stack.
We can push rax
a value into the stack and it would come on top of the value we pused before, would we use pop rax
a value it would remove it from top of the stack.

Functions/Syscalls
Functions and syscall use the registers for processing, if values stored in registers will get changed after a function call or syscall we will lose them.
When calling a syscall to print Hello world
and save the value in rax, we would push rax
into stack. We can then execute the syscall and afterwards pop
value back to rax
. Now is saves the value of rax.
Lets say we have "42" stored in rax
.
; Push rax
push rax ; Put 42 on top of stack
; Use rax for syscall
mov rax 1 ; 1 is syscall number for write
syscall ; syscall "Hello word
; rax now contains value from syscall
; Get original value back
pop rax ; Get 42 back from stack
Syscalls
A syscall is like a global function written in C, provided by the OS kernel. Where the kernel is like a bridge between the programs and hardware. A syscall takes the arguments in registers and executes it. To write something to screen we can use write
syscall, input the string to be printed and then call the syscall to print.
A list of syscalls provided by Linux Kernel
$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h
#ifndef _ASM_X86_UNISTD_64_H
#define _ASM_X86_UNISTD_64_H 1
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5
Write syscall
# Finding the arguments
$ man -s 2 write
ssize_t write(int fd, const void *buf, size_t count);
Its asking for 3 arguments:
File Descriptor
fd
to be printed to (usually1
forstdout
)The address pointer to the string to be printed
The length we want to print
Calling syscall
To call a syscall, 3 things are required:
Save registers to stack
Set its syscall number in rax
Sets arguments in registers
Use syscall instruction to call it.
We usually should save any registers we use to the stack before any function call or syscall.
Syscall Number
Start by moving syscall number to rax register. In the manual we find #define __NR_write 1
. The kernel now knows which syscall to use.
mov rax, 1
Syscall Arguments
Put each function's argument in het corresponding register. The first argument should be place in rdi.
Syscall Number/Return value
rax
al
Callee Saved
rbx
bl
1st arg
rdi
dil
2nd arg
rsi
sil
3rd arg
rdx
dl
4th arg
rcx
cl
5th arg
r8
r8b
6th arg
r9
r9b
We have 6 registers for each of the first 6 arguments. Return values of of syscalls or functions are stored in rax
.
We know have to pass fd, pointer and length but our string we want to "Fibonacci Sequence:" is longer than 16 chars so it doesnt fit. Lets make a variable
global _start
section .data
message db "Fibonacci Sequence:", 0x0a ; 0x0a is new line
Message is the pointer to where our string will be stored in memory. We can wc
use to count bytes echo -n "Fibonacci Sequence:" | wc -c
. But 1 byte because of the new line.
mov rax, 1 ; rax: syscall number 1
mov rdi, 1 ; rdi: fd 1 for stdout
mov rsi,message ; rsi: pointer to message
mov rdx, 20 ; rdx: print length of 20 bytes
Exit Syscall
By not properly ending the program we get the error segmentation fault
. In the manual we can find the exit syscall
number and arguments.
# find number
$ grep exit /usr/include/x86_64-linux-gnu/asm/unistd_64.h
#define __NR_exit 60
#define __NR_exit_group 231
# find arguments
$ man -s 2 exit
void _exit(int status);
It needs 1 integer argument, in this we use the exit code 0. And this is our exit syscall
code.
mov rax, 60
mov rdi, 0
syscall
Final code
global _start
section .data
message db "Fibonacci Sequence:", 0x0a
section .text
_start:
mov rax, 1 ; rax: syscall number 1
mov rdi, 1 ; rdi: fd 1 for stdout
mov rsi,message ; rsi: pointer to message
mov rdx, 20 ; rdx: print length of 20 bytes
syscall ; call write syscall to the intro message
xor rax, rax ; initialize rax to 0
xor rbx, rbx ; initialize rbx to 0
inc rbx ; increment rbx to 1
loopFib:
add rax, rbx ; get the next number
xchg rax, rbx ; swap values
cmp rbx, 10 ; do rbx - 10
js loopFib ; jump if result is <0
mov rax, 60
mov rdi, 0
syscall
Now if we run it we dont get a segmentation fault
➜ ~ ./assembler.sh fib.s
Fibonacci Sequence:
Procedures
To make code more more efficient we can use functions and procedures. Procedures are most used for code refactoring. Its a set of instructions to execute a point in the program. We define it under a procedure label.
We can create procedures: printMessage, initFib, loopFib and Exit. To make it more efficent we use call
to call a procedure. Call saves next instruction pointer rip
to the stack and then just to the procedure. If procedure has ran we end it with ret
to return to the point where we were before jumping to procedure.
ret
instruction is crucial in Return-Oriented Programming (ROP)
call
push the next instruction pointer rip
to the stack, then jumps to the specified procedure
call printMessage
ret
pop the address at rsp
into rip
, then jump to it
ret
Our new script
global _start
section .data
message db "Fibonacci Sequence:", 0x0a
section .text
_start:
call printMessage ; print intro message
call initFib ; set initial Fib values
call loopFib ; calculate Fib numbers
call Exit ; Exit the program
printMessage:
mov rax, 1 ; rax: syscall number 1
mov rdi, 1 ; rdi: fd 1 for stdout
mov rsi,message ; rsi: pointer to message
mov rdx, 20 ; rdx: print length of 20 bytes
syscall ; call write syscall to the intro message
ret
initFib:
xor rax, rax ; initialize rax to 0
xor rbx, rbx ; initialize rbx to 0
inc rbx ; increment rbx to 1
ret
loopFib:
add rax, rbx ; get the next number
xchg rax, rbx ; swap values
cmp rbx, 10 ; do rbx - 10
js loopFib ; jump if result is <0
ret
Exit:
mov rax, 60
mov rdi, 0
syscall
Functions
Calling Functions
Functions are used to use the stack and registers fully. Functions have a Calling Convention. There are four main things we need to consider before calling a function:
Save Registers
on the stack (Caller Saved
)Pass
Function Arguments
(like syscalls)Fix
Stack Alignment
Get Function's
Return Value
(inrax
)
Writing Functions
When it comes to writing a function, there are different things to consider:
Saving
Callee Saved
registers (rbx
andrbp
)Get arguments from registers
Align the Stack
Return value in
rax
Using External Functions
We can use libc to utilize function without have to rewrite or write that code. Like the printf function in libc which accepts printing format. We can pass it numbers and print it as integers, conversion is done automatically. If we want to use a function from libc we have to import it and specify the libc library for dynamic linking when linking with ld.
Importing libc Functions
We can use the extern instuction to import the libc function. In this case we use the printf
function.
global _start
extern printf
section .data
message db "Fibonacci Sequence:", 0x0a
outFormat db "%d", 0x0a, 0x00
section .text
_start:
call printMessage ; print intro message
call initFib ; set initial Fib values
call loopFib ; calculate Fib numbers
call Exit ; Exit the program
printMessage:
mov rax, 1 ; rax: syscall number 1
mov rdi, 1 ; rdi: fd 1 for stdout
mov rsi, message ; rsi: pointer to message
mov rdx, 20 ; rdx: print length of 20 bytes
syscall ; call write syscall to the intro message
ret
initFib:
xor rax, rax ; initialize rax to 0
xor rbx, rbx ; initialize rbx to 0
inc rbx ; increment rbx to 1
ret
printFib:
push rax ; push registers to stack
push rbx
mov rdi, outFormat ; set 1st argument (Print Format)
mov rsi, rbx ; set 2nd argument (Fib Number)
call printf ; printf(outFormat, rbx)
pop rbx ; restore registers from stack
pop rax
ret
loopFib:
call printFib ; print current Fib number
add rax, rbx ; get the next number
xchg rax, rbx ; swap values
cmp rbx, 10 ; do rbx - 10
js loopFib ; jump if result is <0
ret
Exit:
mov rax, 60
mov rdi, 0
syscall
To compile we have to link the libc library
// Some code$ nasm -f elf64 fib.s && ld fib.o -o fib -lc --dynamic-linker /lib64/ld-linux-x86-64.so.2 && ./fib
Importing libc Functions
We can use scanf
to take user input and have it converted to an integer which we can use with cmp
.
Import with
global _start
extern printf, scanf
getInput:
; call scanf
The set the variables of scanf
.
section .data
message db "Please input max Fn", 0x0a
outFormat db "%d", 0x0a, 0x00
inFormat db "%d", 0x00
We need to set a buffer space for input storage in the .bss memory segment. We reserve 1 byte.
section .bss
userInput resb 1
Last updated
Was this helpful?