Большой архив статей, книг, документации по программированию, вебдизайну, компьютерной графике, сетям, операционным системам и многому другому
 
<Добавить в Избранное>    <Сделать стартовой>    <Реклама на сайте>    <Контакты>
  Главная Документация Программы Обои   Экспорт RSS E-Books
 
Поиск по сайту

 

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


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

Мы будем писать загрузочный сектор для трехдюймовой дискеты с файловой системой 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.

Исходники: полный пакет(14,8Kb), fat12.asm.zip(3,86Kb), Netwide Assembler for Win32. Следующий этап загрузка ядра операционной системы.

Автор: Золотов Алексей
Источник: www.zolotov.h14.ru

Ссылки по теме
Динамический опрос клавиатуры
Основы разработки прикладных виртуальных драйверов
Оптимизация программ на ассемблере
 

Компьютерная документация от А до Я - Главная

 
 
Интересное в сети
10 новых программ
CodeLobster PHP Edition 3.7.2
WinToFlash 0.7.0008
Free Video to Flash Converter 4.7.24
Total Commander v7.55
aTunes 2.0.1
Process Explorer v12.04
Backup42 v3.0
Predator 2.0.1
FastStone Image Viewer 4.1
Process Lasso 3.70.4
FastStone Image Viewer 4.0
Xion Audio Player 1.0.125
Notepad GNU v.2.2.8.7.7
K-Lite Codec Pack 5.3.0 Full

Наши сервисы
Рассылка новостей. Подпишитесь на рассылку сейчас и вы всегда будете в курсе последних событий в мире информационных технологий.
Новостные информеры. Поставьте наши информеры к себе и у вас на сайте появится дополнительный постоянно обновляемый раздел.
Добавление статей. Если вы являетесь автором статьи или обзора на тему ИТ присылайте материал нам, мы с удовольствием опубликуем его у себя на сайте.
Реклама на сайте. Размещая рекламу у нас, вы получите новых посетителей, которые могут стать вашими клиентами.
 
Это интересно
 

Copyright © CompDoc.Ru
При цитировании и перепечатке ссылка на www.compdoc.ru обязательна. Карта сайта.
 
Rambler's Top100