Загрузочный сектор

www.sysbin.com: "Загрузочный сектор
Дата: 06/07/05; Автор: shade; Оригинальный источник: www.sysbin.com


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

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


; Общая часть для всех типов FAT

BS_jmpBoot:

jmp short BootStart ; Переходим на код загрузчика

nop

BS_OEMName db '*-v4VIHC' ; 8 байт, что было на моей дискете, то и написал

BPB_BytsPerSec dw 0x200 ; Байт на сектор

BPB_SecPerClus db 1 ; Секторов на кластер

BPB_RsvdSecCnt dw 1 ; Число резервных секторов

BPB_NumFATs db 2 ; Количество копий FAT

BPB_RootEntCnt dw 224 ; Элементов в корневом каталоге (max)

BPB_TotSec16 dw 2880 ; Всего секторов или 0

BPB_Media db 0xF0 ; код типа устройства

BPB_FATsz16 dw 9 ; Секторов на элемент таблицы FAT

BPB_SecPerTrk dw 18 ; Секторов на дорожку

BPB_NumHeads dw 2 ; Число головок

BPB_HiddSec dd 0 ; Скрытых секторов

BPB_TotSec32 dd 0 ; Всего секторов или 0

; Заголовок для FAT12 и FAT16

BS_DrvNum db 0 ; Номер диска для прерывания int 0x13

BS_ResNT db 0 ; Зарезервировано для Windows NT

BS_BootSig db 29h ; Сигнатура расширения

BS_VolID dd 2a876CE1h ; Серийный номер тома

BS_VolLab db 'X boot disk' ; 11 байт, метка тома

BS_FilSysType db 'FAT12   ' ; 8 байт, тип ФС

; Структура элемента каталога

struc DirItem

DIR_Name: resb 11

DIR_Attr: resb 1

DIR_ResNT: resb 1

DIR_CrtTimeTenth resb 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
struc NotInitData
SysSize: resd 1 ; Размер системной области FAT
fails: resd 1 ; Число неудачных попыток при чтении
fat: resd 1 ; Номер загруженного сектора с элементами FAT
endstruc ;NotInitData
; По этому адресу мы будем загружать загрузчик
%define SETUP_ADDR 0x1000
; А по этому адресу нас должны были загрузить
%define BOOT_ADDR 0x7C00
%define BUF 0x500
BootStart:
cld
xor cx, cx
mov ss, cx
mov es, cx
mov ds, cx
mov sp, BOOT_ADDR
mov bp, sp
; Сообщим о том что мы загружаемся
mov si, BOOT_ADDR + mLoading
call print

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

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

mov al, [byte bp+BPB_NumFATs]
cbw
mul word [byte bp+BPB_FATsz16]
add ax, [byte bp+BPB_HiddSec]
adc dx, [byte bp+BPB_HiddSec+2]
add ax, [byte bp+BPB_RsvdSecCnt]
adc dx, cx
mov si, [byte bp+BPB_RootEntCnt]
; dx:ax - Номер первого сектора корневого каталога
; si - Количество элементов в корневом каталоге
pusha
; Вычислим размер системной области FAT = резервные сектора +
; все копии FAT + корневой каталог
mov [bp+SysSize], ax ; осталось добавить размер каталога
mov [bp+SysSize+2], dx
; Вычислим размер корневого каталога
mov ax, 32
mul si
; dx:ax - размер корневого каталога в байтах, а надо в секторах
mov bx, [byte bp+BPB_BytsPerSec]
add ax, bx
dec ax
div bx
; ax - размер корневого каталога в секторах
add [bp+SysSize], ax ; Теперь мы знаем размер системной
adc [bp+SysSize+2], cx ; области FAT, и начало области данных
popa
; В dx:ax - снова номер первого сектора корневого каталога
; si - количество элементов в корневом каталоге
Теперь мы будем просматривать корневой каталог в поисках нужного нам файла
NextDirSector:
; Загрузим очередной сектор каталога во временный буфер
mov bx, 700h ; es:bx - буфер для считываемого сектора
mov di, bx ; указатель текущего элемента каталога
mov cx, 1 ; количество секторов для чтения
call ReadSectors
jc near DiskError ; ошибка при чтении
RootDirLoop:
; Ищем наш файл
; cx = 0 после функции ReadSectors
cmp [di], ch ; byte ptr [di] = 0?
jz near NotFound ; Да, это последний элемент в каталоге
; Нет, не последний, сравним имя файла
pusha
mov cl, 11 ; длина имени файла с расширением
mov si, BOOT_ADDR + LoaderName ; указатель на имя искомого файла
rep cmpsb ; сравниваем
popa
jz short Found ; Нашли, выходим из цикла
; Нет, ищем дальше
dec si ; RootEntCnt
jz near NotFound ; Это был последний элемент каталога
add di, 32 ; Переходим к следующему элементу каталога
; bx указывает на конец прочтенного сектора после call ReadSectors
cmp di, bx ; Последний элемент в буфере?
jb short RootDirLoop ; Нет, проверим следующий элемент
jmp short NextDirSector ; Да последний, загрузим следующий сектор
Из этого кода мы можем выйти одну из трех точек: ошибка при чтении DiskError, файл найден Found или файл не найден NotFound.

