№33. Процесс загрузки: тайное становится явным

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

Сорванные башни

(часть 1)

- У нас сегодня будет заголовок или нет?
- Будет, - сказал Дрозд. - Я уже букву "К" нарисовал.
- Какую "К"? При чем здесь "К"?
- А что, не надо было?
- Я сейчас умру, - сказал Роман. - Газета называется "За
передовую магию". Покажи мне там хоть одну букву "К"!
А. и Б. Стругацкие, "Понедельник..."

#Бредисловие от WASM GROUP

(тайный архив переписки дzenствующих, полученный ХАКЕРОМ Саней за ЧАШКОЙ ПиВа)

<00E>: Ну что, рассылку делаем?
Надо вступление написать, оформить художественно и с чувством.
Про сессию что-нибудь...
<00F>: Сейчас займусь... Какой-то бред в голову лезет... 8-)))
<00E>: А ну покажь!... Хе... хе-хе-хе... Что, плющит тебя, Федор Михайлович? ;)
<00F>: Да, конкретно башню рвет... :))
<00C>: Забей, Сеня! (с) "Братва и кольцо"
<00E>: Ок, пускаю в рассылку!
<00A>: Да сразу - на сайт! :)
<00F>: ??? :-О А теперь у кого башню рвет? ХАХА!!! ЭТО - в рассылку???
<00E>: Аль у тебя что-то лучше есть для оформления?
<00F>: Мда... пока нет :( Но там много получается...
<00E>: Погоди! Зачем оформление? Сделаем сериал ;)
"Телекомпания WASM GROUP & HI-TECH GROUP презент.". Сценарий твой,
PROдюсер 00A, а я на белом окне (или коне?).
Надеюсь по сценарию главный герой красивый, умный и так далее? :))
<00F>: Не первое, и не второе, а про "так далее" точно не знаю.
<00A>: У нас сегодня будет рассылка или где?
<00E>: Или гдеееее :)))
<00F>: On my way, красный лидер!
<00E>: ;) Affirmative! Loading Dreamweaver.exe!
[Progressssssssssss50%____________]

Гарри Поттер и девайс маглов

