Introduction to subroutines
Subroutines are functions. They are reusable pieces of code that can be
called by your program to perform various repeatable tasks. Subroutines are
declared using labels just like weve used before eg. start: however
we dont use the JMP instruction to get to them - instead we use a new
instruction CALL. We also dont use the JMP instruction to return to our
program after we have run the function. To return to our program from
a subroutine we use the instruction RET instead. Why dont we JMP to
subroutines?
The great thing about writing a subroutine is that we can reuse it. If we
want to be able to use the subroutine from anywhere in the code we would
have to write some logic to determine where in the code we had jumped from
and where we should jump back to. This would litter our code with unwanted
labels. If we use CALL and RET however, assembly handles this problem for
us using something called the stack. Introduction to the stack
The stack is a special type of memory. Its the same type of memory that
weve used before however its special in how it is used by our program.
The stack is what is call Last In First Out memory LIFO. You can think of
the stack like a stack of plates in your kitchen. The last plate you put on
the stack is also the first plate you will take off the stack next time you
use a plate.
The stack in assembly is not storing plates though, its storing values.
You can store a lot of things on the stack such as variables, addresses
or other programs. We need to use the stack when we call subroutines to
temporarily store values that will be restored later.
Any register that your function needs to use should have its current
value put on the stack for safe keeping using the PUSH instruction. Then
after the function has finished its logic, these registers can have
their original values restored using the POP instruction. This means that
any values in the registers will be the same before and after youve
called your function. If we take care of this in our subroutine we can
call functions without worrying about what changes theyre making to our
registers.
The CALL and RET instructions also use the stack. When you CALL a
subroutine, the address you called it from in your program is pushed onto
the stack. This address is then popped off the stack by RET and the program
jumps back to that place in your code. This is why you should always JMP to
labels but you should CALL functions.
Hello World Program Subroutines Compile with: nasm -f elf
helloworld-len.asm Link with 64 bit systems require elfi386 option: ld
-m elfi386 helloworld-len.o -o helloworld-len Run with: ./helloworld-len
SECTION .data
msg db Hello, brave new world!, 0Ah
SECTION .tex
global start
start:
mov eax, msg move the address of our message string into EAX call
strlen call our function to calculate the length of the string
mov edx, eax our function leaves the result in EAX mov ecx, msg
this is all the same as before mov ebx, 1 mov eax, 4 int 80h
mov ebx, 0 mov eax, 1 int 80h
strlen: this is our first function declaration
push ebx push the value in EBX onto the stack to preserve it while we use
EBX in this function
mov ebx, eax
move the address in EAX into EBX Both point to the same
segment in memory
nextchar: this is the same as lesson3
cmp byte eax, 0
jz finished
inc eax
jmp nextchar
finished:
sub eax, ebx
pop ebx pop the value on the stack back into EBX
ret return to where the function was called
nasm -f elf helloworld-len.asm
ld -m elfi386 helloworld-len.o -o helloworld-len
./helloworld-len
Hello, brave new world!