From the CPU’s point of view, a function is nothing more than a jump to a memory address where the instructions of a routine are located and a return jump to the instruction immediately following the first jump. In this article, I will explain how functions should be called to make them reusable and safe to use.
Before we begin, it is recommended that you read these previous articles:
In this example, we will use the al register as a parameter for our function my_print_function.
mov al, 'H'
jmp my_print_function
return_to_here:
jmp the_end
my_print_function:
mov ah, 0x0e ; tty mode
int 0x10 ; print the character in al
jmp return_to_here ; return from the function call
the_end:
jmp $
times 510-($-$$) db 0
dw 0xaa55
We generate the image:
We load it into qemu:
SeaBIOS (version rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org)
iPXE (http://ipxe.org) 00:03.0 C980 PCI2.10 PnP PMM+07F91410+07EF1410 C980
Booting from Hard Disk...
H
The problem with this approach is that the function is not reusable since the return address is hardcoded. If we called the function from another point in the code, the return point of the function would be incorrect. But what if we could store the position where the code is being executed just before calling the function, execute it, and return to that saved point?
This is precisely what the call/ret instructions do:
- Save the ip(Instruction pointer) on the stack -> push ip
- Jump to the function
- Execute the function
- Retrieve the ip from the stack -> pop ip
- Jump to the ip
In this way, the return address of the function is correct and the function is therefore reusable at any point in the code. Let’s see an example:
mov al, 'H'
call my_print_function
call my_print_function
jmp the_end
my_print_function:
mov ah, 0x0e ; tty mode
int 0x10 ; print the character in al
ret
the_end:
jmp $
times 510-($-$$) db 0
dw 0xaa55
We generate the image:
We load it in qemu:
qemu-system-x86_64 boot_sect_function2.bin
SeaBIOS (version rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org)
iPXE (http://ipxe.org) 00:03.0 C980 PCI2.10 PnP PMM+07F91410+07EF1410 C980
Booting from Hard Disk...
HH
We have called the same function twice without any problem.
When a function is called, it can modify the contents of the registers to perform the tasks programmed, so it is not safe to continue the execution of the main thread of the program relying on the registers to have the correct values. To solve this, there are two instructions that “push/pop” all the registers onto the stack. These instructions are pusha/popa.
The previous example adapted would be as follows:
mov bp, 0x8000 ; this is an address far away from 0x7c00 so that we don't get overwritten
mov sp, bp ; if the stack is empty then sp points to bp
mov al, 'H'
call my_print_function
call my_print_function
jmp the_end
my_print_function:
pusha ; push all registers to stack
mov ah, 0x0e ; tty mode
int 0x10 ; print the character in al
popa ; pop all registers from stack
ret
the_end:
jmp $
times 510-($-$$) db 0
dw 0xaa55
We generate the image:
We load it in qemu:
SeaBIOS (version rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org)
iPXE (http://ipxe.org) 00:03.0 C980 PCI2.10 PnP PMM+07F91410+07EF1410 C980
Booting from Hard Disk...
HH
Once again, we have used the function twice, but this time safely, since when we returned to the main thread of the program, the registers were exactly in the same state as before calling the function.
Now that we have clear functions, let’s create a program that will print on the screen the string we indicate, but for this we must end the string with the 0x00 (null byte) character.
[org 0x7c00]
; ---- main ----
; The main routine makes sure the parameters are ready and then calls the function
mov bp, 0x8000 ; this is an address far away from 0x7c00 so that we don't get overwritten
mov sp, bp ; if the stack is empty then sp points to bp
mov bx, HELLO; set memory address value of HELLO string to bx register
call print
mov bx, GOODBYE; set memory address value of GOODBYE string to bx register
call print
jmp $
; ---- main ----
; ---- print function ----
print:
pusha
; keep this in mind:
; while (string[i] != 0) { print string[i]; i++ }
; the comparison for string end (null byte)
start:
mov al, [bx] ; 'bx' is the base address for the string
cmp al, 0
je done
mov ah, 0x0e; tty mode
int 0x10 ; print char
; increment pointer to next byte and do next loop
add bx, 1
jmp start
done:
popa
ret
; ---- print function ----
; ---- data ----
HELLO:
db 'Hello, World', 0
GOODBYE:
db 'Goodbye', 0
; ---- data ----
; padding and magic number
times 510-($-$$) db 0
dw 0xaa55
We generate the image:
We load it in qemu:
SeaBIOS (version rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org)
iPXE (http://ipxe.org) 00:03.0 C980 PCI2.10 PnP PMM+07F91410+07EF1410 C980
Booting from Hard Disk...
Hello, WorldGoodbye
Functions can be programmed in an external file to be later imported. We will extract our print function and add a new one, print_nl.
; ---- print function ----
print:
pusha
; keep this in mind:
; while (string[i] != 0) { print string[i]; i++ }
; the comparison for string end (null byte)
start:
mov al, [bx] ; 'bx' is the base address for the current char
cmp al, 0
je done
mov ah, 0x0e; tty mode
int 0x10 ; print char
; increment pointer to next byte and do next loop
add bx, 1
jmp start
done:
popa
ret
; ---- print function ----
; ---- print_nl function ----
print_nl:
pusha
mov ah, 0x0e
mov al, 0x0a ; newline char
int 0x10
mov al, 0x0d ; carriage return
int 0x10
popa
ret
; ---- print_nl function ----
We repeat the prints, but this time importing the functions from the external library.
[org 0x7c00]
mov bp, 0x8000 ; this is an address far away from 0x7c00 so that we don't get overwritten
mov sp, bp ; if the stack is empty then sp points to bp
mov bx, HELLO; set memory address value of HELLO string to bx register
call print
call print_nl
mov bx, GOODBYE; set memory address value of GOODBYE string to bx register
call print
jmp $
%include "./boot_sect_print.asm"
HELLO:
db 'Hello, World', 0
GOODBYE:
db 'Goodbye', 0
; padding and magic number
times 510-($-$$) db 0
dw 0xaa55
We generate the image:
We load it into Qemu:
SeaBIOS (version rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org)
iPXE (http://ipxe.org) 00:03.0 C980 PCI2.10 PnP PMM+07F91410+07EF1410 C980
Booting from Hard Disk...
Hello, World
Goodbye