Получив список учебников на следующий год, Гарри был удивлен. Среди "Основ магии слова" и пособия по выращиванию королевских виверн в домашних условиях были такие книги как "Современное программное обеспечение", "Секреты системного реестра" и "Вижуал Бейсик для начинающих". Ну это еще куда ни шло, но что такое "Windows для чайников" или "Основы вирусной атаки"?
В лавке магических книг эти лежали в сторонке и магическими не выглядели совершенно. Да, самые обычные магловские книги!
- А, Поттер, великий ламер, книжечки покупаешь? - раздался за спиной голос, по которому Гарри безошибочно узнал Драко Малфроя. Гарри обернулся. Да, это был Малфрой, с новенькой тележкой-самокатом, на которой лежали, кроме всех прочих учебников, яркие журналы с непонятным названием "Xakep" и книга, на обложке которой Гарри разглядел надпись "Internet". Пока Гарри подбирал слова, чтобы ответить на непонятное оскорбление (а в оскорбительности странного эпитета сомневаться не приходилось), Малфрой продолжил:
- Да, Поттер, это тебе не в квиддиш играть. Тут мозги нужны, интеллект!
- Тогда тебе-то точно ничего не светит, - отрезал Гарри и вышел из лавки под язвительный голос Малфроя:
- Посмотрим, как тебе понравятся нюки и войдозер, Поттер!
...Все выяснилось в первый учебный день. Новый предмет назывался магловедение и вел его декан Слизерина Северус Снегг. "А при чем здесь все эти windows?"- шепотом спросил Гарри у Рона.
- Минус 50 баллов Гриффиндорфу! - раздался над самым ухом голос Снегга. - Я требую внимания и не потерплю небрежного отношения к своему предмету!
Снегг прошелся по притихшему классу и откашлялся.
- Итак, мы будем изучать технику маглов. Одним из основных девайсов (то есть устройств) является электронно-вычислительная машина, или просто - компьютер. Я расскажу вам, как с его помощью можно рассчитать что угодно - даже силу действия заклинаний и зелий - за считанные доли секунды. Вы узнаете, как получить моментальное сообщение из любого города мира и как его отправить. Программистов из вас наверняка не выйдет, но пользователями вы станете и даже освоите Visual Basic. Мне эта тема близка как специалисту по защите от темных сил. Один из наших великих волшебников доказал, что в мире маглов именно компьютер является такой силой - а врага надо знать в лицо. Его работы, кстати, публиковались и у маглов - вы, Грейнджер, могли их видеть.
Тишину нарушило хихиканье Малфроя. Снегг даже бровью не повел. Рон не сдержался:
- Почему вы не делаете замечания Малфрою?
- Минус 100 баллов Гриффиндорфу! - Взревел професор Снегг. - Разговаривать, Уизли, будете, когда я вас спрошу! ...Итак, продолжим. При работе с компьютером вас подстерегает множество опасностей, одна из которых - вирусы. Это такие же программы, однако их назначение - наносить разрушения компьютерной системе. Это что-то похожее на заклинания, внесенные в список запрещенных. Программы пишут программисты. Знаете, откуда берутся программисты? Это волшебники, родившиеся в семьях обычных маглов, и не подозревающие о своих способностях. Если их матери пили пиво во время беременности, программисты рождаются Темными. Именно они и пишут вирусы. И не думайте, что предмет просто зачетный! Я договорился с ректором, и мой экзамен будет среди обязательных, когда вы будете сдавать летнюю сессию - вашу последнюю сессию в Хогвартсе. Кстати, сессия будет в конце лета - учебный план этого курса объемист, а это ваш последний год обучения и отдохнуть вы успеете. А кое-кто наотдыхается вволю. На экзамене я заражу вирусами ваши компьютеры и без колебаний отчислю любого, кто не справится! Это и вас касается, Поттер! Поттер!
Гарри, засмотревшийся на профиль Гермионы, вздрогнул.
- Может быть, я сказал, что можно ловить ворон, Поттер? Минус 50 баллов Гриффиндорфу! Или вы все знаете? Ну-ка, скажите мне, как протекает процесс загрузки?
Гарри молчал. Руку подняла, как всегда, Гермиона. На каникулах она приобрела с рук Pentium II и уже успела побродить по интернету. Знание всех языков без словаря весьма помогало. На одном из русских сайтов она видела статью, где рассказывалось о написании "Нестандартного Загрузчика", и хотя мало что поняла, в общих чертах знала. Особенно её поразило содержание второй части этой статьи, которую она запомнила особенно хорошо.

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

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

(часть 2)

3. Вспомогательная программа

Алгоритм действий следующий. При запуске программы wb.com ей в командной строке в качестве параметра передается название bot-файла, который необходимо скопировать на дискету. Далее открываем этот файл с использованием функции 3Dh прерывания DOS 21h, и сохраняем в памяти дескриптор открытого файла. Файловый указатель перемещаем в позицию 7B00h (пропуская “мусор” в начале файла). Считываем первые 512 байт (будущий загрузочный сектор), и сохраняем его в отдельном буфере, поскольку, во-первых, нам надо будет добавить туда реальный блок параметров BIOS, считанный из загрузочного сектора дискеты, а во-вторых, по смещению 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  ; функция 40h
     MOV BX,1  ; вывод на экран
     INT 21   ; вызов DOS
     XOR AX,AX  ; устанавливаем 0 в AX для прерывания
        ; BIOS 16h
     INT 16   ; приостановка программы - ожидание
        ; нажатия клавиш
     CMP DX,(TXT1)  ; проверка, не выводится ли 1-е
        ; сообщение
     JE OPEN_FILE  ; если да - переход к коду для
        ; открытия файла
    ; Сюда попадаем, если выводится одно из сообщений об ошибке
     JMP OUT   ; выход из программы

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

    INVITE:
     MOV DX,(TXT1)  ; адрес первого сообщения
       MOV CX,3F  ; 63 (3Fh) символа
     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  ; обнулить BX
     MOV BL,[80]  ; в BX - число по адресу 80h, т.е.
        ; число введенных символов
        ; (вместе с пробелом)
     XOR AX,AX  ; обнулить AX
     MOV [BX+81],AX  ; поместить 0 по адресу, равному
        ; сумме 81 и числа, хранящегося в BX
        ; - т.е. в конец имени файла
     MOV AH,3D  ; функция 3Dh (открыть файл)
    ; в AL уже находится 0 - открываем файл для чтения
     MOV DX,82  ; адрес начала имени файла (без
        ; пробела) в DTA
     INT 21   ; вызов DOS
     JC FILE_ERR  ; при ошибке вывести сообщение
     MOV [HANDLE],AX  ; сохранить дескриптор файла по
        ; адресу [HANDLE]  
      

