Tovi Jaeschke

Links:

Recent posts:

Assembly tutorial: hello world to fizzbuzz

Assembly is a programming language where the programmer interfaces directly with the kernel, which means the syntax is different depending on the architecture and OS of the computer. I started learning Assembly as a way to learn how to reverse engineer programs written in c/c++.

You can download my code here or by running

git clone https://gitlab.com/tovijaeschke/asm-tutorial.git

Basic assembly syntax

Assembly has three basic sections:

Assembly uses a series of registers to store data, which can be explicitly changed by the programmer, or may hold the result of various syscalls. Syscalls are executed by entering the syscall number in the rax register, storing any necessary arguments in the other registers (rbx,rcx,rsi,rdi), and calling the syscall mnemonic. A table of syscall numbers and their arguments can be found here.

Each line of assembly follows a simple syntax:

	
(label)   mnemonic   (operands)   (;comment)
	

where each field in brackets is optional.

Hello, World!!

First we create the variable "msg" in the .data section. Then we initialize each register in preparation to print the string, and call the kernel. After this we exit the program.

	
; hello_world.asm
; Prints "Hello, world!" in 64 bit linux assembly written in intel syntax
; Compile with "nasm -f elf64 hello_world.asm && ld hello_world.o -o hello_world" 

.text:
	global _start

_start:
	mov rax, 1   ; system call for write
	mov rdi, 1   ; file handle for stdout
	mov rsi, msg ; move msg variable to rsi register for printing
	mov rdx, 15  ; length of the string
	syscall      ; call kernel

	mov rax, 60  ; system call for exit
	mov rdi, 0   ; exit code
	syscall      ; call kernel

section .data:
	; initialize doubleword msg variable
	msg db 'Hello, world!',0xa,0x0
	

./hello_world
Hello, World!

Calculate length of string

In the previous example, we move the length of the string into the rdx register, which is the length of the string we are printing (including the line feed character, 0xA, and the string terminator, 0x0). However, sometimes we don't know the length of the string we want to print. The next example uses a subroutine loop to calculate the length of a string.

	
; calc_str_len.asm
; Prints calculates the length of the string "Hello, world!" and prints to stdout
; Compile with "nasm -f elf64 calc_str_len.asm && ld calc_str_len.o -o calc_str_len" 

.text:
	global _start

_start:
	mov rax, msg      ; move msg to rax register
	call strlen       ; call strlen subroutine

	mov rax, 1        ; system call for write
	mov rdi, 1        ; file handle for stdout
	syscall           ; call kernel

	mov rax, 60       ; system call for exit
	mov rdi, 0        ; exit code
	syscall           ; call kernel


strlen:
	push rax          ; push rax to stack
	mov rbx, rax      ; move rax value (msg variable) to rbx register

calc_str_len:
	cmp byte [rax], 0 ; check if the pointer of rax equals 0 (string delimeter)
	jz exit_loop      ; jump to "exit_loop" if zero flag has been set
	inc rax           ; increment rax (position along string)
	jmp calc_str_len  ; jump to start of loop

exit_loop:
	sub rax, rbx      ; subtract rbx from rax to equal to length of bytes between them
	mov rdx, rax      ; rax will now equal the length of the string
	pop rsi           ; pop top value on stack to rsi for printing
	ret

.data:
	; initialize doubleword msg variable
	msg db 'Hello, world!',0xa,0x0
	

./calc_str_len
Hello, World!

Using macros to make assembly easier

In the past two examples, I have written all the code in one file, however with a complex language like assembly, this can quickly become hard to manage and keep track of. Assembly also has the ability to create macros, which are functions defined in the .data section, that take a set number of parameters. This provides code which is predictable and easily reusable. I have created a new file called "macros.asm" to keep my macros, and linked it to my next example program with "%include 'macros.asm'.

	
; Collection of Assembly macros to make programming in assembly easier
; Include it with "%include 'macros.asm'

section .data
	; Macro to calculate string length and print to stdout
	%macro printStr 1
		;; Store previous data
		push rax
		push rbx
		push rcx
		push rdx
		push rdi
		push rsi

		;; Move first arg to rax
		mov rax, %1
		;; push rax to stack
		push rax
		;; move 0 to rbx for loop counter
		mov rbx,0
		;; counts letters
		%%printLoop:
			inc rax
			inc rbx
			mov cl,[rax]
			cmp cl,0
			jne %%printLoop
			;; sys_write
			mov rax,1
			mov rdi,1
			pop rsi
			mov rdx,rbx
			syscall

		;; pop values back to registers

		pop rsi
		pop rdi
		pop rdx
		pop rcx
		pop rbx
		pop rax

