forked from KolibriOS/kolibrios
Rustem Gimadutdinov (rgimad)
73864ff1d7
git-svn-id: svn://kolibrios.org@9019 a494cfbc-eb01-0410-851d-a64ba20cac60
334 lines
30 KiB
Plaintext
334 lines
30 KiB
Plaintext
; Copyright (c) 2008-2009, diamond
|
||
; All rights reserved.
|
||
;
|
||
; Redistribution and use in source and binary forms, with or without
|
||
; modification, are permitted provided that the following conditions are met:
|
||
; * Redistributions of source code must retain the above copyright
|
||
; notice, this list of conditions and the following disclaimer.
|
||
; * Redistributions in binary form must reproduce the above copyright
|
||
; notice, this list of conditions and the following disclaimer in the
|
||
; documentation and/or other materials provided with the distribution.
|
||
; * Neither the name of the <organization> nor the
|
||
; names of its contributors may be used to endorse or promote products
|
||
; derived from this software without specific prior written permission.
|
||
;
|
||
; THIS SOFTWARE IS PROVIDED BY Alexey Teplov aka <Lrz> ''AS IS'' AND ANY
|
||
; EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
; DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
|
||
; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
;*****************************************************************************
|
||
|
||
Читай между строк - там никогда не бывает опечаток.
|
||
|
||
Бутсектор для FAT32-тома на носителе с размером сектора 0x200 = 512 байт.
|
||
|
||
=====================================================================
|
||
|
||
Есть две версии в зависимости от того, поддерживает ли носитель LBA,
|
||
выбор осуществляется установкой константы use_lba в первой строке исходника.
|
||
Требования для работы:
|
||
1) Сам бутсектор, первая копия FAT и все используемые файлы
|
||
должны быть читабельны. (Если дело происходит на носителе с разбиением на
|
||
разделы и загрузочный код в MBR достаточно умный, то читабельности резервной
|
||
копии бутсектора (сектор номер 6 на томе) достаточно вместо читабельности
|
||
самого бутсектора).
|
||
2) Минимальный процессор - 80386.
|
||
3) В системе должно быть как минимум 584K свободной базовой памяти.
|
||
|
||
=====================================================================
|
||
|
||
Документация в тему (ссылки проверялись на валидность 15.05.2008):
|
||
официальная спецификация FAT: http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx
|
||
в формате PDF: http://staff.washington.edu/dittrich/misc/fatgen103.pdf
|
||
русский перевод: http://wasm.ru/docs/11/fatgen103-rus.zip
|
||
официальная спецификация расширения EDD BIOS 3.0: http://www.phoenix.com/NR/rdonlyres/19FEBD17-DB40-413C-A0B1-1F3F560E222F/0/specsedd30.pdf
|
||
то же, версия 1.1: http://www.phoenix.com/NR/rdonlyres/9BEDED98-6B3F-4DAC-BBB7-FA89FA5C30F0/0/specsedd11.pdf
|
||
описание функций BIOS: Interrupt List by Ralf Brown: http://www.cs.cmu.edu/~ralf/files.html
|
||
официальная спецификация Boot BIOS: http://www.phoenix.com/NR/rdonlyres/56E38DE2-3E6F-4743-835F-B4A53726ABED/0/specsbbs101.pdf
|
||
|
||
=====================================================================
|
||
|
||
Схема используемой памяти:
|
||
...-7C00 стек
|
||
7C00-7E00 код бутсектора
|
||
7E00-8200 вспомогательный файл загрузчика (kordldr.f32)
|
||
8400-8C00 информация о кэше для таблицы FAT: 100h входов по 8
|
||
байт: 4 байта (две ссылки - вперёд и назад) для
|
||
организации L2-списка всех прочитанных секторов в
|
||
порядке возрастания последнего времени использования
|
||
+ 4 байта для номера сектора; при переполнении кэша
|
||
выкидывается элемент из головы списка, то есть тот,
|
||
к которому дольше всех не было обращений
|
||
60000-80000 кэш для таблицы FAT (100h секторов)
|
||
80000-90000 текущий кластер текущей рассматриваемой папки
|
||
90000-... кэш для содержимого папок (каждой папке отводится
|
||
2000h байт = 100h входов, одновременно в кэше
|
||
может находиться не более 8 папок;
|
||
точный размер определяется размером доступной
|
||
физической памяти - как правило, непосредственно
|
||
перед A0000 размещается EBDA, Extended BIOS Data Area)
|
||
|
||
=====================================================================
|
||
|
||
Основной процесс загрузки.
|
||
Точка входа (start): получает управление от BIOS при загрузке, при этом
|
||
dl содержит идентификатор диска, с которого идёт загрузка
|
||
1. Настраивает стек ss:sp = 0:7C00 (стек располагается непосредственно перед
|
||
кодом), сегмент данных ds = 0, и устанавливает ss:bp на начало
|
||
бутсектора (в дальнейшем данные будут адресоваться через [bp+N] -
|
||
это освобождает ds и экономит на размере кода). Сохраняет в стеке
|
||
идентификатор загрузочного диска для последующего обращения
|
||
через byte [bp-2].
|
||
2. LBA-версия: проверяет, поддерживает ли носитель LBA, вызовом функции 41h
|
||
прерывания 13h. Если нет, переходит на код обработки ошибок с
|
||
сообщением об отсутствии LBA.
|
||
CHS-версия: определяет геометрию носителя вызовом функции 8 прерывания 13h и
|
||
записывает полученные данные поверх BPB. Если вызов завершился ошибкой,
|
||
предполагает уже существующие данные корректными.
|
||
3. Вычисляет начало данных FAT-тома, сохраняет его в стек для последующего
|
||
обращения через dword [bp-10]. В процессе вычисления узнаёт начало
|
||
первой FAT, сохраняет и его в стек для последующего обращения через
|
||
dword [bp-6].
|
||
4. (Заканчивая тему параметров в стеке) Помещает в стек dword-значение -1
|
||
для последующего обращения через dword [bp-14] - инициализация
|
||
переменной, содержащей текущий сектор, находящийся в кэше FAT
|
||
(-1 не является валидным значением для номера сектора FAT).
|
||
5. Ищет в корневой папке элемент kordldr.f32. Если не находит - переходит на
|
||
код обработки ошибок с сообщением о ненайденном загрузчике.
|
||
Замечание: на этом этапе загрузки искать можно только в корневой
|
||
папке и только имена, заданные в формате файловой системе FAT
|
||
(8+3 - 8 байт на имя, 3 байта на расширение, все буквы должны
|
||
быть заглавными, при необходимости имя и расширение дополняются
|
||
пробелами, разделяющей точки нет, завершающего нуля нет).
|
||
6. Загружает первый кластер файла kordldr.f32 по адресу 0:7E00 и передаёт
|
||
ему управление. При этом в регистре eax оказывается абсолютный
|
||
номер первого сектора kordldr.f32, а в cx - число считанных секторов
|
||
(равное размеру кластера).
|
||
|
||
Вспомогательные процедуры бутсектора.
|
||
Код обработки ошибок (err):
|
||
1. Выводит строку с сообщением об ошибке.
|
||
2. Выводит строку "Press any key...".
|
||
3. Ждёт нажатия any key.
|
||
4. Вызывает int 18h, давая шанс BIOSу попытаться загрузиться откуда-нибудь ещё.
|
||
5. Для подстраховки зацикливается.
|
||
|
||
Процедура чтения кластера (read_cluster):
|
||
на входе должно быть установлено:
|
||
ss:bp = 0:7C00
|
||
es:bx = указатель на начало буфера, куда будут прочитаны данные
|
||
eax = номер кластера
|
||
на выходе: ecx = число прочитанных секторов (размер кластера),
|
||
es:bx указывает на конец буфера, в который были прочитаны данные,
|
||
eax и старшие слова других 32-битных регистров разрушаются
|
||
Загружает в ecx размер кластера, перекодирует номер кластера в номер сектора
|
||
и переходит к следующей процедуре.
|
||
|
||
Процедура чтения секторов (read_sectors32 и read_sectors2):
|
||
на входе должно быть установлено:
|
||
ss:bp = 0:7C00
|
||
es:bx = указатель на начало буфера, куда будут прочитаны данные
|
||
eax = стартовый сектор (относительно начала логического диска
|
||
для read_sectors32, относительно начала данных
|
||
для read_sectors2)
|
||
cx = число секторов (должно быть больше нуля)
|
||
на выходе: es:bx указывает на конец буфера, в который были прочитаны данные
|
||
старшие слова 32-битных регистров могут разрушиться
|
||
0. Если вызывается read_sectors2, она переводит указанный ей номер сектора
|
||
в номер относительно начала логического диска, прибавляя номер сектора
|
||
начала данных, хранящийся в стеке как [bp-10].
|
||
1. Переводит стартовый сектор (отсчитываемый от начала тома) в сектор на
|
||
устройстве, прибавляя значение соответствующего поля из BPB.
|
||
2. В цикле (шаги 3-6) читает секторы, следит за тем, чтобы на каждой итерации
|
||
CHS-версия: все читаемые секторы были на одной дорожке.
|
||
LBA-версия: число читаемых секторов не превосходило 7Fh (требование
|
||
спецификации EDD BIOS).
|
||
CHS-версия:
|
||
3. Переводит абсолютный номер сектора в CHS-систему: сектор рассчитывается как
|
||
единица плюс остаток от деления абсолютного номера на число секторов
|
||
на дорожке; дорожка рассчитывается как остаток от деления частного,
|
||
полученного на предыдущем шаге, на число дорожек, а цилиндр - как
|
||
частное от этого же деления. Если число секторов для чтения больше,
|
||
чем число секторов до конца дорожки, уменьшает число секторов для
|
||
чтения.
|
||
4. Формирует данные для вызова int 13h (ah=2 - чтение, al=число секторов,
|
||
dh=головка, (младшие 6 бит cl)=сектор,
|
||
(старшие 2 бита cl и весь ch)=дорожка, dl=диск, es:bx->буфер).
|
||
5. Вызывает BIOS. Если BIOS рапортует об ошибке, выполняет сброс диска
|
||
и повторяет попытку чтения, всего делается не более трёх попыток
|
||
(несколько попыток нужно в случае дискеты для гарантии того, что
|
||
мотор раскрутился). Если все три раза происходит ошибка чтения,
|
||
переходит на код обработки ошибок с сообщением "Read error".
|
||
6. В соответствии с числом прочитанных на текущей итерации секторов
|
||
корректирует текущий сектор, число оставшихся секторов и указатель на
|
||
буфер (в паре es:bx корректируется es). Если всё прочитано, заканчивает
|
||
работу, иначе возвращается на шаг 3.
|
||
LBA-версия:
|
||
3. Если число секторов для чтения больше 7Fh, уменьшает его (для текущей
|
||
итерации) до 7Fh.
|
||
4. Формирует в стеке пакет для int 13h (кладёт все нужные данные командами
|
||
push, причём в обратном порядке: стек - структура LIFO, и данные в
|
||
стеке хранятся в обратном порядке по отношению к тому, как их туда
|
||
клали).
|
||
5. Вызывает BIOS. Если BIOS рапортует об ошибке, переходит на код обработки
|
||
ошибок с сообщением "Read error". Очищает стек от пакета,
|
||
сформированного на предыдущем шаге.
|
||
6. В соответствии с числом прочитанных на текущей итерации секторов
|
||
корректирует текущий сектор, число оставшихся секторов и указатель на
|
||
буфер (в паре es:bx корректируется es). Если всё прочитано, заканчивает
|
||
работу, иначе возвращается на шаг 3.
|
||
|
||
Процедура поиска элемента в папке (lookup_in_dir):
|
||
на входе должно быть установлено:
|
||
ss:bp = 0:7C00
|
||
ds:si = указатель на имя файла в формате FAT (см. выше)
|
||
eax = начальный кластер папки
|
||
bx = 0
|
||
на выходе: флаг CF определяет, удалось ли найти файл; если удалось, то
|
||
CF сброшен и es:di указывает на элемент папки
|
||
В цикле считывает кластеры папки и ищет запрошенный элемент в прочитанных
|
||
данных. Для чтения кластера использует уже описанную процедуру read_clusters,
|
||
для продвижения по цепочке кластеров - описанную далее процедуру
|
||
get_next_clusters. Данные читаются в область памяти, начинающуюся с адреса
|
||
8000:0000, при этом первые 2000h байт из данных папки (может быть, меньше,
|
||
если чтение прервётся раньше) не перекрываются последующими чтениями
|
||
(это будет использовано позднее, в системе кэширования из kordldr.f32).
|
||
Выход осуществляется в любом из следующих случаев: найден запрошенный элемент;
|
||
кончились элементы в папке (первый байт очередного элемента нулевой);
|
||
кончились данные папки в соответствии с цепочкой кластеров из FAT.
|
||
|
||
Процедура вывода на экран ASCIIZ-строки (out_string):
|
||
на входе: ds:si -> строка
|
||
В цикле, пока не достигнут завершающий ноль, вызывает функцию int 10h/ah=0Eh.
|
||
|
||
=====================================================================
|
||
|
||
Работа вспомогательного загрузчика kordldr.f32:
|
||
1. Определяет, был ли он загружен CHS- или LBA-версией бутсектора.
|
||
В зависимости от этого устанавливает смещения используемых процедур
|
||
бутсектора. Критерий проверки: в CHS-версии по адресу err находится
|
||
байт 0xE8 (машинная команда call), в LBA-версии по тому же адресу
|
||
находится байт 0x14, а адрес процедуры err другой.
|
||
2. Узнаёт размер свободной базовой памяти (т.е. свободного непрерывного куска
|
||
адресов памяти, начинающегося с 0) вызовом int 12h. В соответствии с
|
||
ним вычисляет число элементов в кэше папок. Хотя бы для одного элемента
|
||
место должно быть, отсюда ограничение в 592 Kb (94000h байт).
|
||
Замечание: этот размер не может превосходить 0A0000h байт и
|
||
на практике оказывается немного (на 1-2 килобайта) меньшим из-за
|
||
наличия дополнительной области данных BIOS "вверху" базовой памяти.
|
||
3. Инициализирует кэширование папок. Бутсектор уже загрузил какую-то часть
|
||
данных корневой папки; копирует загруженные данные в кэш и запоминает,
|
||
что в кэше есть корневая папка.
|
||
4. Инициализирует кэширование FAT. Бутсектор имеет дело с FAT в том и только
|
||
том случае, когда ему приходится загружать данные корневой папки,
|
||
не поместившиеся в один кластер. В этом случае в памяти присутствует
|
||
один сектор FAT (если было несколько обращений - последний из
|
||
использованных).
|
||
5. Если кластер равен сектору, то бутсектор загрузил только часть файла
|
||
kordldr.f32, и загрузчик подгружает вторую свою часть, используя
|
||
значения регистров на входе в kordldr.f32.
|
||
6. Загружает вторичный загрузчик kord/loader по адресу 1000:0000. Если файл не
|
||
найден, или оказался папкой, или оказался слишком большим, то переходит
|
||
на код обработки ошибок из бутсектора с сообщением
|
||
"Fatal error: cannot load the secondary loader".
|
||
Замечание: на этом этапе имя файла уже можно указывать вместе с путём
|
||
и в формате ASCIIZ, хотя поддержки длинных имён и неанглийских символов
|
||
по-прежнему нет.
|
||
7. Изменяет код обработки ошибок бутсектора на переход на метку hooked_err.
|
||
Это нужно, чтобы последующие обращения к коду бутсектора в случае
|
||
ошибок чтения не выводил соответствующее сообщение с последующей
|
||
перезагрузкой, а рапортовал об ошибке чтения, которую могло бы
|
||
как-нибудь обработать ядро.
|
||
8. Если загрузочный диск имеет идентификатор меньше 0x80,
|
||
то устанавливает al='f' ("floppy"), ah=идентификатор диска,
|
||
иначе al='h' ("hard"), ah=идентификатор диска-0x80 (номер диска).
|
||
(Говорите, дискеток с FAT32 не бывает? В чём-то Вы правы... но
|
||
уверены ли Вы, что нет загрузочных устройств, подобных дискетам,
|
||
но большего размера, и для которых BIOS-идентификатор меньше 0x80?)
|
||
Устанавливает bx='32' (тип файловой системы - FAT32).
|
||
Устанавливает si=смещение функции обратного вызова. Поскольку в этот
|
||
момент ds=0, то ds:si образуют полный адрес.
|
||
9. Передаёт управление по адресу 1000:0000.
|
||
|
||
Функция обратного вызова для вторичного загрузчика:
|
||
предоставляет возможность чтения файла.
|
||
Вход и выход описаны в спецификации на загрузчик.
|
||
1. Сохраняет стек вызывающего кода и устанавливает свой стек:
|
||
ss:sp = 0:(7C00-10), bp=7C00: пара ss:bp при работе с остальным
|
||
кодом должна указывать на 0:7C00, а -10 берётся от того, что
|
||
инициализирующий код бутсектора уже поместил в стек 10 байт параметров,
|
||
и они должны сохраняться в неизменности. (Значение [ebp-14],
|
||
"текущий сектор, находящийся в кэше FAT", не используется после
|
||
инициализации кэширования в kordldr.f32.)
|
||
2. Разбирает переданные параметры и вызывает нужную из вспомогательных
|
||
процедур (загрузки файла либо продолжения загрузки файла).
|
||
3. Восстанавливает стек вызывающего кода и возвращает управление.
|
||
|
||
Вспомогательные процедуры kordldr.f32.
|
||
Процедура получения следующего кластера в FAT (get_next_cluster):
|
||
1. Вычисляет номер сектора в FAT, в котором находится запрошенный элемент.
|
||
(В секторе 0x200 байт, каждый вход занимает 4 байта.)
|
||
2. Проверяет, есть ли сектор в кэше. Если есть, пропускает шаги 3 и 4.
|
||
3. Если нет, то в кэш нужно вставить новый элемент. Если кэш ещё не заполнен,
|
||
выделяет очередной элемент в конце кэша. Если заполнен, удаляет
|
||
самый старый элемент (тот, к которому дольше всего не было обращений);
|
||
для того, чтобы отслеживать порядок элементов по времени последнего
|
||
обращения, все (выделенные) элементы кэша связаны в двусвязный список,
|
||
в котором первым элементом является самый старый, а ссылки вперёд
|
||
указывают на следующий по времени последнего обращения.
|
||
4. Читает соответствующий сектор FAT с диска.
|
||
5. Корректирует список: текущий обрабатываемый элемент удаляется с той позиции,
|
||
где он находится, и добавляется в конец. (В случае со свежедобавленными
|
||
в кэш элементами удаления не делается, поскольку их в списке ещё нет.)
|
||
6. Считывает нужный вход в FAT, сбрасывая старшие 4 бита.
|
||
7. Сравнивает прочитанное значение с пределом: если оно строго меньше
|
||
0x0FFFFFF7, то оно задаёт номер следующего кластера в цепочке;
|
||
в противном случае цепочка закончилась.
|
||
|
||
Процедура загрузки файла (load_file):
|
||
1. Текущая рассматриваемая папка - корневая. В цикле выполняет шаги 2-4.
|
||
2. Конвертирует имя текущего рассматриваемого компонента имени (компоненты
|
||
разделяются символом '/') в FAT-формат 8+3. Если это невозможно
|
||
(больше 8 символов в имени, больше 3 символов в расширении или
|
||
больше одной точки), возвращается с ошибкой.
|
||
3. Ищет элемент с таким именем в текущей рассматриваемой папке.
|
||
а) Проверяет, есть ли такая папка в кэше папок. (Идентификация папок
|
||
осуществляется по номеру начального кластера.) Если такой папки ещё
|
||
нет, добавляет её в кэш; если тот переполняется, выкидывает папку,
|
||
к которой дольше всего не было обращений. (Для каждого элемента кэша
|
||
хранится метка от 0 до (размер кэша)-1, определяющая его номер при
|
||
сортировке по давности последнего обращения. При обращении к какому-то
|
||
элементу его метка становится нулевой, а те метки, которые меньше
|
||
старого значения, увеличиваются на единицу.)
|
||
б) Просматривает в поисках запрошенного имени все элементы из кэша,
|
||
используя процедуру из бутсектора. Если обнаруживает искомый элемент,
|
||
переходит к шагу 4. Если обнаруживает конец папки, возвращается из
|
||
процедуры с ошибкой.
|
||
в) В цикле считывает папку посекторно. При этом пропускает начальные
|
||
секторы, которые уже находятся в кэше и уже были просмотрены. Каждый
|
||
прочитанный сектор копирует в кэш, если там ещё остаётся место,
|
||
и просматривает в нём все элементы. Работает, пока не случится одно из
|
||
трёх событий: найден искомый элемент; кончились кластеры (судя по
|
||
цепочке кластеров в FAT); очередной элемент папки сигнализирует о конце
|
||
(первый байт нулевой). В двух последних случаях возвращается с ошибкой.
|
||
4. Проверяет тип найденного элемента (файл/папка): последний элемент в
|
||
запрошенном имени должен быть файлом, все промежуточные - папками.
|
||
Если текущий компонент имени - промежуточный, продвигает текущую
|
||
рассматриваемую папку и возвращается к пункту 2.
|
||
5. Проходит по цепочке кластеров в FAT и считывает все кластеры в указанный
|
||
при вызове буфер последовательными вызовами функции бутсектора;
|
||
при этом если несколько кластеров файла расположены на диске
|
||
последовательно, то их чтение объединяется в одну операцию.
|
||
Следит за тем, чтобы не превысить указанный при вызове процедуры
|
||
лимит числа секторов для чтения.
|
||
|
||
Процедура продолжения загрузки файла (continue_load_file): встроена
|
||
внутрь шага 5 load_file; загружает в регистры нужные значения (ранее
|
||
сохранённые из load_file) и продолжает шаг 5.
|