As tabelas Near Call / Jump nem sempre funcionam em um gerenciador de inicialização

Problema Geral

Estou desenvolvendo um gerenciador de inicialização simples e deparei com um problema em alguns ambientes em que instruções como estas não funcionam:

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 um deles envolve envolvimento indiretoLIGAR compensações absolutas de memória. Descobri que tenho problemas se usar itens semelhantesJMP mesas. As chamadas e saltos que são relativos não parecem ser afetados. Código como este funciona:

call print_char 

Eu segui os conselhos apresentados no Stackoverflow por pôsteres discutindo os prós e contras de escrever um gerenciador de inicialização. Em particular, eu vi issoStackoverflow responda comDicas gerais sobre o carregador de inicialização. A primeira dica foi:

Quando o BIOS salta para o seu código, você não pode confiarCS,DS,ES,SS,SP registradores com valores válidos ou esperados. Eles devem ser configurados adequadamente quando o seu carregador de inicialização é iniciado. Você só pode garantir que o seu carregador de inicialização seja carregado e executado a partir do endereço físico 0x07c00 e que o número da unidade de inicialização seja carregado no diretórioDL registo.

Seguindo todos os conselhos, não confiei emCS, Montei uma pilha e configureiDS apropriado para oORG (Deslocamento de origem) eu usei. Eu criei um exemplo mínimo verificável completo que demonstra o problema. Eu construí isso usandoNASM, mas não parece ser um problema específico paraNASM.

Exemplo Mínimo

O código a ser testado é o seguinte:

[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

Eu construo umISO imagem e uma imagem de disquete de 1,44 MB para fins de teste. Estou usando um ambiente Debian Jessie, mas a maioria das distribuições Linux seria semelhante:

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

Acabo com uma imagem de disquete chamadafloppy.img e umISO imagem chamadamyos.iso.

Expectativas vs resultados reais

Na maioria das condições, esse código funciona, mas em vários ambientes não funciona. Quando funciona, simplesmente imprime isso no visor:

BMMME

Eu imprimoB usando um típicoLIGAR com deslocamento relativo, parece funcionar bem. Em alguns ambientes, quando executo o código, apenas recebo:

B

E então parece simplesmente parar de fazer qualquer coisa. Parece imprimir oB corretamente, mas então algo inesperado acontece.

Ambientes que parecem funcionar:

QEMU inicializado com disquete e ISOVirtualBox inicializado com disquete e ISOVMWare 9 inicializado com disquete e ISODosBox inicializado com disqueteEmbalado oficialmenteBochs(2.6) no Debian Jessie usando imagem de disqueteBochs 2.6.6 (construído a partir do controle de origem) no Debian Jessie usando imagem de disquete eISO imagemSistema AST Premmia SMP P90 de meados dos anos 90 usando disquete eISO

Ambientes que não funcionam conforme o esperado:

Embalado oficialmenteBochs(2.6) no Debian Jessie usandoISO imagemSistema baseado em 486DX com BIOS AMI do início dos anos 90 usando imagem de disquete. Os CDs não inicializam neste sistema, portanto não foi possível testá-lo.

O que eu acho interessante é queBochs (versão 2.6) não funciona como esperado no Debian Jessie usando umISO. Quando eu inicializo a partir do disquete com a mesma versão, ele funciona conforme o esperado.

Em todos os casos, oISO e a imagem do disquete parecia carregar e começar a rodar desdeTUDO casos, pelo menos, foi capaz de imprimirB no visor.

Minhas perguntasQuando falha, por que apenas imprime umB e nada mais?Por que alguns ambientes funcionam e outros falham?Isso é um bug no meu código ou no hardware / BIOS?Como posso corrigi-lo para que eu ainda possa usar tabelas Jump e Call indiretas próximas para compensações absolutas de memória? Estou ciente de que posso evitar essas instruções completamente e isso parece resolver meu problema, mas gostaria de entender como e se posso usá-las adequadamente em um gerenciador de inicialização.

questionAnswers(1)

yourAnswerToTheQuestion