Второй этап загрузчика печатает мусор используя Int 0x10/ah=0x0e
Я пытаюсь изучить ассемблер и написать загрузчик. Следующий код загружает содержимое дисковода гибких дисков в память и переходит к нему (начинает загрузку по адресу 0x1000). Этот код должен печатать "X" на экране, но по какой-то причине он печатает пробел. Может кто-нибудь, пожалуйста, скажите мне, что не так?
[bits 16]
jmp reset
reset: ;Resets floppy drive
xor ax,ax ;0 = Reset floppy disk
mov dl,0 ;Drive 0 is floppy
int 0x13
jc reset ;If carry flag was set, try again
mov ax,0x1000 ;When we read the sector, we are going to read address 0x1000
mov es,ax ;Set ES with 0x1000
floppy:
mov ah,0x2 ;2 = Read floppy
mov al,0x11 ;Reading one sector
mov ch,0x0 ;Track 1
mov cl,0x2 ;Sector 2, track 1
mov dh,0x0 ;Head 1
mov dl,0x0 ;Drive = 0 (Floppy)
int 0x13
jc floppy ;If carry flag was set, try again
jmp 0x1000:0000 ;Jump to 0x1000, start of second program
times 510 - ($ - $$) db 0 ;Fill the rest of sector with 0
dw 0xAA55 ;This is the boot signiture
;---
;--[segment 2]--
mov bx, var
mov ah, 0x0e
mov al, [bx]
int 0x10
jmp $
var:
db 'X'
times 737280 - ($ - $$) db 0
1 ответ
Я могу сделать вывод, что вы используете NASM (или NASM- совместимый) ассемблер. Я не знаю, какую ОС вы используете для сборки загрузчика, но я буду использовать Linux или Windows. Другие среды будут несколько похожи.
Вы должны разделить ваш загрузчик на две части, чтобы сделать это проще. Одним из них является загрузчик, а второй является вторым этапом, который вы загружаете в 0x1000:0x0000. Это позволяет нам правильно определить точку начала нашего загрузчика. Ожидается, что загрузчик будет загружен по физическому адресу 0x07c00, а второй этап - по адресу 0x10000 ((0x1000<< 4 +) + 0). Нам нужен ассемблер для правильной генерации адресов для наших данных и кода.
Я написал несколько ответов на Stackru, в которых описываются некоторые изменения, внесенные в код. Несколько наиболее важных из них:
- Общие советы по загрузчику, которые дают общие рекомендации и предположения, которые вы не хотите делать в загрузчике
- Информация о подводных камнях неправильной настройки DS и получения мусора при доступе к переменным памяти. Это относится в некоторой степени к вашей второй стадии
Если у вас нет правильного понимания пары сегмент: смещение, я рекомендую эту статью. Я поднимаю это, потому что в вашем вопросе и в вашем коде, кажется, есть путаница. Вы, кажется, думаете, что адрес физической памяти 0x1000 совпадает с парой сегмент: смещение 0x1000:0x0000. В своем вопросе вы говорите:
Следующий код загружает содержимое дисковода гибких дисков в память и переходит к нему (начинает загрузку по адресу 0x1000).
В вашем коде у вас есть эта строка и комментарий:
jmp 0x1000:0000 ;Jump to 0x1000, start of second program
Если вы просмотрите эту ссылку, вы обнаружите, что сегмент: смещение вычисляется по физическому адресу, сдвигая сегмент влево на 4 бита (умножьте на 16 десятичных знаков), а затем добавив смещение. Уравнение обычно выглядит как (сегмент<<4)+ смещение. В вашем случае 0x1000:0x0000 - это сегмент 0x1000 и смещение 0x0000. Используя уравнение для получения физического адреса в памяти, вы получите (0x1000<<4)+0x0000 = 0x10000 (не 0x1000)
Из вашего кода невозможно сказать, как вы собираете с NASM. Я привожу пример того, как это можно сделать, но важной частью является разделение загрузчика. Предположим, мы поместили ваш загрузчик в файл с именем bootload.asm
:
[bits 16]
[ORG 0x7c00] ; Bootloader starts at physical address 0x07c00
; BIOS sets DL to boot drive before jumping to the bootloader
; Since we specified an ORG(offset) of 0x7c00 we should make sure that
; Data Segment (DS) is set accordingly. The DS:Offset that would work
; in this case is DS=0 . That would map to segment:offset 0x0000:0x7c00
; which is physical memory address (0x0000<<4)+0x7c00 . We can't rely on
; DS being set to what we expect upon jumping to our code so we set it
; explicitly
xor ax, ax
mov ds, ax ; DS=0
cli ; Turn off interrupts for SS:SP update
; to avoid a problem with buggy 8088 CPUs
mov ss, ax ; SS = 0x0000
mov sp, 0x7c00 ; 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
reset: ; Resets floppy drive
xor ax,ax ; 0 = Reset floppy disk
int 0x13
jc reset ; If carry flag was set, try again
mov ax,0x1000 ; When we read the sector, we are going to read address 0x1000
mov es,ax ; Set ES with 0x1000
floppy:
xor bx,bx ;Ensure that the buffer offset is 0!
mov ah,0x2 ;2 = Read floppy
mov al,0x1 ;Reading one sector
mov ch,0x0 ;Track 1
mov cl,0x2 ;Sector 2, track 1
mov dh,0x0 ;Head 1
int 0x13
jc floppy ;If carry flag was set, try again
jmp 0x1000:0000 ;Jump to 0x1000, start of second program
times 510 - ($ - $$) db 0 ;Fill the rest of sector with 0
dw 0xAA55 ;This is the boot signature
Вы должны заметить, что я удалил эту строку:
mov dl,0x0 ;Drive = 0 (Floppy)
Это жесткий код загрузочного диска на дискету A:. Если вы загружаетесь с USB, жесткого диска или дискеты B: ваш код не будет работать, потому что в этих случаях номер диска, скорее всего, не будет нулевым. BIOS передает фактический загрузочный диск, который использовался для загрузки вашего загрузчика. Это значение находится в регистре DL. Это значение, которое вы должны использовать для функций диска BIOS. Поскольку DL уже содержит загрузочный диск, мы используем его как есть.
Второй этап может быть изменен таким образом. Я предполагаю, что файл называется stage2.asm
:
[BITS 16]
[ORG 0x0000] ; This code is intended to be loaded starting at 0x1000:0x0000
; Which is physical address 0x10000. ORG represents the offset
; from the beginning of our segment.
; Our bootloader jumped to 0x1000:0x0000 which sets CS=0x1000 and IP=0x0000
; We need to manually set the DS register so it can properly find our variables
; like 'var'
mov ax, cs
mov ds, ax ; Copy CS to DS (we can't do it directly so we use AX temporarily)
mov bx, var
mov ah, 0x0e
mov al, [bx]
xor bh, bh ; BH = 0 = Display on text mode page 0
int 0x10
jmp $
var:
db 'X'
Я не пытался оптимизировать ваш код. Идея состоит в том, чтобы показать, как добавить клей, чтобы исправить ваши проблемы. Оба файла указывают точку начала, используя ORG
директивы. Загрузчики должны быть собраны так, чтобы они работали по адресу памяти 0x07c00 . Вы загружаете второй этап по адресу 0x1000:0x0000, который отображается на физический адрес 0x10000. Мы установили ORG на 0x0000, так как FAR JUMP jmp 0x1000:0000
установит CS= 0x1000, а IP= 0x0000. Поскольку IP равен 0x0000, мы хотим, чтобы ORG соответствовал ему, чтобы ссылки на близкую память относились к началу нашего сегмента 64 КБ.
Это позволит ассемблеру генерировать правильные ссылки на память для ваших переменных и кода. Поскольку вы не сделали этого должным образом в своем коде, на втором этапе вы читали неправильную область памяти для var
и впоследствии отображал неправильный символ.
После разделения двух файлов вам необходимо собрать их с помощью NASM, а затем поместить их в образ диска. В отличие от вашего вопроса, я буду использовать DD для создания образа дискеты 720k, а затем поместить начальный загрузчик в начало (без усечения диска), а затем расположить второй этап, начиная с сектора сразу после него. Это может быть достигнуто следующим образом:
# Assemble both components as binary images with NASM
nasm -f bin bootload.asm -o bootload.bin
nasm -f bin stage2.asm -o stage2.bin
# Create a 720k disk image
dd if=/dev/zero of=disk.img bs=1024 count=720
# Place bootload.bin at the beginning of disk.img without truncating
dd if=bootload.bin of=disk.img conv=notrunc
# Place stage2.bin starting at the second 512byte sector and write
# it without truncating the disk image. bs=512 seek=1 will skip the
# first 512 byte sector and start writing stage2.bin there.
dd if=stage2.bin of=disk.img bs=512 seek=1 conv=notrunc
Вы можете запустить такой образ, используя QEMU, с чем-то вроде:
qemu-system-i386 -fda disk.img
Если вы используете Windows, и у вас нет доступа к DD, вы можете использовать эту модификацию для stage2.asm
:
[BITS 16]
[ORG 0x0000] ; This code is intended to be loaded starting at 0x1000:0x0000
; Which is physical address 0x10000. ORG represents the offset
; from the beginning of our segment.
; Our bootloader jumped to 0x1000:0x0000 which sets CS=0x1000 and IP=0x0000
; We need to manually set the DS register so it can properly find our variables
; like 'var'
mov ax, cs
mov ds, ax ; Copy CS to DS (we can't do it directly so we use AX temporarily)
mov bx, var
mov ah, 0x0e
mov al, [bx]
xor bh, bh ; BH = 0 = Display on text mode page 0
int 0x10
jmp $
var:
db 'X'
; Extend the second stage to (720K - 512 bytes)
; bootload.bin will take up first 512 bytes
times 737280 - 512 - ($ - $$) db 0
А затем соберите и создайте образ диска 720K с помощью этих команд:
nasm -f bin bootload.asm -o bootload.bin
nasm -f bin stage2.asm -o stage2.bin
copy /b bootload.bin+stage2.bin disk.img
disk.img
будет образ диска 720K, который должен использоваться QEMU или Bochs. Окончательный размер disk.img
должно быть 737 280 байт.
Если вы хотите переместить значение из адреса памяти в регистр, вы можете сделать это напрямую без промежуточного регистра. В вашем stage2.asm
у вас есть это:
mov bx, var
mov ah, 0x0e
mov al, [bx]
Это может быть записано как:
mov ah, 0x0e
mov al, [var]
Это переместит один байт из области памяти var и переместит его непосредственно в AL. NASM определяет размер в байтах, поскольку AL назначения - это 8-битный регистр.