Ассемблер. BIOS. Выводим строку дампа памяти

Здравствуйте.
Продолжим наши изыскания по языку ассемблеру и коду работающему на функциях BIOS. Предыдущая статья по этой теме лежит где-то здесь
В этот раз мы немножко оптимизируем загрузчик, и вторым сектором загрузим уже более что то функциональное. А именно выведем строку дампа памяти в 16 байт. Да, а ещё будут картиночки сначала, и в конце уже исходный код с какими-то комментариями.
Ещё бы я конечно рекомендовал бы этот исходник набрать ручками, а не копировать к себе в редактор, дабы приучить свою нейросеть к синтаксису языка и регистровой структуре процессора.


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

use16 ; указываем ассемблеру, что наш исполняемый код будет 16 битный
org 7c00h

start:
cli
mov ax, cs
mov ds, ax ; data segment
mov ss, ax ; stack segment
mov es, ax ; enhanced segment

mov sp, start ; stack pointer

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

sti
call .IP ; сохраняем в стек наш живой адрес с которого мы запустились

.IP:
xor dx, dx ; координаты курсора X = 0 и Y = 0
mov ax, msg_start ; сообщаем, что загрузчик загружен
call PRINT_STRING

inc dh ; Y + 1
xor dl, dl ; X = 0
mov ax, msg_segment ; выводим сегмент загрузки (просто для информации)
call PRINT_STRING
mov ax, cs
call PRINT_HEX_WORD ; выводим сегмент

inc dh ; Y + 1
xor dl, dl ; X = 0
mov ax, msg_adress ; информация — с какого адреса стартовали
call PRINT_STRING
pop ax
sub ax, 14h
call PRINT_HEX_WORD ; выводим байт код диска

inc dh ; Y + 1
xor dl, dl ; X = 0
mov ax, msg_disk_start ; информация — с какого номера диска стартовали
call PRINT_STRING
mov al, [disk_start] call PRINT_HEX_BYTE ; выводим байт код диска

; зафиксируем сообщения перед загрузкой второго сектора
inc dh ; Y + 1
xor dl, dl
mov ax, msg_press_key ; адрес строки — нажмите клавишу
call PRINT_STRING
call PRESS_KEY

; загружаем второй сектор
push ax
push dx ; сохраняем координаты x, y
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, 600h ; адрес = 32768. Посчитал как 7C00h + 512 (загруженный сектор) + 512 (резерв), es уже равен cs (буфер ES:BX)
int 13h ; вызываем функцию. Если CF=1(флаг переноса), то ошибка и в ah — код ошибки
pop dx ; востанавливаем координаты x, y
jc .ERROR ; если ошибка то преходим и показываем её код
pop ax

inc dh ; Y + 1, новая строка
xor dl, dl ; x = 0
mov ax, msg_load_complete ; адрес строки сообщения об успешной загрузке
call PRINT_STRING ; выводим строку

; зафиксируем сообщения перед переходом на загруженный сектор
inc dh ; Y + 1
xor dl, dl ; X = 0
mov ax, msg_press_key ; строка — нажмите клавишу
call PRINT_STRING
call PRESS_KEY ; функция ожидания нажатия клавиши

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

; выводим об ошибке загрузки сектора, al — код ошибки
.ERROR:
inc dh ; Y + 1
xor dl, dl ; X = 0
push ax ; сохраняем код ошибки
mov ax, msg_err_load ; строка сообщения об ошибки
call PRINT_STRING
pop ax ; восстанавливаем код ошибки
call PRINT_HEX_BYTE ; вывод байта кода ошибки

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

; вывод строки на экран
; ax — адрес выводимой строки
; dl — колонка (X) курсора
; dh — строка (Y) курсора
PRINT_STRING:
push ax
push si
pushf ; сохраняем регистр флагов
mov si, ax
cld ; направление для строковых данных, машинный код FC. Флаг DF = 0. Указатель SI = +1
.loops:
lodsb ; считать байт по адресу DS:SI в AL, машинный код AC
test al, al ; проверяем конец строки
jz .return
call PRINT_CHAR ; выводим символ
inc dl ; X = X + 1
jmp .loops
.return:
popf
pop si
pop ax
ret ; возврат из процедуры. Машинный код C3 — близкий возврат, в пределах сегмента. Машинный код CB — дальний возврат

; вывод на экран слова (16-бит)
; ax — выводимое слово
; dl — колонка (X) курсора
; dh — строка (Y) курсора
PRINT_HEX_WORD:
push ax
mov al, ah
call PRINT_HEX_BYTE ; выводим старший байт
pop ax
inc dl
call PRINT_HEX_BYTE ; выводим младший байт
ret

