Низкоуровневое программирование для Дzenствующих

Вид материалаДокументы

Содержание


2.Загрузочный код
AH – функция (0 – сброс, 2 – чтение сектора, 3 – запись сектора); AL
CH – номер дорожки; CL
13h прерывания BIOS 10h
Бог или студент
Подобный материал:
1   2   3   4   5   6   7   8   9   ...   42

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


Приступим к созданию нашего загрузчика. Первым делом надо загрузить весь код с дискеты в оперативную память, затем передать ему управление. При этом необходимо учесть, что мы не можем воспользоваться услугами операционной системы – можно использовать лишь низкоуровневые функции 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 ; считать 1 сектор
MOV BX,7E00 ; разместить в памяти, начиная с адреса 7E00, т.е.

; следующие 512 (200h) байт после 7C00
XOR CH,CH ; обнуляем CH – дорожка 0
MOV CL,2 ; сектор 2 (1-й сектор уже считан)
XOR DX,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 ; 3 попытки повторного чтения

REPEAT:

MOV AH,2 ; функция 2 (чтение сектора)

INT 13 ; вызов прерывания BIOS 13h

JNC OK ; если ошибки не было, продолжить с OK


; Попадание сюда означает, что была ошибка чтения

DEC SI ; уменьшим число повторов на 1

JZ ERROR ; если число повторов =0, т.е. 3 безуспешные

; попытки, выход по ошибке

XOR AH,AH ; обнуляем AH - функция 0 (сброс)

INT 13 ; сброс дисковода для повторной попытки

JMP REPEAT ; повтор попытки чтения


OK: ; Сюда попадаем, если не было ошибки чтения

INC CL ; следующий сектор

CMP CL,12 ; проверяем, достигло ли число секторов 18 (12h)

JNG NEXT ; если не превысило, дальше с NEXT

; (Число секторов превысило 18):

MOV CL,1 ; установить сектор 1

INC DH ; увеличить номер головки на 1

CMP DH,1 ; проверяем, достиг ли номер головки 1

JNG NEXT ; если не превысило, дальше с NEXT

; (Номер головки превысил 1):

XOR DH,DH ; обнуляем DH: головка 0

INC CH ; увеличить номер дорожки

CMP CH,50 ; проверяем, достигнута ли дорожка 80 (50h)

JGE ERROR ; если достигнута - ошибка


NEXT:

DEC WORD PTR [7C3E] ; уменьшить счетчик читаемых

; секторов (по адресу 7C3E) на 1

JZ END ; если достигнут 0 - загрузка окончена,

; дальше перейти к END


; Сюда попадаем, если прочитаны не все сектора

ADD BX,200 ; следующий сектор разместить в памяти со

; смещением 512 (200h) байт относительно

; начала настоящего, т.е. непосредственно

; после него

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 ; сервис 3
MOV BH,0 ; страница 0 (по умолчанию)
MOV BP,(TEXT) ; адрес выводимой строки
MOV CX,0C ; вывести 12 (0Ch) символов
XOR DX,DX ; координаты (0,0)
INT 10 ; вызов прерывания BIOS 10h

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

XOR AX,AX ; устанавливаем AX=0 (функция ввода с клавиатуры)
INT 16 ; вызов BIOS

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

INT 19

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

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

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

a 7C00

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

200E:7C00

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

JMP 7C40

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

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

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

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

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

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

r cx

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

7D00

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

w

После успешной записи на экран будет выведено: 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 адекватным образом переписать на дискету.

(продолжение следует...)


#Послесловие


Иногда я задаюсь вопросом, кто круче Бог или студент, и тогда понимаю, что Бог творил мир 6 дней, и лишь на седьмой отдыхал. А студент отдыхает пол года, а творит мир за одну ночь...

До следующего выпуска, в котором мы будем создавать мир.

C уважением, Edmond/HI-TECH.