%endmacro

%macro exit 1
	mov rax,60
	mov rdi,%1
	syscall
%endmacro

	
	
; hello_world_macro.asm
; Prints "Hello, world!" using Assembly macros
; Compile with "nasm -f elf64 hello_world_macro.asm && ld hello_world_macro.o -o hello_world_macro" 

%include 'macros.asm'

.text:
	global _start

_start:
	printStr msg
	exit 0

.data:
	; initialize doubleword msg variable
	msg db 'Hello, world!',0x0
	

./hello_world_macro
Hello, World![root@localhost] # 

Adding line feed and integer printing to macros.asm
	
; Collection of Assembly macros to make programming in assembly easier
; Include it with "%include 'macros.asm'

section .data
	newline db 0xA,0x0

	; Macro to calculate string length and print to stdout
	%macro printStr 1
		;; Store previous data
		push rax
		push rbx
		push rcx
		push rdx
		push rdi
		push rsi

		;; Move first arg to rax
		mov rax, %1
		;; push rax to stack
		push rax
		;; move 0 to rbx for loop counter
		mov rbx,0
		;; counts letters
		%%printLoop:
			inc rax
			inc rbx
			mov cl,[rax]
			cmp cl,0
			jne %%printLoop
			;; sys_write
			mov rax,1
			mov rdi,1
			pop rsi
			mov rdx,rbx
			syscall

		;; pop values back to registers

		pop rsi
		pop rdi
		pop rdx
		pop rcx
		pop rbx
		pop rax

	%endmacro
	
	%macro printStrLF 1
		push rax ; push rax to stack
		mov rax,%1
		printStr rax ; print argument passed
		printStr newline ; print newline
		pop rax
	%endmacro

	%macro printInt 1
		push    rax
		push    rcx
		push    rdx
		push    rsi

		mov rax,%1
		mov     rcx, 0
 
		%%divideLoop:
			inc     rcx
			mov     rdx, 0
			mov     rsi, 10
			idiv    rsi
			add     rdx, 48
			push    rdx
			cmp     rax, 0
			jnz     %%divideLoop
	 
		%%printLoop:
			dec     rcx
			mov     rax, rsp
			printStr rax
			pop     rax
			cmp     rcx, 0
			jnz     %%printLoop
		
		pop     rsi
		pop     rdx
		pop     rcx
		pop     rax

	%endmacro

	%macro printIntLF 1
		push rax
		mov rax, %1
		printInt rax
		printStr newline
		pop rax
	%endmacro

	%macro exit 1
		mov rax,60
		mov rdi,%1
		syscall
	%endmacro

	
FizzBuzz

FizzBuzz is a simple programming challenge where the program should output a series of numbers (usually 1 to 100), however if the number is a multiple of 3 or 5, the program should print out fizz or buzz respectively instead. If a number is a multiple of both 3 and 5, the program should print FizzBuzz.

	
; fizzbuzz.asm
; Prints numbers 1 to 100, however if the number is a multiple of 3 it will print Fizz,
; and if the number is a multiple of 5 it will print Buzz. If the number is a multiple
; of both, it will print FizzBuzz
; Compile with "nasm -f elf64 fizzbuzz.asm && ld fizzbuzz.o -o fizzbuzz" 

%include 'macros.asm'

.text:
	global _start

_start:
	mov rcx,0 ; counter
	mov rsi,0 ; fizz check
	mov rdi,0 ; buzz check

loop:
	inc rcx

check_fizz:
	mov rdx,0
	mov rax,rcx
	mov rbx, 3
	div rbx
	mov rsi,rdx
	cmp rsi,0
	jne check_buzz
	printStr f

check_buzz:
	mov rdx,0
	mov rax,rcx
	mov rbx, 5
	div rbx
	mov rdi,rdx
	cmp rdi,0
	jne check_int
	printStr b

check_int:
	cmp rsi,0
	je cont
	cmp rdi,0
	je cont
	printInt rcx

cont:
	printStr n 
	cmp rcx,99
	jle loop
	exit 0

.data:
	; initialize doubleword msg variable
	f db 'Fizz',0x0
	b db 'Buzz',0x0
	n db 0xA,0x0
	

./fizzbuzz
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
...