Пишем свой загрузочный сектор


Мы будем писать загрузочный сектор для трехдюймовой дискеты с файловой системой FAT12. После окончания начальной загрузки программа POST находит активное устройство и загружает с него короткую программу загрузки ОС - загрузочный сектор. Загрузочный сектор это первый физический сектор устройства, в данном случае дискеты и его размет равен всего ничего 512 байт. С помощью этих 512 байт кода мы должны найти основную часть загрузчика операционной системы, загрузить его в память и передать ему управление. Заголовок файловой системы FAT находится в первом секторе дискеты, благодаря чему этот заголовок, содержащий всю необходимую информацию о файловой системе, загружается вместе нашим загрузчиком.

Наш загрузочный сектор будет искать в корневом каталоге некоторый файл - загрузчик, загрузит его в память и передаст ему управление на его начало. А загрузчик уже сам разберется, что ему делать дальше. Я использую NASM, т.к. считаю, что он больше подходит для наших целей.

И так, приступим. Как я уже говорил, в начале нашего загрузочного сектора располагается заголовок FAT, опишем его:

; Общая часть для всех типов FAT
BS_jmpBoot:
jmpshort BootStart; Переходим на код загрузчика
nop
BS_OEMNamedb '*-v4VIHC'; 8 байт, что было на моей дискете, то и написал
BPB_BytsPerSecdw 0x200; Байт на сектор
BPB_SecPerClusdb 1; Секторов на кластер
BPB_RsvdSecCntdw 1; Число резервных секторов
BPB_NumFATsdb 2; Количектво копий FAT
BPB_RootEntCntdw 224; Элементов в корневом катологе (max)
BPB_TotSec16dw 2880; Всего секторов или 0
BPB_Mediadb 0xF0; код типа устройства
BPB_FATsz16dw 9; Секторов на элемент таблицы FAT
BPB_SecPerTrkdw 18; Секторов на дорожку
BPB_NumHeadsdw 2; Число головок
BPB_HiddSecdd 0; Скрытых секторов
BPB_TotSec32dd 0; Всего секторов или 0
; Заголовок для FAT12 и FAT16
BS_DrvNumdb 0; Номер дика для прерывания int 0x13
BS_ResNTdb 0; Зарезервировано для Windows NT
BS_BootSigdb 29h; Сигнатура расширения
BS_VolIDdd 2a876CE1h; Серийный номер тома
BS_VolLabdb 'X boot disk'; 11 байт, метка тома
BS_FilSysTypedb 'FAT12   '; 8 байт, тип ФС
; Структура элемента каталога
strucDirItem
DIR_Name:resb 11
DIR_Attr:resb 1
DIR_ResNT:resb 1
DIR_CrtTimeTenthresb 1
DIR_CrtTime:resw 1
DIR_CrtDate:resw 1
DIR_LstAccDate:resw 1
DIR_FstClusHi:resw 1
DIR_WrtTime:resw 1
DIR_WrtDate:resw 1
DIR_FstClusLow:resw 1
DIR_FileSize:resd 1
endstruc ;DirItem

Большинство полей мы использовать не будем, и так мало места для полета. Загрузчик BIOS передает нам управление на начало загрузочного сектора, т.е. на BS_jmpBoot, поэтому в начале заголовка FAT на отводится 3 байта для короткой или длинной инструкции jmp. Мы в данном случае использовали короткую, указав модификатор short, и в третьем байте просто разместили однобайтовую инструкцию nop.

По инструкции jmp short BootStart мы переходим на наш код. Проведем небольшую инициализацию:

; Наши не инициализированные переменные
; При инициализации они затрут не нужные нам
; поля заголовка FAT: BS_jmpBoot и BS_OEMName
strucNotInitData
SysSize:resd 1; Размер системной области FAT
fails:resd 1; Число неудачных попыток при чтении
fat:resd 1; Номер загруженного сектора с элементами FAT
endstruc ;NotInitData
; По этому адресу мы будем загружать загрузчик
%define SETUP_ADDR0x1000
; А по этому адресу нас должны были загрузить
%define BOOT_ADDR0x7C00
%define BUF0x500
BootStart:
cld
xorcx, cx
movss, cx
moves, cx
movds, cx
movsp, BOOT_ADDR
movbp, sp
; Сообщим о том что мы загружаемся
movsi, BOOT_ADDR + mLoading
callprint

Все сегментные регистры настраиваем на начало физической памяти. Вершину стека настраиваем на начало нашего сектора, стек растет вниз (т.е. в сторону младших адресов), так что проблем быть не должно. Туда же указывает регистр bp - нам нужно обращаться к полям заголовка FAT и паре наших переменных. Мы используем базовую адресацию со смещением, для чего используем регистр bp т.к. в этом случае можно использовать однобайтовые смещения, вместо двухбайтовых адресов, что позволяет сократить код. Процедуру print, выводящую сообщение на экран, рассмотрим позже.

Теперь нам нужно вычислить номера первых секторов корневого каталога и данных файлов.

moval, [byte bp+BPB_NumFATs]
cbw
mulword [byte bp+BPB_FATsz16]
addax, [byte bp+BPB_HiddSec]
adcdx, [byte bp+BPB_HiddSec+2]
addax, [byte bp+BPB_RsvdSecCnt]
adcdx, cx
movsi, [byte bp+BPB_RootEntCnt]
; dx:ax - Номер первого сектора корневого каталога
; si - Количество элементов в корневом каталоге
pusha
; Вычислим размер системной области FAT = резервные сектора +
; все копии FAT + корневой каталог
mov[bp+SysSize], ax; осталось добавить размер каталога
mov[bp+SysSize+2], dx
; Вычислим размер корневого каталога
movax, 32
mulsi
; dx:ax - размер корневого каталога в байтах, а надо в секторах
movbx, [byte bp+BPB_BytsPerSec]
addax, bx
decax
divbx
; ax - размер корневого каталога в секторах
add[bp+SysSize], ax; Теперь мы знаем размер системной
adc[bp+SysSize+2], cx; области FAT, и начало области данных
popa
; В dx:ax - снова номер первого сектора корневого каталога
; si - количество элементов в корневом каталоге

Теперь мы будем просматривать корневой каталог в поисках нужного нам файла

NextDirSector:
; Загрузим очередной сектор каталога во временный буфер
movbx, 700h; es:bx - буфер для считываемого сектора
movdi, bx; указатель текущего элемента каталога
movcx, 1; количество секторов для чтения
callReadSectors
jcnear DiskError; ошибка при чтении
RootDirLoop:
; Ищем наш файл
; cx = 0 после функции ReadSectors
cmp[di], ch; byte ptr [di] = 0?
jznear NotFound; Да, это последний элемент в каталоге
; Нет, не последний, сравним имя файла
pusha
movcl, 11; длина имени файла с расширением
movsi, BOOT_ADDR + LoaderName; указатель на имя искомого файла
repcmpsb; сравниваем
popa
jzshort Found; Нашли, выходим из цикла
; Нет, ищем дальше
decsi; RootEntCnt
jznear NotFound; Это был последний элемент каталога
adddi, 32; Переходим к следующему элементу каталога
; bx указывает на конец прочтенного сектора после call ReadSectors
cmpdi, bx; Последний элемент в буфере?
jbshort RootDirLoop; Нет, проверим следующий элемент
jmpshort NextDirSector; Да последний, загрузим следующий сектор

Из этого кода мы можем выйти одну из трех точек: ошибка при чтении DiskError, файл наден Found или файл не найден NotFound.

 
« Предыдущая статья   Следующая статья »