Если файл найден, то загрузим его в память и передадим управление на его начало.

Found:
; Загрузка загрузчика (извините, каламбур)
mov bx, SETUP_ADDR
mov ax, [byte di+DIR_FstClusLow] ; Номер первого кластера файла
; Загружаем сектор с элементами FAT, среди которых есть FAT[ax]
; LoadFAT сохраняет значения всех регистров
call LoadFAT
ReadCluster:
; ax - Номер очередного кластера
; Загрузим его в память
push ax
; Первые два элемента FAT служебные
dec ax
dec ax
; Число секторов для чтения
; cx = 0 после ReadSectors
mov cl, [byte bp+BPB_SecPerClus] ; Секторов на кластер
mul cx
; dx:ax - Смещение кластера относительно области данных
add ax, [byte bp+SysSize]
adc dx, [byte bp+SysSize+2]
; dx:ax - Номер первого сектора требуемого кластера
; cx еще хранит количество секторов на кластер
; es:bx - конец прошлого кластера и начало нового
call ReadSectors ; читаем кластер
jc near DiskError ; Увы, ошибка чтения
pop ax ; Номер кластера
; Это конец файла?
; Получим значение следующего элемента FAT
pusha
; Вычислим адрес элемента FAT
mov bx, ax
shl ax, 1
add ax, bx
shr ax, 1
; Получим номер сектора, в котором находится текущий элемент FAT
cwd
div word [byte bp+BPB_BytsPerSec]
cmp ax, [bp+fat] ; Мы уже читали этот сектор?
popa
je Checked ; Да, читали
; Нет, надо загрузить этот сектор
call LoadFAT
Checked:
; Вычислим адрес элемента FAT в буфере
push bx
mov bx, ax
shl bx, 1
add bx, ax
shr bx, 1
and bx, 511 ; остаток от деления на 512
mov bx, [bx+0x700] ; а вот и адрес
; Извлечем следующий элемент FAT
; В FAT16 и FAT32 все немного проще :(
test al, 1
jnz odd
and bx, 0xFFF
jmp short done
odd:
shr bx, 4
done:
mov ax, bx
pop bx
; bx - новый элемент FAT
cmp ax, 0xFF8 ; EOF - конец файла?
jb ReadCluster ; Нет, читаем следующий кластер
; Наконец-то загрузили
mov ax, SETUP_ADDR>>4 ; SETUP_SEG
mov es, ax
mov ds, ax
; Передаем управление, наше дело сделано :)
jmp SETUP_ADDR>>4:0
LoadFAT ;proc
; Процедура для загрузки сектора с элементами FAT
; Элемент ax должен находится в этом секторе
; Процедура не должна менять никаких регистров
pusha
; Вычисляем адрес слова содержащего нужный элемент
mov bx, ax
shl ax, 1
add ax, bx
shr ax, 1
cwd
div word [byte bp+BPB_BytsPerSec]
; ax - смещение сектора относительно начала таблицы FAT
mov [bp+fat], ax ; Запомним это смещение, dx = 0
cwd   ; dx:ax - номер сектора, содержащего FAT[?]
; Добавим смещение к первой копии таблицы FAT
add ax, [byte bp+BPB_RsvdSecCnt]
adc dx, 0
add ax, [byte bp+BPB_HiddSec]
adc dx, [byte bp+BPB_HiddSec+2]
mov cx, 1 ; Читаем один сектор. Можно было бы и больше, но не быстрее
mov bx, 700h ; Адрес буфера
call ReadSectors
jc DiskError ; Ошибочка вышла
popa
ret
;LoadFAT endp

В FAT12 на каждый элемент FAT отводится по 12 бит, что несколько усложняет нашу работу, в FAT16 и FAT32 на каждый элемент отводится по 16 и 32 бита соответственно и можно просто прочесть слово или двойное слово, а в FAT12 необходимо прочесть слово содержащее элемент FAT и правильно извлечь из него 12 бит.

Теперь разберем процедуру загрузки секторов. Процедура получает номер сектора в dx:ax (нумерация с нуля) и преобразует его к формату CSH (цилиндр, сектор, сторона), используемому прерыванием BIOS int 0x13.

; *************************************************
; *          Чтение секторов с диска              *
; *************************************************

; * Входные параметры:                            *

; * dx:ax       - (LBA) номер сектора             *

; * cx          - количество секторов для чтения  *

; * es:bx       - адрес буфера                    *

; *************************************************

; * Выходные параметры:                           *

