Нестандартный загрузчик — Архив WASM.RU

Все статьи

Нестандартный загрузчик — Архив WASM.RU

Данная статья предназначена для тех, кто хочет подробнее узнать о процессе начальной загрузки компьютера и поэкспериментировать с ним. Никакого специального программного обеспечения не потребуется: все описываемые действия проделываются с использованием отладчика debug, входящего в состав Windows 95/98. Проделав описанные в статье действия на своем компьютере, вы сможете создать вспомогательную программу wb.com и установить в реестре ее ассоциацию с расширением файлов .bot, что позволит одним щелчком мыши переносить созданные с помощью debug файлы на дискету, сделав ее загружаемой.

Начальная загрузка

После проверки программой BIOS подключенного оборудования управление передается процедуре начальной загрузки с дисков, доступ к которой можно получить через прерывание INT 19h. Данная процедура загружает всего один сектор (первый) с нулевой дорожки нулевой головки, размещая его в памяти по адресу 7С00h, и передавая управление после загрузки сектора по этому же адресу. Ответственность за дальнейшую загрузку компьютера ложится на код, содержащийся в этом секторе.

Совершенно очевидно, что из-за ограниченного объема сектора (512 байт) загрузочный код разнообразием не блещет и в основном сводится к операции загрузки с диска в оперативную память дополнительных секторов, содержащих ядро операционной системы, и передаче управления по соответствующему адресу памяти. Кроме кода загрузки, в стандартном загрузочном секторе содержится также приведенный ниже в таблице блок параметров BIOS с данными о форматировании диска.

СмещениеРазмер, байтСодержание
3h8Аббревиатура и номер версии ОС
Bh2Число байтов в секторе (512)
Dh1Число секторов в кластере
Eh2Число резервных секторов в резервной области раздела
10h1Число копий FAT в разделе (2)
11h2Количество 32-байтных дескрипторов файлов в корневом каталоге
13h2Общее число секторов в разделе
15h1Тип носителя информации (для современных дискет F0h)
16h2Количество секторов, занимаемых одной копией FAT
18h2Число секторов на дорожке
1Ah2Число головок
1Ch4Число скрытых секторов перед началом раздела (для дискет 0)
20h40 (используется FAT32)
24h1Номер дисковода (для дискет - от 0 до 3)
25h10 (для Windows NT)
26h1Признак расширенной загрузочной записи (29h)
27h4Номер логического диска (создается при форматировании)
2Bh11Метка диска (текстовая строка)
36h8Аббревиатура типа файловой системы
3EhНачало загрузочного кода

Первые три байта загрузочного сектора содержат инструкцию безусловного перехода (JMP) на код, начинающийся после блока параметров BIOS. Кроме того, последними двумя байтами являются 55h и AAh (сигнатура - признак загрузочного сектора логического диска).

Загрузочный код

Приступим к созданию нашего загрузчика. Первым делом надо загрузить весь код с дискеты в оперативную память, затем передать ему управление. При этом необходимо учесть, что мы не можем воспользоваться услугами операционной системы - можно использовать лишь низкоуровневые функции BIOS. Для работы с дисками существуют функции прерывания INT 13h, при вызове которых используются следующие регистры:

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 должна находится команда безусловного перехода

JMP 7C40

Для последовательного чтения секторов организуем цикл, после каждой итерации значение по адресу 7C3E будем уменьшать на 1, пока оно не достигнет 0 (это и будет сигналом выхода из цикла). Необходимо также предусмотреть сообщение об ошибке на случай трех последовательных безуспешных попыток чтения сектора; для этой цели используем незамысловатый текст "Read error" (необходимо помнить, что при начальной загрузке кодовая таблица с кириллицей в видеопамять не загружена, поэтому для сообщений нельзя использовать русский шрифт).

Итак, по адресу 7C40 размещается следующий код (все числа в коде - шестнадцатеричные):

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, цикл повторяется:

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. Данная функция выводит символьную строку. В регистрах должны быть следующие значения:

AH - функция (13h);
AL - один из 4 возможных сервисов (мы используем 3 - вывод символа с атрибутом и перевод курсора);
BH - страница видеопамяти (обычно 0);
BP - адрес выводимой строки (в паре ES:BP);
CX - длина строки (в символах);
DX - координаты на экране (DH - строка, DL - столбец). 

Итак, ошибку обрабатывает следующий код:

ERROR:MOV AH,13 
 MOV AL,3 
 MOV BH,0 
 MOV BP,(TEXT) ; адрес выводимой строки
 MOV CX,0C 
 XOR DX,DX 
 INT 10 

