BIOS int 10h печать мусора на QEMU
У меня проблема при написании программы сборки реального режима x86, которая запускается как загрузчик в QEMU. Я пытаюсь напечатать текст через прерывание BIOS 0x10. Мой код:
print:
pusha
.loop:
mov AL, [SI]
cmp AL, 0
je .end
call printChar
inc SI
jmp .loop
.end:
popa
ret
printChar:
pusha
mov AH, 0x0E
mov BH, 0
mov BL, 0x0F
int 0x10
popa
ret
я использую [ORG 0x7c00]
в качестве отправной точки. Я проверил этикетку printChar и назвал ее буквой AL, и она отлично работает. Когда я пытаюсь загрузить адрес памяти для такого сообщения:
loadMsg db "Loading",0
mov SI, loadMessage
call print
Я получаю мусор как 'U' в качестве вывода на эмулятор QEMU. Вчера я написал код, очень похожий на этот, и у меня нет проблем. В чем причина моей проблемы и как ее можно исправить?
2 ответа
Недавно я написал несколько советов по общему загрузчику в ответе Stackru, которые могут быть вам полезны. Вероятный совет № 1 относится к вашей проблеме:
Когда BIOS переходит к вашему коду, вы не можете полагаться на регистры CS,DS,ES,SS,SP, имеющие действительные или ожидаемые значения. Они должны быть настроены соответствующим образом при запуске вашего загрузчика. Вы можете только гарантировать, что ваш загрузчик будет загружен и запущен с физического адреса 0x00007c00 и что номер загрузочного диска загружен в регистр DL.
На основании того факта, что printChar работает и что запись всей строки не означает, что DS: SI не указывает на правильное место в памяти, где находится ваша строка. Обычная причина этого заключается в том, что разработчики ошибочно полагают, что регистр CS и / или DS установлен соответствующим образом, когда BIOS переходит на загрузчик. Это должно быть установлено вручную. В случае исходной точки 0x7c00 DS должен быть установлен на 0. В 16-битном реальном режиме адреса физической памяти вычисляются из пар сегмент: смещение по формуле (segment<<4)+offset
, В вашем случае вы используете смещение 0x7C00. Значение в DS, равное 0, приведет к правильному физическому адресу (0<<4)+0x7c00 = 0x07c00 .
Вы можете установить DS в 0 в начале вашей программы, например:
xor ax, ax ; set AX to zero
mov ds, ax ; DS = 0
В случае QEMU, BIOS переходит к 0x07c0:0x0000 . Это также представляет ту же область физической памяти (0x07c0<<4)+0 = 0x07c00 . Такой переход установит CS = 0x07c0 (не CS = 0). Поскольку существует много пар сегментов: смещений, которые отображаются в одну и ту же ячейку физической памяти, необходимо правильно настроить DS. Вы не можете полагаться на то, что CS - это та ценность, которую вы ожидаете. Так что в QEMU подобный код даже не будет правильно устанавливать DS (при использовании ORG 0x7c00
):
mov ax, cs
mov ds, ax ; Make DS=CS
Это может работать на некоторых эмуляторах, таких как DOSBOX и на некоторых физических устройствах, но не на всех. Среда, в которой этот код будет работать, - это когда BIOS переходит к 0x0000:0x7c00 . В этом случае CS будет нулевым, когда он достигнет кода вашего загрузчика, и копирование CS в DS будет работать. Не думайте, что CS будет нулевым во всех средах - это главное, что я делаю. Всегда устанавливайте регистры сегмента так, как вам нужно.
Пример кода, который должен работать:
BITS 16
ORG 0x7c00
GLOBAL main
main:
xor ax, ax ; AX = 0
mov ds, ax ; DS = 0
mov bx, 0x7c00
cli ; Turn off interrupts for SS:SP update
; to avoid a problem with buggy 8088 CPUs
mov ss, ax ; SS = 0x0000
mov sp, bx ; SP = 0x7c00
; We'll set the stack starting just below
; where the bootloader is at 0x0:0x7c00. The
; stack can be placed anywhere in usable and
; unused RAM.
sti ; Turn interrupts back on
mov SI, loadMsg
call print
cli
.endloop:
hlt
jmp .endloop ; When finished effectively put our bootloader
; in endless cycle
print:
pusha
.loop:
mov AL, [SI] ; No segment on [SI] means implicit DS:[SI]
cmp AL, 0
je .end
call printChar
inc SI
jmp .loop
.end:
popa
ret
printChar:
pusha
mov AH, 0x0E
mov BH, 0
mov BL, 0x0F
int 0x10
popa
ret
; Place the Data after our code
loadMsg db "Loading",0
times 510 - ($ - $$) db 0 ; padding with 0 at the end
dw 0xAA55 ; PC boot signature
Это проблема:
loadMsg db "Loading",0
mov SI, loadMessage
call print
Если программа не перепрыгивает через "загрузочный" текст, она выполняет любые инструкции, которые эти байты могут (или не могут) представлять. Нечто подобное исправляет это:
jmp print_msg
loadMsg db "Loading",0
print_msg:
mov SI, loadMessage
call print