Las tablas cercanas de llamada / salto no siempre funcionan en un gestor de arranque
He estado desarrollando un simple gestor de arranque y me he encontrado con un problema en algunos entornos donde las instrucciones como estas no funcionan:
mov si, call_tbl ; SI=Call table pointer
call [call_tbl] ; Call print_char using near indirect absolute call
; via memory operand
call [ds:call_tbl] ; Call print_char using near indirect absolute call
; via memory operand w/segment override
call near [si] ; Call print_char using near indirect absolute call
; via register
Cada uno de estos implica casi indirectosLLAMADA a compensaciones absolutas de memoria. He descubierto que tengo problemas si uso similaresJMP mesas. Las llamadas y saltos que son relativos no parecen verse afectados. Código como este funciona:
call print_char
He tomado el consejo presentado en Stackoverflow por carteles que discuten lo que se debe y no se debe hacer al escribir un gestor de arranque. En particular vi estoDesbordamiento de pila responde conConsejos generales para el gestor de arranque. El primer consejo fue:
Cuando el BIOS salta a su código, no puede confiarCS,DS,ES,SS,SP registros con valores válidos o esperados. Deben configurarse adecuadamente cuando se inicia el gestor de arranque. Solo puede garantizarse que su gestor de arranque se cargará y ejecutará desde la dirección física 0x07c00 y que el número de la unidad de arranque se cargará en elDL registro.Tomando todos los consejos, no confié enCS, Configuré una pila y configuréDS ser apropiado para elORG (Desplazamiento de origen) solía. He creado un ejemplo mínimo completo verificable que demuestra el problema. Construí esto usandoNASM, pero no parece ser un problema específico paraNASM.
Ejemplo mínimoEl código para probar es el siguiente:
[ORG 0x7c00]
[Bits 16]
section .text
main:
xor ax, ax
mov ds, ax ; DS=0x0000 since OFFSET=0x7c00
cli ; Turn off interrupts for potentially buggy 8088
mov ss, ax
mov sp, 0x7c00 ; SS:SP = Stack just below 0x7c00
sti ; Turn interrupts back on
mov si, call_tbl ; SI=Call table pointer
mov al, [char_arr] ; First char to print 'B' (beginning)
call print_char ; Call print_char directly (relative jump)
mov al, [char_arr+1] ; Character to print 'M' (middle)
call [call_tbl] ; Call print_char using near indirect absolute call
; via memory operand
call [ds:call_tbl] ; Call print_char using near indirect absolute call
; via memory operand w/segment override
call near [si] ; Call print_char using near indirect absolute call
; via register
mov al, [char_arr+2] ; Third char to print 'E' (end)
call print_char ; Call print_char directly (relative jump)
end:
cli
.endloop:
hlt ; Halt processor
jmp .endloop
print_char:
mov ah, 0x0e ; Write CHAR/Attrib as TTY
mov bx, 0x00 ; Page 0
int 0x10
retn
; Near call address table with one entry
call_tbl: dw print_char
; Simple array of characters
char_arr: db 'BME'
; Bootsector padding
times 510-($-$) db 0
dw 0xAA55
Yo construyo ambosYO ASI imagen y una imagen de disquete de 1,44 MB para fines de prueba. Estoy usando un entorno Debian Jessie pero la mayoría de las distribuciones de Linux serían similares:
nasm -f bin boot.asm -o boot.bin
dd if=/dev/zero of=floppy.img bs=1024 count=1440
dd if=boot.bin of=floppy.img conv=notrunc
mkdir iso
cp floppy.img iso/
genisoimage -quiet -V 'MYBOOT' -input-charset iso8859-1 -o myos.iso -b floppy.img -hide floppy.img iso
Termino con una imagen de disquete llamadafloppy.img
y unYO ASI imagen llamadamyos.iso
.
En la mayoría de las condiciones, este código funciona, pero en varios entornos no lo hace. Cuando funciona, simplemente imprime esto en la pantalla:
BMMME
ImprimoB
usando un típicoLLAMADA con un desplazamiento relativo parece funcionar bien. En algunos entornos, cuando ejecuto el código, acabo de recibir:
B
Y luego parece que simplemente deja de hacer cualquier cosa. Parece imprimir elB
correctamente, pero luego sucede algo inesperado.
Ambientes que parecen funcionar:
QEMU arrancado con disquete e ISOVirtualBox arrancado con disquete e ISOVMWare 9 arrancado con disquete e ISODosBox arrancado con disqueteEmbalado oficialmenteBochs(2.6) en Debian Jessie usando imagen de disqueteBochs 2.6.6 (construido a partir del control de código fuente) en Debian Jessie usando una imagen de disquete yYO ASI imagenSistema AST Premmia SMP P90 de mediados de los 90 con disquete yYO ASIEntornos que no funcionan como se esperaba:
Embalado oficialmenteBochs(2.6) en Debian Jessie usandoYO ASI imagenSistema basado en 486DX con BIOS AMI de principios de los 90 que usa la imagen de disquete. Los CD no se iniciarán en este sistema, por lo que no se pudo probar el CD.Lo que me parece interesante es queBochs (versión 2.6) no funciona como se esperaba en Debian Jessie usando unYO ASI. Cuando inicio desde el disquete con la misma versión, funciona como se esperaba.
En todos los casos elYO ASI y la imagen del disquete parecía cargarse y comenzar a ejecutarse desdeTODAS casos fue al menos capaz de imprimirB
en la pantalla
B
¿y nada más?¿Por qué algunos entornos funcionan y otros fallan?¿Es esto un error en mi código o en el hardware / BIOS?¿Cómo puedo solucionarlo para poder seguir usando tablas de salto y llamada casi indirectas para compensaciones de memoria absolutas? Soy consciente de que puedo evitar estas instrucciones por completo y eso parece resolver mi problema, pero me gustaría poder entender cómo y si puedo usarlas correctamente en un gestor de arranque.