Ассемблер. BIOS. Загрузчик. Читаем второй сектор

Продолжим работать с ассемблером. Так же, как и в предыдущей статье  стартуем с флоппи диска. Будет чуточку переделанный загрузчик, который чуточку разрастётся, и будет второй сектор, с кодом небольшого размера, который мы будем читать и исполнять. В итоге, должно будет получится как на картинке:

Было интересно вывести какую то небольшую информацию о загрузчике.  На первом этапе это была информация о том, в какой сегмент памяти читается загрузчик. Потом где-то подцепив информацию о диске, с которого происходит загрузка, и добавил вывод информации о нём. Но это было всё второстепенно, так как основная задача была — это подгрузить второй сектор с диска загрузчиком и запустить его на выполнение.

Каждый этап выполнения программы сопровождается информационным сообщением и фиксацией ожидания нажатия клавиши для продолжения.

Вот код самого загрузчика:

Исходник файла boot.asm

use16
org 7c00h

start:
mov ax, cs ; читаем сегмент кода в регистр ax 
mov ds, ax ; из регистра ax переписываем адрес сегмента в регистр сегмента данных dx
mov ss, ax ; из регистра ax переписываем адрес сегмента в регистр сегмента стека ss
mov es, ax ; из регистра ax переписываем адрес сегмента в дополнительный регистр сегмента данных es

mov sp, start ; stack pointer

mov [disk_start], dl ; обычно при запуске системы номер диска, с которого проведён запуск передаётся в dl

mov bh, 0 ; видео страница 0
mov dl, [x] ; Координата X курсора
mov dh, [y] ; Координата Y курсора
mov ah, 02h ; сервис установить курсор
int 10h ; вызов сервиса
mov si, msg_start ; сообщаем, что загрузчик загружен
call print_string

; Выведем сообщение — с какого диска стартовали
inc [y] ;y = y + 1 перепрыгиваем на следующую строку
mov dh, [y] mov ah, 02h ; сервис — установить курсор
int 10h
mov si, msg_disk_start ; информация — с какого номера диска стартовали
call print_string
mov al, [disk_start] call WRITE_HEX_BYTE ; выводим байт код диска

; Выведем сообщение — в каком сегменте оперативной памяти работаем
inc [y] ;y = y + 1 перепрыгиваем на следующую строку
mov dh, [y] int 10h ; установка курсора. В ah сохранился код функции 02h
mov si, msg_segment
call print_string
push ax ; сохраним номер функции установки курсора
mov ax, cs ; выводим сегмент загрузки (просто для информации)
call WRITE_HEX_DWORD ;
pop ax ; восстановим номер функции установки курсора

; зафиксируем сообщения перед загрузкой второго сектора
mov bh, 0
inc [y] ; Y + 1
mov dh, [y] int 10h ; установка курсора. В ah сохранился код функции 02h
mov si, msg_press_key ; адрес строки — нажмите клавишу
call print_string

;mov ah, 01h ; поверка наличии символа (для 84-клавишной клавиатуры). Выход: если ZF=0, то AH=скан-код BIOS, AL=символ ASCII
mov ah, 00h ; читать (ожидать) следующую нажатую клавишу. AL=ASCII символ
int 16h ; вызов функции ожидания нажатия клавиши