; * cx       - Количество не прочтенных секторов  *

; * es:bx    - Указывает на конец буфера          *

; * cf = 1   - Произошла ошибка при чтении        *

; *************************************************

ReadSectors ;proc

next_sector:

; Читаем очередной сектор

mov byte [bp+fails], 3 ; Количество попыток прочесть сектор

try:

; Очередная попытка

pusha

; Преобразуем линейный адрес в CSH

; dx:ax = a1:a0

xchg ax, cx  ; cx = a0

mov ax, [byte bp+BPB_SecPerTrk]

xchg ax, si  ; si = Scnt

xchg ax, dx  ; ax = a1

xor dx, dx

; dx:ax = 0:a1

div si  ; ax = q1, dx = c1

xchg ax, cx  ; cx = q1, ax = a0

; dx:ax = c1:a0

div si  ; ax = q2, dx = c2 = c

inc dx  ; dx = Sector?

xchg cx, dx  ; cx = c, dx = q1

; dx:ax = q1:q2

div word [byte bp+BPB_NumHeads] ; ax = C (track), dx = H

mov dh, dl  ; dh = H

mov ch, al

ror ah, 2

or cl, ah

mov ax, 0201h  ; ah=2 - номер функции, al = 1 сектор

mov dl, [byte bp+BS_DrvNum]

int 13h

popa

jc Failure ; Ошибка при чтении

; Номер следующего сектора

inc ax

jnz next

inc dx

next:

add bx, [byte bp+BPB_BytsPerSec]

dec cx ; Все сектора прочтены?

jnz next_sector ; Нет, читаем дальше

return:

ret

Failure:

dec byte [bp+fails] ; Последняя попытка?

jnz try ; Нет, еще раз

; Последняя, выходим с ошибкой

stc

ret

;ReadSectors endp
Осталось всего ничего:
; Сообщения об ошибках

NotFound: ; Файл не найден

mov si, BOOT_ADDR + mLoaderNotFound

call print

jmp short die

DiskError: ; Ошибка чтения

mov si, BOOT_ADDR + mDiskError

call print

;jmp short die 

die: ; Просто ошибка

mov si, BOOT_ADDR + mReboot

call print

_die: ; Бесконечный цикл, пользователь сам нажмет Reset

jmp short _die

; Процедура вывода ASCIIZ строки на экран

; ds:si - адрес строки

print: ; proc

pusha

print_char:

lodsb ; Читаем очередной символ

test al, al ; 0 - конец?

jz short pr_exit ; Да конец

; Нет, выводим этот символ

mov ah, 0eh

mov bl, 7

int 10h

jmp short print_char ; Следующий

pr_exit:

popa

ret

;print endp

; Перевод строки

%define endl 10,13,0

; Строковые сообщения

mLoading db 'Loading...',endl

mDiskError db 'Disk I/O error',endl

mLoaderNotFound db 'Loader not found',endl

mReboot  db 'Reboot system',endl

; Выравнивание размера образа на 512 байт

times 499-($-$$) db 0

LoaderName db 'BOOTOR     ' ; Имя файла загрузчика

BootMagic dw 0xAA55 ; Сигнатура загрузочного сектора

Ну вот вроде бы и все. Компилируется все это до безобразия просто:

> nasm -f bin boot.asm -lboot.lst -oboot.bin

Осталось только как-то записать этот образ в загрузочный сектор вашей дискеты и разместить в корне этой дискеты файл загрузчика BOOTOR. Загрузочный сектор можно записать с помощью такой вот простой программы на Turbo (Borland) Pascal. Эта программа будет работать как в DOS, так и в Windows - пробовал на WinXP - работает как ни странно, но только с floopy. Но все же я рекомендую запускать эту утилиту из-под чистого DOS'а, т.к. WinXP обновляет не все поля в заголовке FAT и загрузочный сектор может работать некорректно.

var
fn:string;
f:file;
buf:array[0..511] of byte;
ok:boolean; 

begin
fn:=ParamStr(1);
if fn='' then writeln('makeboot bootsect.bin')
else
begin
writeln('Making boot floppy');

{$I-}

assign(f,fn);
reset(f,sizeof(buf));
BlockRead(f,buf,1);
close(f);

{$I+}

if IOResult<>0 then

begin
Writeln('Failed to read file '',fn,''');
Halt(1);
end;
ok:=false;

asm

mov       ax, 0301h
mov       cx, 1
mov       dx, 0
mov       bx, seg buf
mov       es, bx
mov       bx, offset buf
int       13h
jc        @error
mov       ok, true

@error:

end;

if ok then writeln('Done :)')
else begin
writeln('Makeboot failed :(');
Halt(1);

end;
end;
end.



Отправлено через панель инструментов Google:"

Comments

Popular posts from this blog

Открываем порт для сервера Minecraft на роутере mikrotik (команда для терминала в WinBox)

Интересное о Формальдегиде