Как загружать/считывать секторы из памяти?
Пишу свою ОС. Сейчас разбираюсь как загружать/считывать секторы из памяти.
Опищу ситуацию: заполнил два сектора (по 512 байт) после загрузочного 0xDADA и 0xFACE соотвестственно, считываю их с помощью 0x13 прерывания по адресу 0x9000 (функция disk_load). После чего хочу убедиться в правильности считывания: вывожу первое слово по адресу 0x9000 и первое слово по адресу (0x9000 + 512). Ожидаю увидеть 0xDADA и 0xFACE соответственно, но вижу следущее:
0xDADA
0xZRVW
Была мысль о том, что возможно это прерывание не может за раз несколько секторов считать, но гугл говорит, что может. Возможно проблема в том, что при выводе слов надо явно указать сегментный регистр, но это тоже не работает. Прошу, объясните, что не так. Код прилагаю. Использую эмулятор Bochs и ассемблер NASM.
Главная функция boot_sect.asm :
[org 0x7c00]
mov [BOOT_DRIVE], dl; Запомимаем на будушее наш загрузочный диск(Зачем это нужно?)
mov bp, 0x8000 ; Убираем стек с пути
mov sp, bp ; SP - вершина стека, BP - база стека - нижняя граница
mov bx, 0x9000 ; ES:BX = 0x0000:0x9000
mov dh, 2; Считываем два сектора
mov dl, [BOOT_DRIVE] ; ?
call disk_load
mov dx, [0x9000]; Выводим первое машинное слово первого считанного сектора. Ожидаем: 0xDADA
call print_hex
call print_nl
mov dx, [0x9000 + 512]; Выводим первое машинное слово второго считанного сектора. Ожидаем: 0xFACE
call print_hex
jmp $
;Includes
%include "functions/print/print_string.asm"
%include "functions/print/print_hex.asm"
%include "functions/disk/disk_load.asm"
; Global variables
BOOT_DRIVE: db 0
; Padding and magic number
times 510-($-$$) db 0
dw 0xaa55
; BIOS загружает только первый 512-байтный сектор из загрузочного диска
; Таким образом если вручную добавим еще пару секторов в загрузочный диск путём
; повторения похожих чисел, то мы сможем убедится в том, что мы действительно
; загрузили(прочитали) эти два дополнительных сектора из диска, с которого бутаемся.
times 256 dw 0xdada ; Первый доп сектор
times 256 dw 0xface ; Второй доп cектор
Функция, считывающая секторы functions/dick/disk_load.asm :
disk_load:
pusha
push dx ;Сохраним DX в стеке -> потом сможем узнать сколько секторов было запрошено на чтение
mov ah, 0x02; BIOS disk read function
mov al, dh; 2 сектора читаем
mov cl, 0x02 ; Начиная со второго сектора читаем(тот, что сразу после загрузочного)
mov ch, 0x00 ; Цилиндр 0
mov dh, 0x00 ; Читаем с первой стороны диска
int 0x13
jc read_error ; Если CF(carry flag) установлен в 1 => не получилось считать, тогда выполняем инструкции по метке read_error
pop dx ; Возвращаем значение DX из стека
cmp dh, al ;Если AL(кол-во реально считанных секторов) != DH(ожидаемое кол-во прочитанных секторов) => прыжок на sectors_error
jne sectors_error
popa
ret
read_error:
mov bx, READ_ERROR_MSG
call print_string
call print_nl
jmp $
sectors_error:
mov bx, SECTORS_ERROR_MSG
call print_string
call print_nl
jmp $
READ_ERROR_MSG db "Disk read error!", 0
SECTORS_ERROR_MSG db "Read incorrect number of sectors!", 0
Функции вывода строк functions/print/print_string.asm :
print_string:
pusha
mov ah, 0x0e
return_to_check:
mov al, [bx]
cmp al, 0
jne print_symbol
popa
ret
print_symbol:
int 0x10
inc bx
jmp return_to_check
print_nl:
pusha
mov ah, 0x0e ; BIOS teletype routine
mov al, 0x0a ; LF = line feed = '\n'
int 0x10 ; print on the screen interrupt
mov al, 0x0d ; CR = carriage return = '\r'
int 0x10
popa
ret
Функция вывода шестнадцатеричного значения(dx как параметр) functions/print/print_hex.asm :
print_hex:
pusha ; сохраняем все регистры в стеке
mov cx, 4 ; Счетчик цикла. Нам надо напечать 4 символа после 0x
char_loop:
dec cx ; декрементация счетчика
mov ax, dx ; в ax хранится текущий выводимый символ
shr dx, 4 ; смещаем dx на длину одного символа символа
and ax, 0xf ; Изолируем текущий символ, обнуляя остальные
mov bx, HEX_OUT ; Устанавливаем BX на адрес памяти нашей строки
add bx, 2 ; Пропускаем первые два символа '0x'
add bx, cx ; пермещамся на текущий заполняемый элемент(зависит от счетчика) - изначально это послений элемент
cmp ax, 0xa ; Проверяем явлется текущий выводимый элемент числом или буквой
jl set_letter ; Если число, то сразу идем устанавливать его в нашу строку
add byte [bx], 7 ; Если буква, то берем байт по адресу, хранящемуся в BX, складывает значение этого байта с 7 и записывается результат туда же
; Добавляем 7 потому что от последней цифры до первой буквы в таблице ASCII расстояние 7
; и чтобы его скомпенсировать мы и добавлем это расстояние
; Полное объяснение: код буквы, как и код цифры есть простое смещение относительно НУЛЯ(0x30 = 00110000 = '0')
; Но так как в таблице ASCII между цифрами и Заглавными латинскими буквами есть расстояние в 7 символов(там другие символы)
; -> чтобы получить нужный код буквы неоюходимо учитывать это расстояние.
; Получается, в случае если очередной символ шестнадцатеричного числа, которое нам надо вывести это буква, то
; мы сначала компенсируем расстояние м/у последней цифрой и первой буквой(прибавлем 7 к коду '0'), а потом прибавляем
; уже смещение этой буквы относительно нуля, если бы буквы шли сразу после цифр.
jmp set_letter
set_letter:
add byte [bx], al ; Добавляем значение в al(код буквы/цифры) к символу в BX -> получаем в нужный ASCII код соотв символа в BX.
cmp cx, 0 ; Проверяем, не установили ли мы уже все символы
je print_hex_done ; Если да, то выводим готовую строку на экран
jmp char_loop ; Если нет, то продолжаем - выполняем еще одну итерацию кода выше(char_loop)
print_hex_done:
mov bx, HEX_OUT
call print_string ; Функция печати строки
popa ; Восстанавливаем все регистры из стека
ret
HEX_OUT:
db '0x0000', 0
Ответы (1 шт):
Проблема была функции print_hex. А именно в том, что после выхода из функции шаблонная строка HEX_OUT не обнулялась, следовательно код символа в новой выводимой строке просто прибавлялся к коду, лежавшего там до этого символа(с прошлого вывода).