После вывода текста необходимо приостановить выполнение программы до того момента, пока пользователь не прочтет сообщение и явным образом не даст сигнал, что можно продолжать дальше. Обычно для этого используют запрос на ввод данных с клавиатуры через прерывание BIOS 16h. Выполнение программы при этом приостанавливается до тех пор, пока не будет нажата какая-нибудь клавиша на клавиатуре. Итак:

 XOR AX,AX 
 INT 16 

Программа завершилась, что дальше? Обычно в этом месте ставят команду возврата в операционную систему. Однако, в нашем случае это не имеет смысла - операционная система не загружена. Поэтому просто поставим команду перезагрузки системы:

 INT 19

Если дискета оставлена в дисководе, наша программа загрузится, и будет выполнена снова; если ее вытащить, загрузится обычная ОС (DOS или Windows).

Пора приступить к вводу и ассемблированию программы; для этого, как я уже говорил, используем отладчик debug. Однако сначала нужно вместо текстовых меток расставить действительные значения адресов памяти, а сделать это можно только с помощью "двух проходов". При первом проходе вместо реальных значений адресов подставляются произвольные числа; необходимо только следить, чтобы они отличались от настоящих адресов не слишком радикально (особенно в случаях команд ближних условных переходов: их можно использовать лишь при переходах в пределах 128 байт). Составляем таблицу, в которую вписываем все метки, встречающиеся в нашей программе; по мере набора программы и "прохождения" соответствующих меток проставляем в таблице рядом с каждой меткой ее действительный адрес. Затем повторно вводим все те команды, которые содержали метки, уже с реальными значениями адресов.

В случае с "реальным" ассемблером всей этой канителью занимается компилятор. В нашем случае, поскольку вся эта работа была уже проделана автором, вы можете просто воспользоваться уже готовыми адресами, поверив мне на слово, что это действительно те самые адреса, которые нужны. Итак, начинаем работать. Щелкните на кнопке "Start" ("Старт") и выберите пункт "Run" ("Выполнить"). Наберите в командной строке debug. Откроется окно сеанса DOS с черточкой - приглашением отладчика debug. Программа должна начинаться у нас с адреса 7C00, поэтому набираем:

a 7C00

В ответ появится что-то вроде

200E:7C00

Эти числа означают адрес вводимой команды в виде сегмент:смещение. Адрес сегмента в вашем случае будет другим, но это не играет роли. Внимание обращать следует лишь на смещение. Вводим нашу первую команду:

JMP 7C40

После ввода каждой команды нажимаем <Enter>; в ответ debug выдаст адрес следующей вводимой команды. В нашем случае, после первой команды следует область блока параметров BIOS, которую следует пропустить. Для этого еще раз нажимаем <Enter>, затем, чтобы перейти к вводу команд с адреса 7C40, набираем:

а 7C40

Вот листинг оставшейся части программы (учтите, что некоторые одинаковые команды могут обозначаться по-разному, например, JNC и JNB, JNG и JLE и др., так что здесь нет ошибки):

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 с добавленными байтами-атрибутами) вводится следующим образом:

200E:7C9B DW 752,765,761,764,720,765,772,772,76F,772
200E:7CAF

Наконец, в последних двух байтах первого сектора должна быть сигнатура. Нажимаем еще раз <Enter>, затем вводим:

a 7CFE
200E:7CFE DB 55,AA
200E:7E00

Строго говоря, это еще не вся программа. Если вы вспомните, после загрузки всех секторов управление передается по адресу 7E00, а у нас по этому адресу ничего нет, да и никаких других секторов тоже нет. Тем не менее, мы можем сохранить этот кусок и использовать его в виде шаблона при создании других программ. В этом случае все, что нам потребуется, - это добавить код, начиная с адреса 7E00 - и независимо от того, сколько места он будет занимать, при записи на дискету наша программа сможет без всякой посторонней помощи сама себя загрузить и выполнить.

Сначала надо дать нашему шаблону имя, скажем, "template.bot". О расширении .bot немного позже, сейчас же присвоим это имя:

n template.bot

И сохраним его; для этого сначала надо в регистр CX внести размер сохраняемой программы. Размер определяется вычитанием из последнего смещения, полученного нами при вводе программы, числа 100 (не забудьте, что все числа - шестнадцатеричные). В нашем случае это будет 7D00:

r cx <Enter>

Выводится текущее значение регистра CX и двоеточие для ввода нового значения. Вводим:

7D00 <Enter>

Теперь записываем:

w <Enter>
После успешной записи на экран будет выведено:
Writing 7D00 bytes

Шаблон готов. Попробуем создать с его помощью простейшую тестовую программу. Скопируем файл шаблона под другим именем, например, "test.bot". Теперь откроем этот файл в отладчике debug. Это удобно сделать следующим образом. В файловом менеджере DOS (например, FAR или Norton Commander) перейдите в каталог, в котором вы сохранили файл "test.bot". В командной строке наберите:

