Второй этап загрузчика печатает мусор используя 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-битный регистр.

Другие вопросы по тегам