; вывод на экран байта (8-бит)
; al — выводимый байт
; dl — колонка (X) курсора
; dh — строка (Y) курсора
PRINT_HEX_BYTE:
push ax
push cx
mov ah, al ; сохраняем байт, для вывода младших 4-бит
mov cx, 4 ; для сдвига на 4 бита
shr al, cl ; сдвигаем старшие 4-бита в право
call PRINT_HEX_DIGIT ; выводим на экран старшие 4-бита
inc dl ; X = X + 1 (смещаем курсор в право)
mov al, ah ; возвращаем байт для обработки младших 4-бит
and al, 0Fh ; сбрасываем старшие 4-бита, чтобы остались только младшие 4-бита
call PRINT_HEX_DIGIT ; выводим на экран младшие 4-бита
pop cx
pop ax
ret

; перевод 4 бита в шестьнадцатеричный символ
; al — выводимый байт
; dl — колонка (X) курсора
; dh — строка (Y) курсора
PRINT_HEX_DIGIT:
push ax
cmp al, 0Ah ; число больше или равно 10 ?
jae .HEX_LETTER ; jae — переход если больше или равно, если флаг CF = 0 то переход
add al, 30h ; складываем al с кодом ASCII символа нуль (код 48(дес)), тем самым получая символы 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
jmp .WRITE_DIGIT
.HEX_LETTER:
sub al, 0Ah ; вычитаем 10 из преобразуемого числа
add al, 41h ; результат складываем с кодом ASCII символа(A = 65(дес)) + (0 … 5), тем самым получая символы A, B, C, D, E, F
.WRITE_DIGIT:
call PRINT_CHAR ; вывод символа на экран
pop ax
ret

; вывод символа на экран
; al — код символа
; dl — колонка (X) курсора
; dh — строка (Y) курсора
PRINT_CHAR:
push ax
push bx
push cx
push si
push di
push bp
push ax
xor bh, bh ; страница видеопамяти = 0
mov ah, 02h ; код функции установка курсора
int 10h
pop ax
mov ah, 09h ; код функции — писать символ/атрибут в текущей позиции курсора
mov cx, 1 ; счетчик (сколько экземпляров символа записать)
mov bl, 8 ; видео атрибут (текст) или цвет (графика)
int 10h ; вызов функции
pop bp
pop di
pop si
pop cx
pop bx
pop ax
ret

PRESS_KEY:
push ax
mov ah, 00h ; читать (ожидать) следующую нажатую клавишу. AL=ASCII символ
int 16h
pop ax
ret

data_text:
msg_start db «Bootloader start v2.0»,0 ; db — define byte — определить байт
msg_segment db «Segment start #», 0
msg_press_key db «Press key…», 0
msg_load_complete db «Loading sector #2», 0
msg_err_load db «Loading failed. Error code #», 0
msg_disk_start db «Disk start #», 0
msg_adress db «Adress start #», 0

data_v:
disk_start db 0 ; здесь диск с которого стартовали

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

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

 

Исходник файла main.asm
use16
org 600h

start:
call .m1 ; сохраним в стек адрес старта нашего кода
.m1:
inc dh ; Y = Y + 1, курсор на слующую строку
xor dl, dl ; X = 0, на новой строке курсор в нулевую позицию
mov ax, sector_load ; загружаем адрес строки сообщения
call PRINT_STRING ; вызываем подпрограмму вывода строки на экран

; выводим сообщение с какого адреса стартуем
inc dh
xor dl, dl
mov ax, adress_start
call PRINT_STRING
pop ax ; восстанавливаем адрес старта из стека
sub ax, 03h ; корректируем его, ведь он сохранился на три байта больше
call PRINT_WORD

; выводим сообщение «нажать клавишу»
inc dh
xor dl, dl
mov ax, key_press
call PRINT_STRING
call PRESS_KEY ; вызов подпрограммы нажатия клавиши

; выводим дамп памяти
inc dh
xor dl, dl
mov ax, dump_adress ; загружаем адрес строки сообщения
call PRINT_STRING ; вывовдим строку
xor ax, ax ; в регистре ax адрес дампа, пока будем выводить с адреса 0000h
call PRINT_WORD ; контрольноый вывод наашего адреса на экран
.loop_dump: ; цикл вывода дампа
inc dh ; курсор новая строка Y = Y + 1
xor dl, dl ; курсор на начало позиции в новой строке X = 0
call PRINT_LINE ; выводим строку 16 байт на экран
call PRESS_KEY ; ожидаем нажатие клавиши
add ax, 10h ; прибавляем к адресу 17 байт для вывода следующей строки
jmp .loop_dump ; переходим на начало цикла

; вывод на экран строки из 16-ти байт
; ax — адрес выводимого 16-ти байтного дампа
; dl — координата X курсора
; dh — координата Y курсора
PRINT_LINE:
push ax
push cx
push si
mov si, ax
mov cx, 0Fh ; регистр cx — счётчик цикла, загружаем число 16
.loop:
lodsb
call PRINT_BYTE
mov al, 20h ; символ пробела для разделения байтов на экране
call PRINT_CHAR ; выводим пробел на экран
loop .loop ; повторяем цикл 16 раз, в команде loop регистр cx = cx — 1
pop si
pop cx
pop ax
ret

