Нестандартный загрузчик — Архив WASM.RU
Данная статья предназначена для тех, кто хочет подробнее узнать о процессе начальной загрузки компьютера и поэкспериментировать с ним. Никакого специального программного обеспечения не потребуется: все описываемые действия проделываются с использованием отладчика debug, входящего в состав Windows 95/98. Проделав описанные в статье действия на своем компьютере, вы сможете создать вспомогательную программу wb.com и установить в реестре ее ассоциацию с расширением файлов .bot, что позволит одним щелчком мыши переносить созданные с помощью debug файлы на дискету, сделав ее загружаемой.
Начальная загрузка
После проверки программой BIOS подключенного оборудования управление передается процедуре начальной загрузки с дисков, доступ к которой можно получить через прерывание INT 19h. Данная процедура загружает всего один сектор (первый) с нулевой дорожки нулевой головки, размещая его в памяти по адресу 7С00h, и передавая управление после загрузки сектора по этому же адресу. Ответственность за дальнейшую загрузку компьютера ложится на код, содержащийся в этом секторе.
Совершенно очевидно, что из-за ограниченного объема сектора (512 байт) загрузочный код разнообразием не блещет и в основном сводится к операции загрузки с диска в оперативную память дополнительных секторов, содержащих ядро операционной системы, и передаче управления по соответствующему адресу памяти. Кроме кода загрузки, в стандартном загрузочном секторе содержится также приведенный ниже в таблице блок параметров BIOS с данными о форматировании диска.
Смещение Размер, байт Содержание 3h 8 Аббревиатура и номер версии ОС Bh 2 Число байтов в секторе (512) Dh 1 Число секторов в кластере Eh 2 Число резервных секторов в резервной области раздела 10h 1 Число копий FAT в разделе (2) 11h 2 Количество 32-байтных дескрипторов файлов в корневом каталоге 13h 2 Общее число секторов в разделе 15h 1 Тип носителя информации (для современных дискет F0h) 16h 2 Количество секторов, занимаемых одной копией FAT 18h 2 Число секторов на дорожке 1Ah 2 Число головок 1Ch 4 Число скрытых секторов перед началом раздела (для дискет 0) 20h 4 0 (используется FAT32) 24h 1 Номер дисковода (для дискет - от 0 до 3) 25h 1 0 (для Windows NT) 26h 1 Признак расширенной загрузочной записи (29h) 27h 4 Номер логического диска (создается при форматировании) 2Bh 11 Метка диска (текстовая строка) 36h 8 Аббревиатура типа файловой системы 3Eh Начало загрузочного кода Первые три байта загрузочного сектора содержат инструкцию безусловного перехода (JMP) на код, начинающийся после блока параметров BIOS. Кроме того, последними двумя байтами являются 55h и AAh (сигнатура - признак загрузочного сектора логического диска).
Загрузочный код
Приступим к созданию нашего загрузчика. Первым делом надо загрузить весь код с дискеты в оперативную память, затем передать ему управление. При этом необходимо учесть, что мы не можем воспользоваться услугами операционной системы - можно использовать лишь низкоуровневые функции BIOS. Для работы с дисками существуют функции прерывания INT 13h, при вызове которых используются следующие регистры:
Код (Text):
AH - функция (0 - сброс, 2 - чтение сектора, 3 - запись сектора); AL - число секторов (для чтения или записи); BX - буфер памяти для ввода-вывода данных (в паре ES:BX); CH - номер дорожки; CL - номер начального сектора; DH - номер головки (для дискет - 0 или 1); DL - номер дисковода (0=А, 1=B).В случае успешного завершения функции флаг переноса (CF) сбрасывается; при ошибке он установлен.
Работа с дисководом для гибких дисков имеет свои особенности; это довольно медленное устройство, дискеты можно вынимать, поэтому ошибки случаются сравнительно часто (например, при обращении к дисководу мотор не успевает "разогнаться"), поэтому операции чтения или записи при ошибках обычно повторяют по 3 раза, и лишь после трех последовательных неудач выдают сообщение об ошибке конечному пользователю.
Для наших целей мы обойдемся без использования файловой системы. Используем простую линейную модель: весь код у нас будет записан последовательно за загрузочным сектором - сектора 2-18 нулевой дорожки со стороны нулевой головки (в предположении, что используется стандартная дискета на 1,44 Мб), затем сектора 1-18 нулевой дорожки со стороны первой головки; далее переходим на первую дорожку со стороны нулевой головки и т.д. Считывание, естественно, должно производиться в том же порядке. При этом необходимо где-то записать количество секторов, которые необходимо таким образом считать; сохраним это значение в двух байтах после блока параметров BIOS (со смещением 3Eh). Не забудьте, что загрузочный сектор размещается в памяти, начиная с адреса 7C00; это значит, что в памяти число секторов, которые необходимо прочитать с дискеты, будет находиться по адресу 7C3E, а с адреса 7C40 будет код. По адресу же 7C00 должна находится команда безусловного перехода
Код (Text):
JMP 7C40Для последовательного чтения секторов организуем цикл, после каждой итерации значение по адресу 7C3E будем уменьшать на 1, пока оно не достигнет 0 (это и будет сигналом выхода из цикла). Необходимо также предусмотреть сообщение об ошибке на случай трех последовательных безуспешных попыток чтения сектора; для этой цели используем незамысловатый текст "Read error" (необходимо помнить, что при начальной загрузке кодовая таблица с кириллицей в видеопамять не загружена, поэтому для сообщений нельзя использовать русский шрифт).
Итак, по адресу 7C40 размещается следующий код (все числа в коде - шестнадцатеричные):
Код (Text):
MOV AL,1 MOV BX,7E00 ; разместить в памяти, начиная с адреса 7E00, т.е. ; следующие 512 байт после 7C00 XOR CH,CH MOV CL,2 ; сектор 2 (1-й сектор уже считан) XOR DX,DX ; DH=0 (головка 0), DL=0 (дисковод 0, ; т.е. А:Начальная инициализация произведена. Далее начинается цикл, в котором читается сектор (до трех попыток при ошибке). После успешного считывания данных номер сектора (AL) увеличивается на 1, и если он превысил значение 18, устанавливается равным 1, а номер головки (DH) увеличивается на 1. Аналогично номеру сектора, если номер головки превысит 1, устанавливается 0, и увеличивается номер дорожки (CH). Номер дорожки должен находиться в пределах 0-79, т.е. не должен достигать 80 (50h); если это произошло, выдается сообщение об ошибке. Значение счетчика считываемых секторов (по адресу 7C3E) уменьшается на 1, и если оно еще не достигло 0, цикл повторяется:
Код (Text):
LOOP: MOV SI,3 REPEAT: MOV AH,2 INT 13 JNC OK ; Попадание сюда означает, что была ошибка чтения DEC SI JZ ERROR ; 3 безуспешные попытки XOR AH,AH ; INT 13 ; сброс дисковода для повторной попытки JMP REPEAT OK: ; Сюда попадаем, если не было ошибки чтения INC CL CMP CL,12 JNG NEXT ; (Число секторов превысило 18): MOV CL,1 INC DH CMP DH,1 JNG NEXT ; (Номер головки превысил 1): XOR DH,DH INC CH CMP CH,50 JGE ERROR NEXT: DEC WORD PTR [7C3E] ; уменьшить счетчик читаемых ; секторов (по адресу 7C3E) на 1 JZ END ; если достигнут 0 - загрузка окончена ; Сюда попадаем, если прочитаны не все сектора ADD BX,200 ; следующий сектор разместить в памяти непосредственно после настоящего JMP LOOP END: JMP 7E00 ; все сектора прочитаны: управление ; передается по адресу загрузки второго ; сектораОсталось только реализовать вывод сообщения об ошибке. Для этого воспользуемся функцией 13h прерывания BIOS 10h. Данная функция выводит символьную строку. В регистрах должны быть следующие значения:
Код (Text):
AH - функция (13h); AL - один из 4 возможных сервисов (мы используем 3 - вывод символа с атрибутом и перевод курсора); BH - страница видеопамяти (обычно 0); BP - адрес выводимой строки (в паре ES:BP); CX - длина строки (в символах); DX - координаты на экране (DH - строка, DL - столбец).Итак, ошибку обрабатывает следующий код:
Код (Text):
ERROR:MOV AH,13 MOV AL,3 MOV BH,0 MOV BP,(TEXT) ; адрес выводимой строки MOV CX,0C XOR DX,DX INT 10После вывода текста необходимо приостановить выполнение программы до того момента, пока пользователь не прочтет сообщение и явным образом не даст сигнал, что можно продолжать дальше. Обычно для этого используют запрос на ввод данных с клавиатуры через прерывание BIOS 16h. Выполнение программы при этом приостанавливается до тех пор, пока не будет нажата какая-нибудь клавиша на клавиатуре. Итак:
Код (Text):
XOR AX,AX INT 16Программа завершилась, что дальше? Обычно в этом месте ставят команду возврата в операционную систему. Однако, в нашем случае это не имеет смысла - операционная система не загружена. Поэтому просто поставим команду перезагрузки системы:
Код (Text):
INT 19Если дискета оставлена в дисководе, наша программа загрузится, и будет выполнена снова; если ее вытащить, загрузится обычная ОС (DOS или Windows).
Пора приступить к вводу и ассемблированию программы; для этого, как я уже говорил, используем отладчик debug. Однако сначала нужно вместо текстовых меток расставить действительные значения адресов памяти, а сделать это можно только с помощью "двух проходов". При первом проходе вместо реальных значений адресов подставляются произвольные числа; необходимо только следить, чтобы они отличались от настоящих адресов не слишком радикально (особенно в случаях команд ближних условных переходов: их можно использовать лишь при переходах в пределах 128 байт). Составляем таблицу, в которую вписываем все метки, встречающиеся в нашей программе; по мере набора программы и "прохождения" соответствующих меток проставляем в таблице рядом с каждой меткой ее действительный адрес. Затем повторно вводим все те команды, которые содержали метки, уже с реальными значениями адресов.
В случае с "реальным" ассемблером всей этой канителью занимается компилятор. В нашем случае, поскольку вся эта работа была уже проделана автором, вы можете просто воспользоваться уже готовыми адресами, поверив мне на слово, что это действительно те самые адреса, которые нужны. Итак, начинаем работать. Щелкните на кнопке "Start" ("Старт") и выберите пункт "Run" ("Выполнить"). Наберите в командной строке debug. Откроется окно сеанса DOS с черточкой - приглашением отладчика debug. Программа должна начинаться у нас с адреса 7C00, поэтому набираем:
Код (Text):
a 7C00В ответ появится что-то вроде
Код (Text):
200E:7C00Эти числа означают адрес вводимой команды в виде сегмент:смещение. Адрес сегмента в вашем случае будет другим, но это не играет роли. Внимание обращать следует лишь на смещение. Вводим нашу первую команду:
Код (Text):
JMP 7C40После ввода каждой команды нажимаем <Enter>; в ответ debug выдаст адрес следующей вводимой команды. В нашем случае, после первой команды следует область блока параметров BIOS, которую следует пропустить. Для этого еще раз нажимаем <Enter>, затем, чтобы перейти к вводу команд с адреса 7C40, набираем:
Код (Text):
а 7C40Вот листинг оставшейся части программы (учтите, что некоторые одинаковые команды могут обозначаться по-разному, например, JNC и JNB, JNG и JLE и др., так что здесь нет ошибки):
Код (Text):
200E:7C40 MOV AL,01 200E:7C42 MOV BX,7E00 200E:7C45 XOR CH,CH 200E:7C47 MOV CL,02 200E:7C49 XOR DX,DX 200E:7C4B MOV SI,0003 200E:7C4E MOV AH,02 200E:7C50 INT 13 200E:7C52 JNB 7C5D 200E:7C54 DEC SI 200E:7C55 JZ 7C85 200E:7C57 XOR AH,AH 200E:7C59 INT 13 200E:7C5B JMP 7C4E 200E:7C5D INC CL 200E:7C5F CMP CL,12 200E:7C62 JLE 7C76 200E:7C64 MOV CL,01 200E:7C66 INC DH 200E:7C68 CMP DH,01 200E:7C6B JLE 7C76 200E:7C6D XOR DH,DH 200E:7C6F INC CH 200E:7C71 CMP CH,50 200E:7C74 JGE 7C85 200E:7C76 DEC WORD PTR [7C3E] 200E:7C7A JZ 7C82 200E:7C7C ADD BX,0200 200E:7C80 JMP 7C4B 200E:7C82 JMP 7E00 200E:7C85 MOV AH,13 200E:7C87 MOV AL,03 200E:7C89 MOV BH,00 200E:7C8B MOV BP,7C9B 200E:7C8E MOV CX,000D 200E:7C91 XOR DX,DX 200E:7C93 INT 10 200E:7C95 XOR AX,AX 200E:7C97 INT 16 200E:7C99 INT 19 200E:7C9BПо адресу 7C9B у нас должен находиться текст "Read error", который (в коде ASCII с добавленными байтами-атрибутами) вводится следующим образом:
Код (Text):
200E:7C9B DW 752,765,761,764,720,765,772,772,76F,772 200E:7CAFНаконец, в последних двух байтах первого сектора должна быть сигнатура. Нажимаем еще раз <Enter>, затем вводим:
Код (Text):
a 7CFE 200E:7CFE DB 55,AA 200E:7E00Строго говоря, это еще не вся программа. Если вы вспомните, после загрузки всех секторов управление передается по адресу 7E00, а у нас по этому адресу ничего нет, да и никаких других секторов тоже нет. Тем не менее, мы можем сохранить этот кусок и использовать его в виде шаблона при создании других программ. В этом случае все, что нам потребуется, - это добавить код, начиная с адреса 7E00 - и независимо от того, сколько места он будет занимать, при записи на дискету наша программа сможет без всякой посторонней помощи сама себя загрузить и выполнить.
Сначала надо дать нашему шаблону имя, скажем, "template.bot". О расширении .bot немного позже, сейчас же присвоим это имя:
Код (Text):
n template.botИ сохраним его; для этого сначала надо в регистр CX внести размер сохраняемой программы. Размер определяется вычитанием из последнего смещения, полученного нами при вводе программы, числа 100 (не забудьте, что все числа - шестнадцатеричные). В нашем случае это будет 7D00:
Код (Text):
r cx <Enter>Выводится текущее значение регистра CX и двоеточие для ввода нового значения. Вводим:
Код (Text):
7D00 <Enter>Теперь записываем:
После успешной записи на экран будет выведено:Код (Text):
w <Enter>Код (Text):
Writing 7D00 bytesШаблон готов. Попробуем создать с его помощью простейшую тестовую программу. Скопируем файл шаблона под другим именем, например, "test.bot". Теперь откроем этот файл в отладчике debug. Это удобно сделать следующим образом. В файловом менеджере DOS (например, FAR или Norton Commander) перейдите в каталог, в котором вы сохранили файл "test.bot". В командной строке наберите:
Код (Text):
debug test.botТеперь по адресу 7E00 (набрав 'a 7E00') к этой программе можно добавить дополнительный код, например, такой:
Код (Text):
200D:7E00 MOV AH,13 200D:7E02 MOV AL,3 200D:7E04 XOR BH,BH 200D:7E06 MOV BP,7E17 200D:7E09 MOV CX,9 200D:7E0C XOR DX,DX 200D:7E0E INT 10 200D:7E10 XOR AX,AX 200D:7E12 INT 16 200D:7E14 JMP 8000 200D:7E17 DW 753,765,763,774,76F,772,720,732,720 200D:7E29Этот код аналогичен коду, выводящему сообщение об ошибке на экран. В данном случае выводится сообщение "Sector 2"; управление передается по адресу 8000h. Ценность этого небольшого фрагмента кода в том, что его можно с небольшой модификацией разместить в различных секторах и сделать так, чтобы он выводил на экран номер сектора, в котором данный код находится. Это позволяет проконтролировать, что сектора загружаются, и передают друг другу управление нормально. Так, в третьем секторе (по адресу 8000) можно набрать этот же код, только по адресу 8006 на этот раз должна быть команда 'MOV BP, 8017' (т.е. необходимо соответственно увеличивать этот адрес на 200h). По адресу 8014 можно вставить команду 'INT 19', чтобы завершить программу; если же нужно задействовать еще один сектор, надо просто передать ему управление, например, 'JMP 8200', и т.д. В области данных необходимо также произвести изменения в соответствии с номером сектора: в третьем секторе предпоследнее число (732) должно быть 733, в четвертом - 734 и т.д. Для 10 сектора меняются уже два последних числа - '731,730' (что соответствует символам '1' и '0'). Не забудьте после добавления кода сохранить программу, предварительно записав в регистр CX ее новый размер (по последнему смещению минус 100).
Программа готова, но запустить ее в таком виде нам не удастся. Начало нашей программы должно быть записано в загрузочном секторе, причем так, чтобы сохранить блок параметров BIOS. Дело не только в том, что штатными средствами операционной системы этого сделать нельзя; дело еще и в том, что с помощью отладчика debug можно ассемблировать и сохранять небольшие исполняемые файлы, но они будут в формате COM. Формат COM предполагает, что начало файла соответствует смещению 100h, и debug записывает программу на диск соответствующим образом. Поскольку наш загрузчик (как и вообще любой загрузчик) размещается в памяти, начиная с адреса 7С00, debug сохранит в нашем bot-файле перед началом собственно программы 7B00h байт "мусора". Поэтому придется создать вспомогательную программу wb.com (от "Write Boot"), чтобы сохраненный нами файл в "формате" .bot адекватным образом переписать на дискету.
Вспомогательная программа
Алгоритм действий следующий. При запуске программы wb.com ей в командной строке в качестве параметра передается название bot-файла, который необходимо скопировать на дискету. Далее открываем этот файл с использованием функции 3Dh прерывания DOS 21h, и сохраняем в памяти дескриптор открытого файла. Файловый указатель перемещаем в позицию 7B00h (пропуская "мусор" в начале файла). Считываем первые 512 байт (будущий загрузочный сектор), и сохраняем его в отдельном буфере, поскольку, во-первых, нам надо будет добавить туда реальный блок параметров BIOS, считанный из загрузочного сектора дискеты (иначе для повторного использования в DOS или Windows дискету придется форматировать заново), а во-вторых, по смещению 3E (непосредственно после блока параметров) нам необходимо поместить число записанных на дискету секторов (после их успешной записи). Для этой цели загрузочный сектор дискеты также считываем и сохраняем в отдельном буфере; данные блока параметров BIOS (смещения с 3h по 3Eh) копируем из второго буфера в первый. Далее организуем цикл:
- считываем 512 байт из открытого нами bot-файла в третий буфер;
- записываем данные из этого буфера в соответствующий сектор дискеты;
- увеличиваем значение счетчика секторов по смещению 3Eh в первом буфере;
- когда при очередной итерации будет прочитано 0 байт (конец файла), запишем данные из первого буфера (с окончательным значением счетчика записанных секторов) в загрузочный (первый) сектор дискеты.
Обработка ошибок в программе сведена к минимуму, но при использовании файловых операций совсем обойтись без вывода сообщений невозможно. Первым делом в начале работы программы выводится сообщение: "Вставьте в дисковод А: чистую дискету и нажмите любую клавишу". Поскольку вывод осуществляется посредством функции 40h прерывания DOS 21h, можно использовать русские буквы, а также символы перевода строки, возврата каретки, табуляции и т.д. Второе сообщение выводится при ошибке открытия файла (например, если неправильно указано имя файла): "Ошибка открытия файла". Сообщение "Ошибка чтения дискеты" используется для экономии сразу в двух случаях: при ошибке чтения bot-файла выводится лишь часть сообщения ("Ошибка чтения"), при ошибке чтения с гибкого диска - все сообщение. Последнее сообщение - "Ошибка записи" - для случая безуспешной попытки записи сектора на дискету. В области данных отводятся также три буфера по 512 байт для операций чтения-записи, о которых говорилось выше. И, наконец, два байта отводятся для сохранения дескриптора открытого файла.
Приступим к написанию кода. Область данных расположим в начале файла, поэтому первой командой будет безусловный переход для обхода этих данных:
Код (Text):
JMP INVITEУсловно обозначим наши данные следующими метками (впоследствии при вводе с помощью debug подставим вместо них реальные адреса; пока же вместо адреса будем ставить соответствующую метку в скобках): выводимые сообщения - TXT1, TXT2, TXT3 и TXT4 соответственно; FIRST - первый буфер (для считывания первых (после "мусора") 512 байт bot-файла, FDD - второй буфер (для считывания загрузочного сектора дискеты), BUF - третий буфер (для последовательного копирования секторов из bot-файла на дискету); HANDLE - 2 байта (слово) для хранения дескриптора открытого файла.
Непосредственно после данных расположим универсальную процедуру вывода сообщений на экран. Для этой цели используем функцию 40h прерывания DOS 21h. При вызове этой функции в регистрах должны находится следующие значения:
Код (Text):
AH - функция (40h); BX - устройство вывода (1 - экран, 3 - внешнее устройство, 4 - печать); CX - максимальное число байтов; DX - адрес области данных.Для каждого сообщения устанавливаются свои значения числа выводимых символов и адрес начала текста, общая же часть кода выглядит следующим образом:
Код (Text):
TXT_OUT: MOV AH,40 MOV BX,1 INT 21 XOR AX,AX INT 16 CMP DX,(TXT1) ; проверка, не выводится ли 1-е ; сообщение JE OPEN_FILE ; если да - открыть файл ; Сюда попадаем, если выводится одно из сообщений об ошибке JMP OUT ; выход из программыДалее следуют специфические для каждого выводимого сообщения данные:
Код (Text):
INVITE: MOV DX,(TXT1) ; адрес первого сообщения MOV CX,3F JMP TXT_OUT ; вывести строку FILE_ERR: MOV DX,(TXT2) ; при ошибке открытия файла MOV CX,17 JMP TXT_OUT READ_ERR: MOV DX,(TXT3) ; при ошибке чтения bot-файла MOV CX,0E ; вывод лишь части TXT3 JMP TXT_OUT READ_FDD: MOV DX,(TXT3) ; при ошибке чтения дискеты MOV CX,17 ; вывод всего TXT3 JMP TXT_OUT WRITE_ERR: MOV DX,(TXT4) ; при ошибке записи на дискету MOV CX,0F JMP TXT_OUTДля открытия файла используется функция 3Dh прерывания DOS 21h. В регистрах должны находиться следующие значения:
Код (Text):
AH - функция (3Dh); AL - код доступа (0 - для чтения, 1 - для записи, 2 - для чтения и записи); DX - адрес строки с именем файла в ASCIIZ-формате.В случае ошибки устанавливается флаг переноса (CF); при успешном открытии файла он сброшен, а в регистре AX находится дескриптор открытого файла - по нему впоследствии можно обращаться к этому файлу при операциях чтения или записи.
ASCIIZ-формат представляет собой строку в кодировке ASCII, завершающуюся двоичным нулем. Имя bot-файла будет передаваться в командной строке; как получить к нему доступ? Здесь нам придется использовать так называемый префикс программного сегмента, который операционная система размещает в памяти перед каждой COM- или EXE- программой при ее запуске. Префикс программного сегмента имеет начальное смещение 0 и размер 256 (100h) байт (именно поэтому COM-файлы начинаются со смещения 100h). Начиная со смещения 80h в префиксе программного сегмента располагается область, называемая буфером передачи данных (DTA). В первом байте этого буфера размещается длина строки параметров программы. Начиная со второго байта размещаются введенные символы (если таковые имеются), а затем следует всевозможный "мусор".
Таким образом, в нашем случае после имени программы (wb.com) будет следовать пробел, затем имя bot-файла - по смещению 80h будет число, на 1 превышающее число букв в имени файла. Само имя начинается со смещения 82h. Этот адрес можно записать в регистр DX для функции открытия файла; однако сначала надо в конце имени файла (по смещению 81h + число символов в имени, т.е. число, хранящееся по адресу [80h]) поместить 0. Такую несколько громоздкую конструкцию закодируем следующим образом:
Код (Text):
OPEN_FILE: XOR BX,BX MOV BL,[80] ; в BX - число по адресу 80h, т.е. ; число введенных символов ; (вместе с пробелом) XOR AX,AX MOV [BX+81],AX ; поместить 0 по адресу, равному ; сумме 81 и числа, хранящегося в BX - т.е. в конец имени файла MOV AH,3D ; в AL уже находится 0 - открываем файл для чтения MOV DX,82 ; адрес начала имени файла (без ; пробела) в DTA INT 21 JC FILE_ERR MOV [HANDLE],AX ; сохранить дескриптор файлаСледующим действием необходимо установить файловый указатель на значение 7B00h байт от начала файла. Для управления файловым указателем предназначена функция 42h прерывания DOS 21h. В регистрах должны быть следующие значения:
Код (Text):
AH - функция (42h); AL - точка отсчета смещения (0 - от начала файла, 1 - от текущего значения файлового указателя, 2 - от конца файла); BX - дескриптор файла; CX:DX - смещение в байтах (DX - младшее слово, CX - старшее).При ошибке, как обычно, устанавливается флаг переноса (CF).
Итак, продолжаем:
Код (Text):
MOV AH,42 XOR AL,AL MOV BX,[HANDLE] XOR CX,CX MOV DX,7B00 INT 21 JC READ_ERRДальше нам нужно прочитать первые 512 байт bot-файла. Для чтения файла используется функция 3Fh прерывания DOS 21h. В регистрах должны содержаться:
Код (Text):
AH - функция (3Fh); BX - дескриптор файла; CX - число байтов для чтения; DX - адрес области ввода.При успешном выполнении функции флаг переноса CF сбрасывается, а в регистре AX содержится число действительно прочитанных байтов. Если это число равно 0, достигнут конец файла. Итак:
Код (Text):
MOV AH,3F ; дескриптор файла сохранился в регистре BX после операции ; установки файлового указателя MOV CX,200 MOV DX,(FIRST) ; адрес первого буфера INT 21 JC READ_ERRСледующим действием считаем первый (загрузочный) сектор дискеты. Для чтения физических секторов необходимо использовать низкоуровневые функции прерывания BIOS 13h. Мы уже проделывали это при создании файла "template.bot", поэтому это не должно вызвать проблем:
Код (Text):
MOV SI,3 ; 3 попытки при ошибках RETRY:MOV AH,2 MOV AL,1 ; один сектор MOV BX,(FDD); адрес второго буфера XOR CH,CH ; дорожка 0 MOV CL,1 ; сектор 1 XOR DX,DX ; диск А: (0), головка 0 INT 13 JNC OK ; Сюда попадаем при ошибке чтения XOR AH,AH ; функция 0 - сброс дисковода DEC SI CMP SI,0 JE READ_FDD ; Сюда попадаем, если нужно еще раз попытаться прочесть сектор INT 13 JMP RETRYЕсли чтение загрузочного сектора с дискеты было успешным, необходимо скопировать блок параметров BIOS из второго сектора (FDD) в первый (FIRST). Для побайтного копирования данных из одной области памяти в другую используется инструкция MOVSB в сочетании с префиксом REP. Команда MOVSB копирует байт по адресу DS:SI в новое место по адресу ES:DI, при этом значения SI и DI после пересылки байта изменяются на 1: при сброшенном (0) флаге направления DF - увеличиваются на 1, при установленном (1) - уменьшаются на 1. Префикс REP заставляет команду MOVSB повторяться столько раз, сколько записано в регистре CX (при каждом повторе значение CX уменьшается на 1). Таким образом, в CX должно быть записано число байтов, которые необходимо скопировать из одного места в другое.
Код (Text):
MOV SI,(FDD)+3 ; исходный адрес - смещение 3 от начала ; второго буфера (FDD) MOV DI,(FIRST)+3 ; конечный адрес - смещение 3 от начала ; первого буфера (FIRST) CLD ; сбросить флаг направления (SI и DI будут ; возрастать MOV CX,3B REP MOVSB MOV WORD PTR [(FIRST)+3E],1 ; установить в счетчике секторов ; (по смещению 3Eh в первом буфере) значение 1Можно считать, что первый сектор скопирован (хотя пока еще не записан физически на дискету). Для копирования оставшихся секторов организуется цикл, в котором в один и тот же буфер BUF производится сначала чтение очередных 512 байт из bot-файла с использованием уже знакомой нам функции 3Fh прерывания DOS 21h, а затем запись этих данных в соответствующий сектор на дискете уже с использованием низкоуровневой функции 3 прерывания BIOS 13h. Операция записи физического сектора аналогична операции чтения сектора; как и в случае чтения секторов в программе "template.bot", необходимо организовать смену головки и увеличение номера дорожки по мере заполнения секторов. Попытки записи также повторяются по 3 раза. Небольшая сложность лишь в том, что чередуются процесс чтения с использованием функции DOS и процесс записи сектора с использованием функции BIOS; необходимо сохранять в стеке текущие значения регистров для сектора, дорожки и головки (регистры CX и DX), а затем восстанавливать их оттуда. Код выглядит следующим образом:
Код (Text):
MOV CL,2 ; сектор 2, XOR CH,CH ; дорожка 0 XOR DH,DH ; головка 0 LOOP: PUSH CX ; сохранить в стеке текущие значения дорожки ; (CH), сектора (CL) PUSH DX ; и головки (DH) MOV AH,3F MOV BX,[HANDLE] ; загрузить дескриптор файла MOV CX,200 MOV DX,(BUF); адрес буфера ввода-вывода INT 21 JNC M1 JMP READ_ERR M1:Последняя конструкция - как раз тот случай, когда переход по адресу READ_ERR превысил 128 байт, и пришлось использовать сочетание условного и безусловного переходов. При ближних переходах можно использовать просто 'JC READ_ERR'.
Код (Text):
M1: CMP AX,00 ; проверка на конец файла JE WRITE_FIRST ; если конец - перейти на запись ; первого сектора (выход из цикла) ; Очередные данные считаны, конец файла не достигнут, ; продолжаем POP DX ; восстановить сохраненные ранее значения POP CX ; дорожки, сектора и головки. MOV SI,3 ; 3 раза для повторов REWRITE: MOV AH,3 MOV AL,1 ; один сектор MOV BX,(BUF); адрес буфера XOR DL,DL ; диск А: (0) INT 13 JNC OK2 ; Сюда попадаем, если была ошибка записи XOR AH,AH DEC SI CMP SI,0 JE WRITE_ERR; если да - выдать сообщение об ошибке ; Сюда попадаем, если необходимо повторить попытку записи INT 13 JMP REWRITE ; повтор попытки записи OK2: ; Очередной сектор был успешно записан INC CL CMP CL,12 JNG NEXT ; номер сектора превысил 18: MOV CL,1 ; установить сектор 1 INC DH ; увеличить номер головки CMP DH,1 JNG NEXT ; номер головки превысил 1: XOR DH,DH ; установить головку 0 INC CH ; увеличить номер дорожки CMP CH,50 JG WRITE_ERR NEXT: INC WORD PTR [(FIRST)+3E] ; увеличить счетчик ; записанных секторов (по смещению 3Eh от начала первого буфера JMP LOOP WRITE_FIRST: ; все данные из bot-файла переписаны в ; соответствующие сектора на дискете ; число записанных секторов ; сохранено в первом буфере. Необходимо записать лишь сам ; первый сектор. Процедура записи аналогична рассмотренной. MOV SI,3 N3: MOV AH,3 MOV AL,1 MOV BX,(FIRST) XOR DX,DX ; диск А: (0), головка 0 XOR CH,CH ; дорожка 0 MOV CL,1 ; сектор 1 INT 13 JNC OUT ; если запись успешна - выход ; Ошибка записи: XOR AH,AH DEC SI CMP SI,0 JNE N2 JMP WRITE_ERR ; число повторов = 0 - ошибка N2: INT 13 ; сброс дисковода JMP N3 ; повтор попытки записи OUT: ; Выход из программы. Необходимо закрыть файл, если он ; был открыт CMP WORD PTR [HANDLE],0 ; если файл был открыт, ; дескриптор файла не равен 0 JE END ; Файл был открыт - необходимо его закрыть MOV AH,3E MOV BX,[HANDLE] INT 21 END: MOV AH,4C INT 21Чтобы приступить к вводу программы с использованием debug, необходимо, как и в случае с программой "template.bot", вычислить все адреса и подставить их вместо меток. Разница же между двумя программами в том, что на этот раз мы создаем com-программу, поэтому вводить надо начинать со смещения 100h (команда отладчика 'a 100'):
Код (Text):
2039:0100 JMP 0795 2039:0103Вводим текст первого сообщения (оно начинается со смещения 103h):
Код (Text):
2039:0103 DB " Вставьте в дисковод А: чистую дискету и нажмите любую клавишу",0D,0A 2039:0142Второе сообщение начинается со смещения 142h:
Код (Text):
2039:0142 DB "Ошибка открытия файла",0D,0A 2039:0159Третье сообщение со смещения 159h:
Код (Text):
2039:0159 DB "Ошибка чтения дискеты",0D,0A 2039:0170Четвертое сообщение со смещения 170h:
Код (Text):
2039:0170 DB "Ошибка записи",0D,0A 2039:017FДалее должен следовать наш первый буфер (FIRST), его начальным смещением будет 17Fh, а его размер равен 512 (200h) байт, поэтому адресом второго буфера (FDD) будет смещение 37Fh, а третьего (BUF) - 57Fh. После него по смещению 77Fh будут два байта для дескриптора файла (HANDLE); здесь вначале должно быть число 0. Набираем 'a 77E <Enter>':
Код (Text):
2039:077E DW 0 2039:0781Код начинается со смещения 781h:
Код (Text):
2039:0781 MOV AH,40 2039:0783 MOV BX,0001 2039:0786 INT 21 2039:0788 XOR AX,AX 2039:078A INT 16 2039:078C CMP DX,0103 2039:0790 JZ 07BD 2039:0792 JMP 08AA 2039:0795 MOV DX,0103 2039:0798 MOV CX,003F 2039:079B JMP 0781 2039:079D MOV DX,0142 2039:07A0 MOV CX,0017 2039:07A3 JMP 0781 2039:07A5 MOV DX,0159 2039:07A8 MOV CX,000E 2039:07AB JMP 0781 2039:07AD MOV DX,0159 2039:07B0 MOV CX,0017 2039:07B3 JMP 0781 2039:07B5 MOV DX,0170 2039:07B8 MOV CX,000F 2039:07BB JMP 0781 2039:07BD XOR BX,BX 2039:07BF MOV BL,[0080] 2039:07C3 XOR AX,AX 2039:07C5 MOV [BX+0081],AX 2039:07C9 MOV AH,3D 2039:07CB MOV DX,0082 2039:07CE INT 21 2039:07D0 JB 079D 2039:07D2 MOV [077F],AX 2039:07D5 MOV AH,42 2039:07D7 XOR AL,AL 2039:07D9 MOV BX,[077F] 2039:07DD XOR CX,CX 2039:07DF MOV DX,7B00 2039:07E2 INT 21 2039:07E4 JB 07A5 2039:07E6 MOV AH,3F 2039:07E8 MOV CX,0200 2039:07EB MOV DX,017F 2039:07EE INT 21 2039:07F0 JB 07A5 2039:07F2 MOV SI,0003 2039:07F5 MOV AH,02 2039:07F7 MOV AL,01 2039:07F9 MOV BX,037F 2039:07FC XOR CH,CH 2039:07FE MOV CL,01 2039:0800 XOR DX,DX 2039:0802 INT 13 2039:0804 JNB 0812 2039:0806 XOR AH,AH 2039:0808 DEC SI 2039:0809 CMP SI,+00 2039:080C JZ 07AD 2039:080E INT 13 2039:0810 JMP 07F5 2039:0812 MOV SI,0382 2039:0815 MOV DI,0182 2039:0818 CLD 2039:0819 MOV CX,003B 2039:081C REPZ 2039:081D MOVSB 2039:081E MOV WORD PTR [01BD],0001 2039:0824 MOV CL,02 2039:0826 XOR CH,CH 2039:0828 XOR DH,DH 2039:082A PUSH CX 2039:082B PUSH DX 2039:082C MOV AH,3F 2039:082E MOV BX,[077F] 2039:0832 MOV CX,0200 2039:0835 MOV DX,057F 2039:0838 INT 21 2039:083A JNB 083F 2039:083C JMP 07A5 2039:083F CMP AX,0000 2039:0842 JZ 0887 2039:0844 POP DX 2039:0845 POP CX 2039:0846 MOV SI,0003 2039:0849 MOV AH,03 2039:084B MOV AL,01 2039:084D MOV BX,057F 2039:0850 XOR DL,DL 2039:0852 INT 13 2039:0854 JNB 0865 2039:0856 XOR AH,AH 2039:0858 DEC SI 2039:0859 CMP SI,+00 2039:085C JNZ 0861 2039:085E JMP 07B5 2039:0861 INT 13 2039:0863 JMP 0849 2039:0865 INC CL 2039:0867 CMP CL,12 2039:086A JLE 0881 2039:086C MOV CL,01 2039:086E INC DH 2039:0870 CMP DH,01 2039:0873 JLE 0881 2039:0875 XOR DH,DH 2039:0877 INC CH 2039:0879 CMP CH,50 2039:087C JLE 0881 2039:087E JMP 07B5 2039:0881 INC WORD PTR [01BD] 2039:0885 JMP 082A 2039:0887 MOV SI,0003 2039:088A MOV AH,03 2039:088C MOV AL,01 2039:088E MOV BX,017F 2039:0891 XOR DX,DX 2039:0893 XOR CH,CH 2039:0895 MOV CL,01 2039:0897 INT 13 2039:0899 JNB 08AA 2039:089B XOR AH,AH 2039:089D DEC SI 2039:089E CMP SI,+00 2039:08A1 JNZ 08A6 2039:08A3 JMP 07B5 2039:08A6 INT 13 2039:08A8 JMP 088A 2039:08AA CMP WORD PTR [077F],+00 2039:08AF JZ 08B9 2039:08B1 MOV AH,3E 2039:08B3 MOV BX,[077F] 2039:08B7 INT 21 2039:08B9 MOV AH,4C 2039:08BB INT 21 2039:08BD <Enter>Дадим программе имя: 'n wb.com <Enter>', укажем ее размер - 'r cx <Enter>' и '7BD <Enter>', затем сохраним: 'w <Enter>'. Если при вводе не было сделано ошибок, программа должна сразу заработать в вывести на экран первое сообщение. Первое условие, однако, маловероятно, поэтому, скорее всего, программу придется отлаживать. Отладка больших (сравнительно) программ на ассемблере - занятие неблагодарное, особенно без специальных инструментальных средств; поэтому имеет смысл наращивать нашу программу "кусками", на каждом этапе проверяя набранное и добиваясь работоспособности программы. Для этого можно завершающий фрагмент кода ('MOV AH,4C' и 'INT 21') ставить после набора очередного смыслового блока, сохранить в таком виде и попробовать запустить (при этом не надо забывать подставлять в соответствующих местах вместо ссылок вперед на несуществующий еще код адрес этого завершающего фрагмента).
В частности, подобную процедуру можно проделать, набрав данные и процедуру вывода сообщений (до адреса 7BDh), затем набрав процедуру открытия файла (до адреса 7D5h), затем после перемещения файлового указателя (до адреса 7E6h), после чтения загрузочного сектора (до адреса 812h), после цикла копирования секторов (до адреса 887h). На каждом этапе "своя" часть функциональности должна быть обеспечена. В любом случае должно выводиться начальное сообщение. Затем, если в командной строке не было указано имя существующего файла (при необходимости с полным путем к нему), должно выводиться сообщение "Ошибка открытия файла". После реализации чтения загрузочного сектора должно появиться обращение к дисководу и т.д.
Для отладки используем тот же debug, для этого, собственно, он и предназначен. Чтобы вывести ассемблированный код, необходимо набрать 'u [адрес] <Enter>' и сравнить введенный код с тем, который должен быть. Особенно тщательно следует следить за соответствием адресов ссылок и данных. При попытке дизассемблировать область данных мы получим бессмыслицу - для отображения данных служит команда 'd [адрес] <Enter>', данные отображаются в виде шестнадцатиричных чисел.
Обработка ошибок в нашей программе сведена к минимуму, поэтому она требует корректного к себе обращения. При запуске вместе с названием файла wb (расширение указывать не обязательно) через один пробел должно следовать название bot-файла. Необходмо помнить, что формат загружаемого файла нашей программой не проверяется; она с одинаковым успехом сможет открыть файл любого формата - и txt, и bmp, и jpg, и exe и т.д. - и скопирует его на дискету, убрав начальные 7B00h байтов.
Отладка программы wb.com - это только полдела; она может успешно записать загрузочный сектор дискеты, а вот то, что мы туда записываем, само может потребовать отладки. Для этой цели и предназначается относительно простая программа "test.bot". Сначала эту программу можно составить так, чтобы в ней был всего один дополнительный сектор. После записи "test.bot" на дискету с помощью wb.com следует перезагрузить компьютер, оставив дискету в дисководе. При этом последовательность загрузки с помощью BIOS Setup должна быть установлена в порядке "A, C". Если надпись "Sector 2" отображается, можно добавить еще несколько секторов и проверить их работу. Данный шаг позволит убедиться, что программа wb.com правильно записывает последовательность секторов на дискету, а bot-программа - правильно их считывает.
Изменения в реестре
Если все работает нормально, можно несколько повысить для себя уровень сервиса при работе с этими программами. Для начала разместим wb.com в каталоге Windows (например, C:\WINDOWS). Теперь wb можно набирать, как обычную команду операционной системы, и она будет работать независимо от каталога, в котором мы находимся. Чтобы еще больше облегчить себе работу, создадим ассоциацию bot-файлов с программой wb.com. Желающие могут воспользоваться для этой цели проводником Windows, а для любителей программирования ;) я привожу здесь соответствующий reg-файл.
Откройте "Блокнот" Windows и наберите в нем следующий текст:
Код (Text):
REGEDIT4 [HKEY_CLASSES_ROOT\.bot] @="botfile" [HKEY_CLASSES_ROOT\botfile\Shell] @="" [HKEY_CLASSES_ROOT\botfile\Shell\open] @="" [HKEY_CLASSES_ROOT\botfile\Shell\open\command] @="C:\\WINDOWS\\wb.com %1"В последней строке вы должны указать каталог Windows на своей машине, например, C:\\Win98\\wb.com, если у вас Windows размещена в каталоге Win98. Обратите внимание, что обратная косая черта должна быть продублирована. Еще раз тщательно проверьте правильность всех данных и сохраните файл с расширением .reg, например, "bot.reg". Из проводника Windows дважды щелкните на названии этого файла; появится запрос на подтверждение изменений в реестре. Щелкните "OK".
Теперь, если не было сделано ошибок, при двойном щелчке на имени файла с расширением .bot откроется окно DOS и появится знакомое нам приглашение вставить чистую дискету в дисковод А:. Вставив дискету и нажав любую клавишу, мы получим загрузочную дискету. Остается только пожелать успеха в ваших экспериментах с загрузочным сектором. © Roustem
Нестандартный загрузчик
Дата публикации 21 июл 2003