Воробьёвы

(-:

№32. Начало начАл

#Бредисловие

Последняя ночь перед...

Как таинственная и трагична последняя ночь перед страшным экзаменом. Там не дадут достать или поднять маленький обрывок бумаги, на кончике котором зашифрована, напечатана или отксеренна судьба грешной души студента. Спасения нет, шары НЕ БУДЕТ!!! Но остаётся только одна единственная ночь, чтобы изменить мир...

Сквозь тусклый свет призраки спускаются к бедной голове, что, склонившись над таинственной тетрадью, словно заклинание, повторяет непонятные слова из 21-ой буквы, в которых заключено имя страшного предмета.

Часовая стрелка незаметно скользит вперёд. Вот-вот и она уже на 12.00. Текст расползается перед глазами, духи говорят с тобою. Мистика. В чёрной чашке с надписью NestCafe бурлит чёрная жидкость. Кошмарные видение ненаступившего «завтра» висят в воздухе комнаты. Всё соединяется в ужасный единый крик: «Завтра не наступит»… а потом слышится звон… тяжёлый звон из реальности, звон будильника…. Наступило последнее утро.

Мистика, неправад ли? WASM.RU спешит поздравить всех Дzenствующих студентов с окончанием кошмаров по имени "сессия". А всем поступающим желает познать её таинство.

И вот теперь, когда наступило лето, (а у кого-то может быть идёт практика, или сборы на военной кафедре), когда душа поёт и ликует, самое время занятся безумством. Тем более, если это замечательно безумство программирование на ASSEMBLERе.

Как утверждает сам Serrgio[HI-TECH], программирование на ассемблере не только делает ум гибким и лёгким, а так же повышает кислотно-щелочной баланс, выводит шлаки, позволяет закодировать от курения, выпивания и других забавных занятий, а кроме того, улучшает внутренний и внешний товаробмен веществ. А самое главное увеличивает мужскую силу и женское обаяние (я ничего не забыл? Если да присылайте свои объявления по адресу dzenreclama@kanal.ort).

И вот, чтобы хорошо расслабится, я предлагаю, заняться ещё более безумным делом чем программирование на ассемблере. Вы наверное удивитесь: "А что такое возможно?". О, да!!! Такое возможно. Читайте далее.

Рустам Галиев

Нестандартный загрузчик

(часть 1)

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

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

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

Совершенно очевидно, что из-за ограниченного объема сектора (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 (сигнатура – признак загрузочного сектора логического диска). Желающие могут посмотреть на эти данные, воспользовавшись программой diskedit.exe из пакета Norton Utilities, и прочитать с ее помощью физический сектор №1 любой форматированной дискеты. Данные блока параметров BIOS должны присутствовать всегда, поскольку Загрузчик BIOS проверяет его целостность.

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

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

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

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

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

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

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