; вывод слова на экран
; ax — выводимое слово
; dl — координата X курсора
; dh — координата Y курсора
PRINT_WORD:
push ax
mov al, ah
call PRINT_BYTE ; выводим старший байт
pop ax
call PRINT_BYTE ; выводим младший байт
ret

; вывод байта на экран
; al — выводимый байт
; dl — координата X курсора
; dh — координата Y курсора
PRINT_BYTE:
push ax
mov ah, al
shr al, 04h
call PRINT_DIGIT
mov al, ah
and al, 0Fh
call PRINT_DIGIT
pop ax
ret

; перевод 4-ёх бит в 16-тиричный символ и вывод на экран
; al — выводимый байт
; dl — координата X курсора
; dh — координата Y курсора
PRINT_DIGIT:
push ax
pushf
cmp al, 0Ah
jae .m1 ; если al больше или равно 10, то переход
add al, 30h ; складываем с кодом символа «0» (48дес)
jmp .m2
.m1:
add al, 37h ; складываем с кодом символа «7» (55дес). Если al = 10, то 55 + 10 = 65 — код выводимого символа «A»
.m2:
xor ah, ah ; ah = 0, равносильно 1-му выводимому символу
call PRINT_CHAR
popf
pop ax
ret

; вывод строки
; последний байт должен быть = 0, как конец строки
; ax — адрес строки
; dl — координата X курсора
; dh — координата Y курсора
PRINT_STRING:
push ax
push si
pushf ; сохраняем флаги, всётаки немножко, но будем на них влиять
test ax, ax ; может нам передали нулевой адрес ??
jz .return ; флаг ZF=1, да, переходим на выход
mov si, ax ; настраиваем индексный регистр
cld ; сбрасываем флаг направления итерации индексного регистра, DF=0 SI+1
xor ax, ax ; очищаем ax, где ah=0, что равносильно одному экземпляру символа
.loop:
lodsb ; копируем байт в al по адресу из индексного регистра SI, и SI=SI+1
test al, al ; проверяем конец строки, символ = 0
jz .return ; флаг ZF=1, конец строки найден, переходим на выход
call PRINT_CHAR ; вызов процедуры вывода символа по текущим координатам
jmp .loop ; повторяем цикл
.return:
popf
pop si
pop ax
ret

; вывод символа на экран по указанным координатам
; используются функции BIOS
; al — код символа
; ah — количество экземпляров, 0 или 1 = 1.
; dl — координата X курсора
; dh — координата Y курсора
PRINT_CHAR:
push ax
push bx
push cx
pushf ; сохраняем флаги, всё таки немножко, но будем на них влиять
test ah, ah ; проверяем, не прислали ли нам 0 экземпляров на вывод
jnz .m1 ; если количество > 0, ZF = 0, то ничего не корректируем
mov ah, 01h ; если количество = 0, поправляем
.m1:
push ax ; сохраним выводимый символ и количство экземпляров
xor bh, bh ; страница видеопамяти = 0
push dx ; сохраним требуемые координаты курсора
mov ah, 03h ; функция чтения координат курсора
int 10h ; вызов функции
pop ax ; восстановим требуемые координаты курсора
cmp dl, al ; сравниваем текущую и требуемую координату X
jne .int10h ; если не равны, то идём их устанавливать
cmp dh, ah ; сравниваем текущею и требуемую координату Y
je .m2 ; если равны, то ничего с координатами не делаем, прыгаем дальше
.int10h:
mov dx, ax
mov ah, 02h ; функция установки позиции курсора
int 10h
.m2:
pop ax ; восстановим выводимый символ и количство экземпляров
xor cx, cx ; сбрасываем счётчик экземпляров
mov cl, ah ; настраиваем счётчик экземпляров
mov ah, 09h ; функция вывода экземпляров символа на экран в текущей позиции курсора
mov bl, 8 ; BL = видео атрибут (текст) или цвет (графика)
int 10h
inc dl ; смещаем курсор X в право X = X + 1
mov ah, 02h ; устанавливаем новые координаты курсора
int 10h
popf
pop cx
pop bx
pop ax
ret

; подпрограмма ожидания нажатия клавиши
PRESS_KEY:
push ax
mov ah, 00h ; читать (ожидать) следующую нажатую клавишу. AL=ASCII символ
int 16h
pop ax
ret

data_area:
sector_load db «Sector loaded», 0
adress_start db «Adress start #», 0
key_press db «Key press…», 0
dump_adress db «dump 16 bytes from the address #», 0

finish:
times 1022 — finish + start db 0 ; заполняем нулями оставшееся адресное пространство
size: dw finish — start ; размер кода и данных в секторе

2 в “Ассемблер. BIOS. Выводим строку дампа памяти

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