Близкие таблицы вызовов / переходов не всегда работают в загрузчике

Общая проблема

Я разрабатывал простой загрузчик и столкнулся с проблемой в некоторых средах, где подобные инструкции не работают:

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

Каждый из них связан с косвеннымВЫЗОВ к абсолютным смещениям памяти. Я обнаружил, что у меня есть проблемы, если я использую аналогичныеJMP столы. Относительные вызовы и прыжки, похоже, не затрагиваются. Код как это работает:

call print_char 

Я воспользовался советом, представленным на Stackoverflow, постерами, обсуждающими то, что нужно и чего не стоит писать загрузчик. В частности я видел этоПереполнение стека ответить сОбщие советы по загрузке, Первый совет был:

Когда BIOS переходит к вашему коду, вы не можете положитьсяCS,DS,ES,SS,SP регистры, имеющие действительные или ожидаемые значения. Они должны быть настроены соответствующим образом при запуске вашего загрузчика. Вы можете только гарантировать, что ваш загрузчик будет загружен и запущен с физического адреса 0x07c00 и что номер загрузочного диска загружен вDL регистр.

Принимая все советы, я не полагался наCSЯ установил стек и установилDS быть подходящим дляORG (Исходное смещение) Я использовал. Я создал пример минимальной полной проверки, который демонстрирует проблему. Я построил это, используяNASM, но это, похоже, не является проблемой, специфичной дляNASM.

Минимальный пример

Код для тестирования выглядит следующим образом:

[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

Я строю какISO изображение и образ дискеты 1,44 МБ для тестовых целей. Я использую среду Debian Jessie, но большинство дистрибутивов Linux будут похожи:

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

Я получаю образ дискеты под названиемfloppy.img иISO изображение называетсяmyos.iso.

Ожидания против фактических результатов

В большинстве случаев этот код работает, но в ряде сред это не так. Когда это работает, он просто печатает это на дисплее:

BMMME

Я распечатываюB используя типичныйВЫЗОВ с относительным смещением, кажется, работает нормально. В некоторых средах, когда я запускаю код, я просто получаю:

B

А потом, кажется, просто перестает что-либо делать. Кажется, распечататьB правильно, но потом происходит нечто неожиданное.

Среды, которые, кажется, работают:

QEMU загрузился с дискеты и ISOVirtualBox загрузился с дискеты и ISOVMWare 9 загрузился с дискеты и ISODosBox загрузился с дискетыОфициально упакованBochs(2.6) в Debian Jessie с использованием образа дискетыBochs 2.6.6 (собран из системы контроля версий) в Debian Jessie с использованием образа дискеты иISO образСистема AST Premmia SMP P90 с середины 90-х годов с использованием дискет иISO

Среды, которые не работают должным образом:

Официально упакованBochs(2.6) в Debian Jessie используяISO образСистема на базе 486DX с AMI BIOS начала 90-х, использующая образ дискеты. Компакт-диски не загружаются в этой системе, поэтому их невозможно протестировать.

Что я нахожу интересным, так это то, чтоBochs (версия 2.6) не работает должным образом на Debian Jessie с использованиемISO, Когда я загружаюсь с дискеты с той же версией, она работает как положено.

Во всех случаяхISO и образ дискеты, казалось, загружается и начинает работать, так как вВСЕ случаи, по крайней мере, был в состоянии распечататьB на дисплее.

Мои вопросыКогда это терпит неудачу, почему это только распечатываетB и больше ничего?Почему некоторые среды работают, а другие - нет?Это ошибка в моем коде или аппаратном / BIOS?Как я могу исправить это так, чтобы я все еще мог использовать почти непрямые таблицы Jump и Call для абсолютных смещений памяти? Я знаю, что могу полностью избежать этих инструкций, и это, кажется, решает мою проблему, но я хотел бы иметь возможность понять, как и если я могу правильно использовать их в загрузчике.

Ответы на вопрос(1)

Ваш ответ на вопрос