debug test.bot

Теперь по адресу 7E00 (набрав 'a 7E00') к этой программе можно добавить дополнительный код, например, такой:

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

Приступим к написанию кода. Область данных расположим в начале файла, поэтому первой командой будет безусловный переход для обхода этих данных:

JMP INVITE

Условно обозначим наши данные следующими метками (впоследствии при вводе с помощью debug подставим вместо них реальные адреса; пока же вместо адреса будем ставить соответствующую метку в скобках): выводимые сообщения - TXT1, TXT2, TXT3 и TXT4 соответственно; FIRST - первый буфер (для считывания первых (после "мусора") 512 байт bot-файла, FDD - второй буфер (для считывания загрузочного сектора дискеты), BUF - третий буфер (для последовательного копирования секторов из bot-файла на дискету); HANDLE - 2 байта (слово) для хранения дескриптора открытого файла.

Непосредственно после данных расположим универсальную процедуру вывода сообщений на экран. Для этой цели используем функцию 40h прерывания DOS 21h. При вызове этой функции в регистрах должны находится следующие значения:

AH - функция (40h);
BX - устройство вывода (1 - экран, 3 - внешнее устройство, 4 - печать);
CX - максимальное число байтов;
DX - адрес области данных.

Для каждого сообщения устанавливаются свои значения числа выводимых символов и адрес начала текста, общая же часть кода выглядит следующим образом:

TXT_OUT: MOV AH,40 
  MOV BX,1 
  INT 21 
  XOR AX,AX 
  INT 16 
  CMP DX,(TXT1) ; проверка, не выводится ли 1-е      
                  ; сообщение
  JE OPEN_FILE ; если да - открыть файл
; Сюда попадаем, если выводится одно из сообщений об ошибке
  JMP OUT ; выход из программы

Далее следуют специфические для каждого выводимого сообщения данные:

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. В регистрах должны находиться следующие значения:

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. Такую несколько громоздкую конструкцию закодируем следующим образом:

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. В регистрах должны быть следующие значения:

AH - функция (42h);
AL - точка отсчета смещения (0 - от начала файла, 1 - от текущего значения файлового указателя, 2 - от конца файла);
BX - дескриптор файла;
CX:DX - смещение в байтах (DX - младшее слово, CX - старшее). 

При ошибке, как обычно, устанавливается флаг переноса (CF).

Итак, продолжаем:

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. В регистрах должны содержаться:

AH - функция (3Fh);
BX - дескриптор файла;
CX - число байтов для чтения;
DX - адрес области ввода.

При успешном выполнении функции флаг переноса CF сбрасывается, а в регистре AX содержится число действительно прочитанных байтов. Если это число равно 0, достигнут конец файла. Итак:

MOV AH,3F 
; дескриптор файла сохранился в регистре BX после операции  
; установки файлового указателя
MOV CX,200 
MOV DX,(FIRST) ; адрес первого буфера
INT 21 
JC READ_ERR 

Следующим действием считаем первый (загрузочный) сектор дискеты. Для чтения физических секторов необходимо использовать низкоуровневые функции прерывания BIOS 13h. Мы уже проделывали это при создании файла "template.bot", поэтому это не должно вызвать проблем:

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

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), а затем восстанавливать их оттуда. Код выглядит следующим образом:

 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'.

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'):

2039:0100 JMP     0795
2039:0103 

Вводим текст первого сообщения (оно начинается со смещения 103h):

2039:0103 DB " Вставьте в дисковод А: чистую дискету и нажмите любую клавишу",0D,0A 
2039:0142

Второе сообщение начинается со смещения 142h:

2039:0142 DB "Ошибка открытия файла",0D,0A 
2039:0159

Третье сообщение со смещения 159h:

2039:0159 DB "Ошибка чтения дискеты",0D,0A 
2039:0170

Четвертое сообщение со смещения 170h:

2039:0170 DB "Ошибка записи",0D,0A
2039:017F

Далее должен следовать наш первый буфер (FIRST), его начальным смещением будет 17Fh, а его размер равен 512 (200h) байт, поэтому адресом второго буфера (FDD) будет смещение 37Fh, а третьего (BUF) - 57Fh. После него по смещению 77Fh будут два байта для дескриптора файла (HANDLE); здесь вначале должно быть число 0. Набираем 'a 77E <Enter>':

2039:077E DW 0
2039:0781

Код начинается со смещения 781h:

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 и наберите в нем следующий текст:

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 и появится знакомое нам приглашение вставить чистую дискету в дисковод А:. Вставив дискету и нажав любую клавишу, мы получим загрузочную дискету. Остается только пожелать успеха в ваших экспериментах с загрузочным сектором.

2002-2013 (c) wasm.ru