Desde el punto de vista de la CPU una función no es mas que un salto a una dirección de memoria donde se encuentran las intrucciones de una rutina y un salto de retorno a la instrucción inmediatamente posterior al primer salto. En este artÃculo explicaré como se deben de llamar a las funciones para que sean reutilizables y que sea seguro su uso.
Antes de comenzar es recomendable que leas estos artÃculos anteriores:
En este ejemplo utilizaremos el registro al a modo de parámetro de nuestras función 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
Generamos la imagen:
La cargamos en 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
El problema de esta aproximación es que la función no es reutilizable ya que la dirección de retorno está hardcodeada, si llamásemos a la función desde otro punto del código el punto de retorno de la función serÃa incorrecto. Pero y si pudiésemos almacenar la posición donde se está ejecutando el código justo antes de llamar a la función, ejecutarla y volver a esa punto guardado?
Esto es justo lo que hacen las instrucciones call/ret:
- Guarda el ip(Instruction pointer) en la pila -> push ip
- Salta a la función
- Ejecuta la función
- Recupera el ip de la pila -> pop ip
- Salta al ip
De este modo la dirección de retorno de la función es correcta y la función por lo tanto reutilizable en cualquier punto del código, veamos un ejemplo:
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
Generamos la imagen:
La cargamos en 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
Hemos llamado dos veces a la misma función sin problema.
Cuando se llama a una función esta puede modificar el contenido de los registros para realizar las tareas programadas por lo tanto no es seguro seguir la ejecución del hilo principal del programa confiando en que los registros sigan con los valores correctos, para solventar esto existen dos intrucciones que lo que hacen es “pushear/popear” todos los registros en la pila, estas instrucciones son pusha/popa.
El ejemplo anterior adaptado quedarÃa del siguiente modo:
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
Generamos la imagen:
La cargamos en 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
Una vez mas hemos utilizado la función dos veces pero esta vez de forma segura ya que cuando se ha retornado al hilo principal del programa los registros estaban exactamente en el mismo estado que antes de llamar a la función.
Ahora que ya tenemos claras las funciones vamos a crear un programa que imprimirá por pantalla la cadena que le indiquemos, pero para ello debemos terminar la cadena con el carácter 0x00 (null byte).
[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
Generamos la imagen:
La cargamos en 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
Las funciones se pueden programar en un fichero externo para luego ser importado, nosotros vamos a sacar nuestra función print y además añadimos una nueva, 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 ----
Repetimos los prints pero esta vez importando las funciones de la librerÃa externa.
[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
Generamos la imagen:
La cargamos en 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