Следующим действием необходимо установить файловый указатель на значение 7B00h байт от начала файла. Для управления файловым указателем предназначена функция 42h прерывания DOS 21h. В регистрах должны быть следующие значения:

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

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

     MOV AH,42  ; функция установки файлового указателя
     XOR AL,AL  ; обнулить AL - отсчет смещения от начала файла
     MOV BX,[HANDLE]  ; дескриптор файла
     XOR CX,CX  ; смещение меньше 65535 - старшее слово = 0
     MOV DX,7B00  ; смещение 7B00h байт 
     INT 21   ; вызов DOS
     JC READ_ERR  ; при ошибке вывести сообщение  
      

Дальше нам нужно прочитать первые 512 байт bot-файла. Для чтения файла используется функция 3Fh прерывания DOS 21h. В регистрах должны содержаться:

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

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

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

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

     MOV SI,3  ; 3 попытки при ошибках
    RETRY:
     MOV AH,2  ; функция 2 - чтение
     MOV AL,1  ; один сектор 
     MOV BX,(FDD)  ; адрес второго буфера
     XOR CH,CH  ; дорожка 0
     MOV CL,1  ; сектор 1
     XOR DX,DX  ; диск А: (0), головка 0
     INT 13   ; вызов BIOS
     JNC OK   ; если все успешно, продолжить с OK
    ; Сюда попадаем при ошибке чтения
     XOR AH,AH  ; функция 0 - сброс дисковода
     DEC SI   ; число попыток уменьшить на 1
     CMP SI,0  ;достигло 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  ; копировать 3Bh байтов
     REP MOVSB  ; пересылка данных
    ; установить в счетчике секторов
    ; (по смещению 3Eh в первом буфере) значение 1 
     MOV WORD PTR [(FIRST)+3E],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  ; функция чтения DOS
     MOV BX,[HANDLE]  ; загрузить дескриптор файла
     MOV CX,200  ; 512 (200h) байт
     MOV DX,(BUF)  ; адрес буфера ввода-вывода
     INT 21   ; вызвать DOS
     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  ; функция записи сектора BIOS
     MOV AL,1  ; один сектор
     MOV BX,(BUF)  ; адрес буфера
     XOR DL,DL  ; диск А: (0)
     INT 13   ; вызов BIOS
     JNC OK2   ; если не было ошибки - дальше с OK2
    ; Сюда попадаем, если была ошибка записи
     XOR AH,AH  ; подготовка к сбросу дисковода
     DEC SI   ; уменьшить счетчик повторных попыток
     CMP SI,0  ; число оставшихся повторов = 0?
     JE WRITE_ERR  ; если да - выдать сообщение об ошибке
    ; Сюда попадаем, если необходимо повторить попытку записи
     INT 13   ; вызов BIOS - сброс дисковода (AH=0)
     JMP REWRITE  ; повтор попытки записи

    OK2:    ; Очередной сектор был успешно записан
     INC CL   ; увеличить номер сектора
     CMP CL,12  ; номер сектора превысил 18 (12h)?
     JNG NEXT  ; если не превысил - продолжить с NEXT
    ; номер сектора превысил 18:
     MOV CL,1  ; установить сектор 1
     INC DH   ; увеличить номер головки
     CMP DH,1  ; номер головки превысил 1?
     JNG NEXT  ; если не превысил - продолжить с NEXT
    ; номер головки превысил 1:
     XOR DH,DH  ; установить головку 0
     INC CH   ; увеличить номер дорожки
     CMP CH,50  ; номер дорожки достиг 80 (50h)?
     JG WRITE_ERR  ; если достиг - ошибка

    NEXT:
    ; увеличить счетчик записанных секторов 
    ;(по смещению 3Eh от начала первого буфера новая итерация цикла)
     INC WORD PTR [(FIRST)+3E]
     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:
    ; Выход из программы. Необходимо закрыть файл, если он
    ; был открыт.
    ; если файл был открыт, дескриптор файла не равен 0
     CMP WORD PTR [HANDLE],0
     JE END   ; файл не был открыт - выход
    ; Файл был открыт - необходимо его закрыть
     MOV AH,3E  ; функция 3Eh - закрытие файла
     MOV BX,[HANDLE]  ; передать дескриптор файла
     INT 21   ; вызов DOS
    END:
     MOV AH,4C  ; функция возврата в DOS
     INT 21