; загружаем второй сектор
mov ah, 02h ; функция читать секторы int 13h
mov dl, [disk_start] ; номер диска (0=диск A…; 80h=тв.диск 0, 81h=тв.диск 1 …)
mov dh, 0 ; номер головки чтения
mov ch, 0 ; номер дорожки (цилиндра) от 0 до n
mov cl, 2 ; номер сектора, 1 — n
mov al, 1 ; число секторов (в сумме не больше чем один цилиндр
mov bx, 8000h ; адрес = 32768. Посчитал как 7C00h + 512 (загруженный сектор) + 512 (резерв), es уже равен cs (буфер ES:BX)
int 13h ; вызываем функцию. Если CF=1(флаг переноса), то ошибка и в ah — код ошибки
jc .ERROR ; если ошибка то переходим и показываем её код

mov bh, 0 ; выводим сообщение, что сектор загружен
inc [y] ; Y + 1, новая строка
mov dl, [x] mov dh, [y] mov ah, 02h ; устанавливаем курсор
int 10h
mov si, msg_load_complete ; адрес строки сообщения
call print_string ; выводим строку

; зафиксируем сообщения перед переходом на загруженный сектор
inc [y] ; Y + 1
mov dh, [y] int 10h ; установка курсора
mov si, msg_press_key ; строка — нажмите клавишу
call print_string
mov ah, 00h ; функция ожидания нажатия клавиши
int 16h ; вызов функции

jmp 8000h ; переходим на адрес загруженного сектора (в пределах сегмента, ближний переход

; выводим об ошибке загрузки сектора, al — код ошибки
.ERROR:
mov bx, 0
inc [y] ; Y + 1
mov dl, [x] mov dh, [y] mov ah, 02h ; установка курсора
int 10h ; устанавливаем
mov si, msg_err_load ; строка сообщения об ошибки
call print_string
call WRITE_HEX_BYTE ; вывод байта

jmp $ ; зацикливаемся

; Подпрограммы
; вывод строки на экран. Регистр SI — входной, адрес выводимой строки
print_string:
push ax
push bx
push si
cld ; направление для строковых данных, машинный код FC. Флаг DF = 0. Указатель SI = +1
mov ah, 0Eh ; писать символ в активную видео страницу. AL — записываемый символ, BL — цвет переднего плана
mov bl, 0
.loops:
lodsb ; считать байт по адресу DS:SI в AL, машинный код AC
test al, al ; проверяем конец строки
jz .return
int 10h ; выводим символ
jmp .loops
.return:
pop si
pop bx
pop ax
ret ; возврат из процедуры. Машинный код C3 — близкий возврат, в пределах сегмента. Машинный код CB — дальний возврат

; вывод на экран слова (16-бит), регистр AX — входной
WRITE_HEX_DWORD:
push dx
mov dl, ah ; старший байт
call WRITE_HEX_BYTE ; передаём на подпрограмму вывода байта
mov dl, al ; младший байт
call WRITE_HEX_BYTE ; передаём на подпрограмму вывода байта
pop dx
ret

; вывод на экран байта (8-бит), регистр DL — входной
WRITE_HEX_BYTE:
push ax
push cx
push dx
mov dh, dl ; сохраняем байт, для вывода младших 4-бит
mov cx, 4 ; для сдвига на 4 бита
shr dl, cl ; сдвигаем старшие 4-бита в право
call WRITE_HEX_DIGIT ; выводим на экран старшие 4-бита
mov dl, dh ; возвращаем байт для обработки младших 4-бит
and dl, 0Fh ; сбрасываем старшие 4-бита, чтобы остались только младшие 4-бита
call WRITE_HEX_DIGIT ; выводим на экран младшие 4-бита
pop dx
pop cx
pop ax
ret

; перевод 4 бита в шестнадцатеричный символ. Регистр DL входной — 4 бита для перевода в символ ASCII
WRITE_HEX_DIGIT:
push dx
cmp dl, 10
jae .HEX_LETTER ; jae — переход если больше или равно, если флаг CF = 0 то переход
add dl, «0» ; складываем dl с кодом ASCII символа нуль (код 48)
jmp .WRITE_DIGIT
.HEX_LETTER:
add dl, «A» — 10 ; складываем dl с кодом ASCCI символа(A = 65) — 10, так как числа от 10 до 16 уже лежат dl
.WRITE_DIGIT:
call WRITE_HAR ; вывод символа на экран
pop dx
ret

; вывод символа на экран, регистр dl — входной
WRITE_HAR:
push ax
push bx
mov ah, 0eh ; функция — писать символ на активную видео страницу (эмуляция телетайпа)
mov al, dl ; al — выводимый символ
mov bh, 0 ; на всякий случай, видео страница=0
mov bl, 0 ; bl — цвет переднего плана (для графических режимов)
int 10h ; вызов функции
pop bx
pop ax
ret

data_text:
msg_start db «Bootloader start v1.0»,0 ; db — define byte — определить байт
msg_segment db «Loaded into segment #», 0
msg_press_key db «Press key…», 0
msg_load_complete db «Loading is complete», 0
msg_err_load db «Loading is error #», 0
msg_disk_start db «Disk start #», 0

data_v:
; в оперативной памяти храним координаты курсора X и Y
x db 0 ; Координата X
y db 0 ; Координата Y
disk_start db 0 ; здесь диск с которого стартовали

size: dw $ — start ; здесь размер кода вместе с данными

finish:
times 0x1fe — finish + start db 0 ; заполняем нулями оставшееся адресное пространство
signatura:
db 55h, 0xAA ; сигнатура загрузочного сектора

 

Код второго сектора поменьше будет, нам нужно лишь убедится, что он загружен и исполняется:

Исходник файла main.asm

org 8000h

code_segment:
start:

; установка курсора
mov bh, 0 ; видео страница 0
mov ah, 03h ; чтение позиции и размер курсора
int 10h
inc dh ; Y + 1
mov dl, 0 ; позиция курсора в столбце
mov ah, 02h ; установка курсора
int 10h

mov ax, msg_start ; загружаем в регистр ax адрес строки с сообщением о загрузке
call PRINT ; вызов подпрограммы вывода строки текста

jmp $; зацикливаемся

; вывод строки на экран. Регистр ax — входной, адрес строки
PRINT:
push ax
push dx
push bx
push cx
push si

mov si, ax
mov bh, 0 ; страница видео памяти 0
mov ah, 03h ; читать позицию курсора
int 10h

mov cx, 1 ; дл¤ вывода символа, счётчик, сколько экземпляров писать
cld

.LOOPS:
lodsb
test al, al
jz .RETURN
mov ah, 0Ah ; писать символ в текущую позицию курсора
int 10h
inc dl ; x + 1
mov ah, 02h ; установить позицию курсора
int 10h
jmp .LOOPS
.RETURN:
pop si
pop cx
pop bx
pop dx
pop ax
ret

data_segment:
msg_start: db «Module load ok», 0

finish:
times 200h — finish + start db 0

Компилируем в FASM каждый файл отдельно и получаем два файла BIN по 512 байт. Записываем их на дискету, ну и пробуем запустить, загрузившись из под BIOSа с дисковода.

Продолжение

avatar

Денис ZX

г. Орехово-Зуево

Ассемблер. BIOS. Загрузчик. Читаем второй сектор: 3 комментария

Добавить комментарий