Las tablas cercanas de llamada / salto no siempre funcionan en un gestor de arranque

Problema general

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ínimo

El 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.

Expectativas vs resultados reales

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 ASI

Entornos 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

Mis preguntasCuando falla, ¿por qué solo imprime unB ¿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.

Respuestas a la pregunta(1)

Su respuesta a la pregunta