Чтобы приступить к вводу программы с использованием debug, необходимо, как и в случае с программой “template.bot”, вычислить все адреса и подставить их вместо меток. Разница же между двумя программами в том, что на этот раз мы создаем com-программу, поэтому вводить надо начинать со смещения 100h (команда отладчика ‘a 100’):

    2039:0100 JMP     0795
    2039:0103 

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

    2039:0103 DB 82,E1,E2,A0,A2,EC,E2,A5,20,A2,20,A4,A8,E1,AA,AE 
    2039:0113 DB A2,AE,A4,20,80,3A,20,E7,A8,E1,E2,E3,EE,20,A4,A8
    2039:0123 DB E1,AA,A5,E2,E3,20,A8,20,AD,A0,A6,AC,A8,E2,A5,20
    2039:0133 DB AB,EE,A1,E3,EE,20,AA,AB,A0,A2,A8,E8,E3,0D,0A 
    2039:0142

Второе сообщение (“Ошибка открытия файла”) начинается со смещения 142h:

    2039:0142 DB 8E,E8,A8,A1,AA,A0,20,AE,E2,AA,E0,EB,E2,A8,EF,20
    2039:0152 DB E4,A0,A9,AB,A0,0D,0A
    2039:0159

Третье сообщение (“Ошибка чтения дискеты”) со смещения 159h:

    2039:0159 DB 8E,E8,A8,A1,AA,A0,20,E7,E2,A5,AD,A8,EF,20,A4,A8
    2039:0169 DB E1,AA,A5,E2,EB,0D,0A
    2039:0170

Четвертое сообщение (“Ошибка записи”) со смещения 170h:

    2039:0170 DB 8E,E8,A8,A1,AA,A0,20,A7,A0,AF,A8,E1,A8,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-программа – правильно их считывает.

4. Изменения в реестре

Если все работает нормально, можно несколько повысить для себя уровень сервиса при работе с этими программами. Для начала разместим wb.com в каталоге Windows (например, C:\WINDOWS). Теперь wb можно набирать, как обычную команду операционной системы, и она будет работать независимо от каталога, в котором мы находимся. Чтобы еще больше облегчить себе работу, создадим ассоциацию bot-файлов с программой wb.com; для этого придется написать соответствующий 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 и появится знакомое нам приглашение вставить чистую дискету в дисковод А:. Вставив дискету и нажав любую клавишу, мы получим загрузочную дискету. Желающие могут создать таким образом даже собственную операционную систему – причем это можно сделать на компьютере без специальных средств программирования, с использованием одного лишь отладчика debug. Остается только пожелать успеха в ваших экспериментах с загрузочным сектором.

- Я знаю, Грейнджер, что вы все знаете! - усмехнулся Снегг. - А вот Поттер меня огорчает. Подготовте мне к экзамену, Поттер, реферат по OpenGL. Перед сессией выступите и расскажете нам, что это такое! На сегодня все свободны.

(Далi буде...)

И да пребудет с вами сила!

PS: если могут быть проблемы с Поттер (R), заменяем по всему тексту Поттер на Портер, "Рон Уизли" на "Ром Уизки", "Гермиона Грейнджер" на "Героину Гранжер", ну и так далее :))

© 2014-2017 Сергей Воробьев

0.02