0. Предисловие................................................... 1 1. Защита от копирования, основанная на ключевом диске........... 1 1.1 MFM запись данных гибкого диска............................. 1 1.1.1 Разделение двоичных разрядов............................. 2 1.1.2 Поле синхронизации и байт идентификатора................. 2 1.1.3 Полный формат дорожки.................................... 3 1.2 Контроллер гибкого диска INTEL 8272A........................ 3 1.2.1 Регистры 8272A........................................... 4 1.2.2 Обзор команд 8272A....................................... 4 1.2.3 Описание команд контроллера 8272A........................ 7 1.2.4 Контроллер гибкого диска AT.............................. 10 1.2.5 "Нормальные" форматы дискеты PC.......................... 11 1.3 Способ времянезависимой защиты.............................. 12 1.3.1 Дополнительные или отсутствующие сектора................ 12 1.3.2 Слабые двоичные разряды.................................. 13 1.3.3 Данные в промежутке...................................... 13 1.3.4 Сектора без метки адреса данных.......................... 14 1.3.5 Сектора без адресной метки идентификатора сектора........ 14 1.3.6 Сектора с плохой адресной меткой идентификатора сектора.. 15 1.3.7 Поле данных, передаваемое по адресной метке индекса...... 15 1.3.8 Многоскоростные дорожки.................................. 16 1.3.9 Доступ к данным через промежуток......................... 16 1.3.10 Сумасшедшие идеи........................................ 17 1.4 Основанные на таймере способы защиты........................ 17 1.4.1 Порядок контроля секторов................................ 17 1.4.2 Измерение скорости пересылки данных...................... 17 1.5 Защита, основанная на специальных аппаратных средствах...... 18 1.5.1 Модифицированные MFM-форматы............................. 18 1.5.2 Переразмещенные дорожки данных........................... 18 1.5.3 Нестандартные скорости пересылки......................... 18 1.6 Примеры схем защиты......................................... 19 1.6.1 Помощник, чувствующий IBM................................ 19 1.6.2 SuperLok................................................. 19 1.6.3 Cops CopyLock II......................................... 20 1.6.4 Щит PC................................................... 20 2. Защита, основанная на жестком диске........................... 21 2.1 Защита на уровне чипа....................................... 21 2.2 Защита на уровне BIOS....................................... 23 2.2.1 Изменения интерлива...................................... 23 2.2.2 Измененные номера сектора................................ 24 2.2.3 Неиспользуемые дисковые области.......................... 24 2.3 Защита на уровне DOS........................................ 24 2.3.1 Зависимость от номера кластера........................... 24 2.3.2 Неиспользуемые (зарезервированные) дисковые области...... 25 2.3.3 Неиспользуемые (округленные до размера кластера)......... 25 3. Система защиты на основной плате и системе BIOS.............. 25 3.1 Способ, основанный на данных................................ 25 3.2 Способ, основанный на времени............................... 25 4. Приложение A. Простая программа 8272A......................... 26 5. Приложение B. Простая программа тестирования HDC.............. 46 6. Приложение C. Как выяснить номер кластера..................... 55 7. Приложение D. Доступ к хвосту файла........................... 60 8. Приложение E. Как отличать основные платы..................... 60 0. Предисловие Следующее обсуждение проблем защиты копии почти полностью базиру- ется на наших собственных опытах с защищенным от копирования прог- раммным обеспечением PC и личными контактами нашими коллегами. Сле- довательно, приведенные здесь факты не могут рассматриваться как абсолютная истина (или истина вообще). Этот документ не руководство на разработке схем защиты копии, но краткое введение в проблему. Кто-либо, использующий этот документ или поставляемые с ним примеры для разработки программных или аппаратных средств защиты от копиро- вания будет нарушать лицензионное соглашение. Никакие аспекты защиты от копирования здесь не скрываются. Одна- ко, вы можете использовать эти данные только согласно закону. Если нет закона о программном обеспечении ЭВМ (как в Советском Союзе), вы должны всегда руководствоваться моральными соображениями. Мы не несем ответственность за любое неподходящее применение этого доку- мента и поставляемых с ним программ. Вы увидите, что примеры защиты несколько малочисленны и не всегда современны. (Например, замок параллельного порта - в настояще время наиболее популярное устройство защиты - не рассматривается вообще). Принимая во внимание географическое положение Новосибирска, в этом вряд ли есть наша вина. Любая работа этого вида всегда базируется на поддержке других эн- тузиастов, потому что никто не может приобрести все (или некоторые, потому что они продаются за твердую валюту) новые защищенные от ко- пирования программы. Поэтому Мы выражаем нашу глубокую благодарность всем, кто обеспечил нас защищенным программным обеспечением PC. Сергей Пашкович, Новосибирск 06 Июнь 1991 1. Защита от копирования, основанная на ключевом диске Этот тип (или, скорее, типы) защиты имеет тот же самый возраст как и сам PC. Годы развития и сложные аппаратные средства гибкого диска вызывают появление многочисленных методов защиты. Хотя недавно появился метод замка параллельного порта, методика ключевого диска кажется традиционной, что обусловлено, по крайней мере, двумя преи- муществами. Во-первых, ключевой диск может одновременно использо- ваться как дистрибутивный диск и во-вторых, этот тип защиты очень дешевый (но, тем не менее, мало чувситвительный к взлому). Так, клю- чевые диски еще могут использоваться для широко распространяемого программного обеспечения персонального использования. По-моему, в СССР преобладает ключевая защита. Чтобы понять способ защиты контроллера гибкого диска (FDC), нужно знать основные данные и операции FDC. Давайте теперь рассмотрим их. (Примечание: Разделы 1.1 и 1.2 главным образом базируются на доку- ментах фирмы INTEL "Интеллектуальная система базы данных, использую- щая 8272 ","8272 контроллер гибкого диска одинарной/двойной плот- ности ", "8272A контроллер гибкого диска одинарной/двойной плот- ности"). 1.1 MFM запись данных гибкого диска Формат изменяемой частотной модуляции (MFM) гибкого диска представлен в системе IBM 34 и часто называется "двойной плотностью записи". Термин " одинарная плотность записи" соответствует обычной частотной модуляции формата IBM 3740 (FM), который использовал 4 мкс, чтобы записать один бит данных. Оригинал MFM записывает один бит в 2-мкс ячейку, но для пятидюймовых дискет IBM PC использовалась 4-мкс ячейка. Следовательно, бесформатный размер одной дорожки будет 6.1 КБ. Так называемые диски PC "высокой плотности" просто выполняют 2-мкс битовую ячейку оригинальной MFM-спецификации. - 2 - 1.1.1 Разделение двоичных разрядов Запись данных в формате FM простая: начало каждого одноразрядного регистра определяется так называемым битом синхронизации, и факти- ческие данные записаны в центре каждой ячейки (бит данных) (См. рис. 1.1.1a ). Такая методика позволяет просто различать биты, но частот- ные затраты возрастают вдвое, так как необходимо сохранять данные. Однако, полное удаление битов синхронизации вызывовет появление большого числа нерасшированных нулевых битов из-за произвольных из- менений скорости вращения диска и генератора котроллера. │ ┌┐ ┌┐ │ ┌┐ │ │ ││ ││ │ ││ │ │ ││ ││ │ ││ │ ──────┼───────────────────────┼───────────────────────┼───── '1' '0' Рис. 1.1.1a FM-запись данных. Из-за такого недостатка, наибольшее количество битов в MFM за- писывается следующим образом: бит синхронизации записывается в нача- ле битовой ячейки, если в предыдущей и текущей ячейке не записаны биты (См. рис. 1.1.1b). Такое кодирование делает разделение битов рядов более трудной задачей, но перемещает биты синхронизации близко к началу битовой ячейки. │ |┌┐ │ |┌┐ │ | │ ┌┐ | │ │ |││ │ |││ │ | │ ││ | │ │ |││ │ |││ │ | │ ││ | │ ──────┼───────────┼───────────┼───────────┼───────────┼───── '1' '1' '0' '0' Рис. 1.1.1b MFM-запись данных. Легко увидеть, что значение и FM- и MFM-кодирования зависит от начальной позиции битовой ячейки. Например, если мы разместим биты, как показано подчеркнутыми штриховыми линиями на рис. 1.1.1b, после- довательность "1100" станет "0010". Поэтому, для обеспения однознач- ного декодирования, каждое поле данных на дорожке сопровождается по- лем синхронизации. 1.1.2 Поле синхронизации и байт идентификатора MFM-поля синхронизации состоят из 96 битов нуля (то есть , ячей- ки с битом синхронизации) и без битов данных), с последующими тремя байтами A1h (10100001b). Нулевые биты позволяют правильно найти ячейку данных, и A1 - идентифицировать начало фактических байтов данных . Хотя во время форматимрования записывается 12 нулевых бай- тов, (это значение не может изменяться программным обеспечением), фактически нужен только 1 байт (8 битов), чтобы синхронизировать би- товую ячейку. Другие 11 нулевых байтов нужны "на всякий случай". FM-поля синхронизации просты - они состоят из 48 нулевых битов нуля. (Напротив, FDC нуждно 8 битов для безопасности) . Различные поля данных (пользовательские и дополнительные) могут различаться по единственному байту сразу после поля синхронизации. Эти байты не могут смешиваться с данными пользователя, даже если последние содержат точную последовательность байтов синхрониза- ции/идентификатора, потому что эти байты (и только эти байты ) не используют стандарт синхронизирующих битов. К сожалению , мы имеем информацию относительно соответствующих битов синхронизации только для FM-кодирования. Байты идентификатора, по всей видимости, приме- няются также и в MFM. (Отметим, что в поле данных первым записан старший байт). - 3 - Описание байта идентификатора поля синхронизации: FC D7 адресная метка индекса FE C7 метка адреса идентификатора Сектора FB C7 данные сектора F8 C7 удаленные данные FE C7 идентификатор дефектной дорожки IBM Хотя в документации фирмы INTEL поле синхронизации всегда от- носят к части предшествующего промежутка, мы будем рассматривать его как часть последующего поля данных. 1.1.3 Полный формат дорожки Точка ссылки для всех дисковых операций - физическая индексная метка, которая генерируется индексным отверстием дискеты. Весь фор- мат дорожки начинающийся с физической индексной метки и может быть описыван следующим образом: - Физическая индексная метка - Прединдексный промежуток (GAP 5) - Индексная метка адреса (IAM) - Послеиндексный промежуток (GAP 1) Для n от 1 до N-1, где N - число секторов на дорожке : - Идентификатор сектора n - Промежуток после идентификатора (GAP 2) - Данные сектора n - Промежуток после данных (GAP 3) Для последнего сектора данных на дорожке : - Идентификатор сектора n - Промежуток после идентификатора (GAP 2) - Данные сектора n - Последний промежуток (GAP 4) Индексная метка адреса (которая не используется для другой цели 8272A) имеет несколько различных полей синхронизации: вместо A1h используется C2h (11000010b), за которым следует байт идентификатора FCh (11111100b). Поле идентификатора сектора содержит FEh за которым следуют одно- байтовые значения C, H, R, N, где C - номер цилиндра, H - номер го- ловки, R - номера сектора и N - код размера сектора. Эти байты (включая FEh ) сопровождаются 16-битовой циклической контрольной суммой (CRC), Размер данных пользователя в следующем поле данных мо- жет вычисляется как 128 * 2^N, то есть, N=0 определяет размер данных в 128 байтов, N=1 - 256 байтов, N=2 - 512 байтов, и так далее. C=H=R=N=FFh определяет дефектную дорожку IBM. Поле Данных содержит FBh, за которым следуют 128 * 2^N байт дан- ных пользователя и два байта CRC. Как в идентификаторе сектора, так и в поле данных CRC вычисляется по формуле: x^16 +x^12+ x^5+1 с на- чальным значением FFh (как всегда, старший бит первый) . 1.2 Контроллер гибкого диска INTEL 8272A Rumors сообщает, что первоначально PC FDC выполнялся на чипе INTEL 8272. Конечно, это может быть любой совместимый по выводам чип - 8272A, NEC цPD765, и так далее. Мы никогда не видели такого PC, так что Мы только можем сообщить об этом. Однако, чтобы обеспечить совместимость по регистрам (хорошая особенность !) с первыми PC, все большинство современных контроллеров почти одинаково для програм- миста. Команды выполняются 8272A в три последовательных фазы: фазу ко- манды, фазу выполнения и фаза результата. В течение фазы команды ЦП инструктирует 8272A, что нужно делать. В течение фазы выполнения FDC выполняет запрашиваемое действие. Все пересылки данных пользователя (если они есть) производятся в течение фазы выполнения. - 4 - За фазой выполнения следует фазоа результата, когда FDC возвра- щает данные состояния. В то время как FDC запросы данных в течение фаз команды и ре- зультата могли задерживаться до бесконечности (данные будут сохра- няться в внутренних регистрах контроллера 8272A) , все запросы FDC в течение фазы выполнения должны удовлетворяться немедленно, или FDC будет генерировать ошибку ожидания и завершит операцию. Строго гово- ря , запрос данных не может задерживаться дольше времени пересылки 8 битов. Следовательно, на 360-KБ дисководе , который работает на 250K (1K здесь = 1000) бит в секунду (Kбод), FDC будет передавать байт данных каждые 32 цs или 31250 байтов на секунду. Хотя сам 8272A может дейстовоать как в режиме прямого доступа к памяти (DMA), так и без него, только относительно быстрый ЦП спосо- бен на передачу данных с такой скоростью. Выполнение FDC в режиме не-DMA на дисках двойной плотности требует по крайней мере ЦП 80286 с тактовой частотой 6 MГц, в то время, как дискета с высокой плот- ностью AT будет требовать все 10 МГц. 1.2.1 Регистры 8272A Чип 8272A связан с ЦП через два регистра: главный регистр состо- яния (MSR) и регистр данных (DR). MSR доступен только для чтения. DR может читаться или записываться, что определяется битом RQM MSR. Значение битов в MSR приведено на рис. 1.2.1a. ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ RQM │ DIO │ NDM │ CB │ D3B │ D2B │ D1B │ D0B │ └──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘ │ │ │ │ │ │ │ └── Привод 0 занят │ │ │ │ │ │ └──────── Привод 1 занят │ │ │ │ │ └────────────── Привод 2 занят │ │ │ │ └──────────────────── Привод 3 занят │ │ │ └────────────────────────── FDC занят │ │ └──────────────────────────────── не-DMA режим │ └────────────────────────────────────── Ввод/вывод данных └──────────────────────────────────────────── Запрос на Мастера Рис. 1.2.1a Главный регистр состояния 8272A. Разряды DxB будут устанавливаться в 1, тогда соответствующий дисковод выполняет команду установки или перекалибровки. Бит CB устанавливается, тогда FDC выполняет операцию чтения или записи. NDM устанавливается, когда FDC находится в фазе выполнения и запрещены операции DMA (это указываеттакже, что большее количество данных должны подаваться или читать из DR). DIO будет единицей, если ЦП должен читать данные из DRА и нулем, если ЦП должен подавать данные в DR. RQM = 1 указывает, что DR готов переслать следующий байт. Нап- равление пересылки зависит от значения DIO. 8272A сохраняет текущий номер цилиндра во внутренних регистрах для каждого дисковода, соединенного с ним. Так как дисковод для гиб- ких дискет не может отличать один цилиндр от другого (кроме цилиндра 0), это необходимо, чтобы выполнять операции установки, которые транслируются FDC в импульс шага дисковода. Эти регистры очищаются сбросом FDC (но дисковод гибкого диска не будет возвращаться на до- рожку 0 при сбросе). Другие важные внутренние регистры содержат время скорости шага, выгрузки и загрузки головки (см. команду Определить). 1.2.2 Обзор команд 8272A Команды 8272A могут быть разделены на три группы: пересылка дан- ных пользователя, управление димком и диагностические средства. Пе- ресылка данных пользователя включает чтение данных, чтение удаленных данных, запись данных, запись удаленных данных и три команды - 5 - просмотра: просмотр на равно, просмотр на меньше или равно и просмотр на большег или равно. Команды управления диском включа- еют перекалибровку, установку, чтение состояния дисковода, чтение состояния прерывания и форматирование дорожки. Команды чтения иден- тификатора сектора и чтения дорожки могут рассматриваться, как диаг- ностика. Все команды с нераспознаваемым первым байтом будут обраба- тываться как недопустимая команда. Команды 8272A можно легко идентифицировать младшим полубайтом первого байта команды - см. таблицу 1.2.2 (хотя некоторые из таких команд будут недопустимыми). Таблица 1.2.2 . 8272A Коды операции 8272А. X1 Просмотр на равно X2 Чтение дорожки X3 Определить X4 Определить состояние дисковода X5 Записать данные X6 Читать данные X7 Прекалибровка Х8 Опросить состояние прерывания X9 Записывать удаленные данные или просмотр на меньше или равно XA Читать идентификатор XC Читать удаленные данные ХC Форматировать дорожку или просмотр на больше или равно XF Установка Главная последовательность выполнения команды 8272А состоит из следующих шагов: 0. Если DMA будет использоваться в операции, программируется ка- нал 2 микросхемы 8237A для режима пересылки одиночного байта. (При- мечание: сигнал счетчика терминала (TC) из DMA будет вызывать не- посредственное завершение FDC-операций). 1. Для каждого байта в команде ,ждите, пока бит RQM не станет = 1, затем проверьте DIO: значение 0 указывает, что FDC готов принять команду, 1 означает, что ваша команда не распозналась FDC (последую- щее чтение из DR будет возвращать 80h) или что вы уже подали все данные в FDC (а также, что ваша команда недопустима). Если ваша команда не имеет фаз выполнения и кончания (например, Определтить), вы останавливаетесь здесь. 2. Если вы запиустили FDC в режиме не-DMA и команда пересылает данные в течение фазы выполнения (чтение, запись, форматирование), вы ждете, пока бит NDM равен нулю. Когда NDM станет = 1, для каждого чтитаемого или записываемого байта вы ждете установки бита RQM, и тогда записываее следующий байт в (или читаете его из) DR. Если вы не используете пересылку DMA-режима (или не пересылаете данные вообще), вы просто переходите к шагу 3. 3. Конец фазы выполнения указывается IRQ 6 (int 0eh). Вы можете или разрешить 8272A прерывание и обнаруживать его в вашей подпрог- рамме обработки, или можете постоянно опрашивать регистр запроса прерывания 8259A (IRR). Если команда не имеет фазы выполнения (Опросить состояние диско- вода), вы переходите к шагу 4. 4. В фазе результата вы читаете состояние команды из FDC, тести- руя бит RQM (убедитесь, что DIO = 1). 8272A может возвращать до трех байтов состояния (которые описываются ниже) наряду с другими данны- ми, которые будут изменяться от команды к команде. ┌───────────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ IC │ SE │ EC │ NR │ H │ DS1 │ DS0 │ ST0 └───────────┴─────┴─────┴─────┴─────┴─────┴─────┘ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ EN │ 0 │ DE │ OR │ 0 │ ND │ MW │ MA │ ST1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ - 6 - ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ 0 │ CM │ DD │ WC │ SH │ SN │ BC │ MD │ ST2 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ FT │ WP │ RDY │ T0 │ TS │ H │ DS1 │ DS0 │ ST3 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ Рис. 1.2.2 0-3 байты состояния контроллера 8272A ST0 IC - код прерывания: 00 - Нормальное завершение команды. 01 - Аварийное завершение команды (операция еще может быть выполнена, но должны проверяться другие коды условий). 10 - Недопустимая команда. 11 - Аварийное завершение (сигнал готовности диска изменился в течение выполнения). SE - Операция установки закончена. EC - Оборудование проверяет ошибку (получен сигнал неисправности от дисковода или цилиндр 0 не найден после 77 импульсов в течение перекалибровки). NR - Не готов (Дисковод стал не готовым в течение чтения или за- писи или запрошена сторона 1 для одностороннего диска). H - Адрес головки. DS0, DS1 - адрес дисковода. ST1 EN - Ошибка конца дорожки (FDC пытается обратиться к сектору за последним сектором дорожки). Этот флаг будет устанавливаться в 1 (и, следовательно, IC будет равен 01 ), если FDC читал сектор, определяемый как параметр команды EOT и сигнал TC низкий,так что каждая операция чтения в режиме не-DMA будет c ошибкой EN. DE - Ошибка данных. Адресная метка сектора или данных содержит недопустимую CRC. OR - Ошибка ожидания. 8272A не получил доступа к ЦП или DMA в те- чение определенного интервала времени (32 мкс на 360KБ дисководе) и данные были потеряны. ND - Сектор не обнаруженный . Определяемый сектор не был обнару- жен в течение 2-х оборотов диска (то есть, с начала операции пришли два индексных импульса). Для многосекторных пересылок требуется 2-оборотное ожидание для каждого сектора отдельно. NW - Ошибка защиты записи . Сигнал защиты записи был обнаружен в течение операций запииси или форматирования. MA - Отсутствует метка адреса. Адресная метка cектора или данных не обнаружена. ST2 CM - Контрольная Метка. Обнаружена удаленная адресная метка в те- чение команды чтения данных или обнаружена адресная метка в течение команды чтения удаленных данных, и бит SK не уста- новлен. DD - Ошибка данных. Неверная CRC сектора данных. Также будет установлен DE. WC - Ошибка адресации цилиндра. Адрес цилиндра на дорожке не соответствует определяемому. SH - Успешный просмотр. Условия команды просмотра были удовлетво- рены. SN - Просмотр не удовлетворен. BC - Ошибка дефектной дорожки. Аналогично WC, но адрес цилиндра дорожки - FFh. MD - Ошибка отсутствия адресной метки. Также будет устанавли- ваться MA. - 7 - ST3 FT - Неисправность. Дисковод обнаружил неисправность. WP - Защита записи. RDY - Сигнал готовности дисковода. На PC и подсистемах дискет AT он будет всегда установлен, независимо, готов ли дисковод (или установлен вообще). T0 - Сигнал нудевой дорожки. TS - Двусторонний. На PC и подсистемах дискет AT он будет всегда 0. 1.2.3 Описание команд контроллера 8272A Мы приводим все команды 8272A в том порядке, в котором они даны в таблице "Список команд 8272A" руководства по INTEL 8272A. Конечно, это описание не достаточно для написания драйвера гибкого диска. См. руководство INTEL для более полной информации или Приложения A, где дан простой пример. Чтение данных ───────────── Команда: MT MFM SK 0 0 1 1 0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ C/H/R/N/EOT/GPL/DTL Исполнение: Поля данных на дорожке считываются. Результат : ST0/ST1/ST2/C/H/R/N (Здесь и далее: MT - (MultiTrack) инструктирует продолжение операции чтения на дорожке 1 того же цилиндра. SK - заставляет FDC пропускать поля удаленных данных. HDS - указывает номер головки, используемой в операции. DS1, DS0 - представдяют номер дисковода. C/N/R/N - идентификатор начального сектора. EOT - количество секторов на дорожке. GPL - длина межсекторного промежутка. DTL - должен быть FFh при установленном MFM.) Чтение идентификатора сектора ───────────────────────────── Команда: MT MFM SK 0 0 1 1 0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ C/H/R/N/EOT/GPL/DTL Исполнение: Считывается первый корректный идентификатор сектора (первый - значит "первый достигнутый от текущего положения головки", а не "первый после физической адресной метки") Результат: ST0/ST1/ST2/C/H/R/N Чтение удаленных данных ─────────────────────── Команда: MT MFM SK 0 1 1 0 0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ C/H/R/N/EOT/GPL/DTL Исполнение: Считываются удаленные данные на дорожке. Результат: ST0/ST1/ST2/C/H/R/N Чтение дорожки ────────────── - 8 - Команда: 0 MFM SK 0 0 0 1 0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ C/H/R/N/EOT/GPL/DTL Исполнение: Читываются поля данных на дорожке. Результат: ST0/ST1/ST2/C/H/R/N Эта команда будет читать поля данных независимо от значений C/H/R/N, хранящихся в идентификаторах секторов. Поле данных, которое имеет допустимый идентификатор, может считаться этой командой. Хотя документация INTEL указывает, что команда чтения дорожки останавли- вается, когда на всей дорожке не обнаружены поля данных или счетчик секторов достиг значения EOT, эта команда будет завершаться, когда не обнаружена метка адреса данных был обнаружен после метки адреса идентификатора сектора с плохой CRC. Команда чтения дорожки принимает ЛЮБОЕ значение N, так что может читать межсекторный промежуток (или всю дорожку, если N достаточно большое) наряду с сектором данных. Сектора будут считываться в по- рядке их появления под головкой, то есть, если дорожка форматирова- лась с 8 секторами с 512 байтами (интерлив 1:1), и вы начинаете ко- манду дорожки чтения с R = 1, N = 3, EOT = 4, сектора 1, 3, 5 и 7 будут считаны. Запись данных ───────────── Команда: MT MFM 0 0 0 1 0 1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ C/H/R/N/EOT/GPL/DTL Исполнение: На дорожку записываются поля данных Результат: ST0/ST1/ST2/C/H/R/N Запись удаленных данных ─────────────────────── Команда: MT MFM 0 0 1 0 0 1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ C/H/R/N/EOT/GPL/DTL Исполнение: На дорожку записываются поля удаленных данных Результат: ST0/ST1/ST2/C/H/R/N Форматирование дорожки ────────────────────── Команда: MT MFM 0 0 1 1 0 1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ N/SC/GPL/D Исполнение: Команда форматирует дорожку с SC секторами размером N. Сектора заполняются байтом D. Значения C/H/R/N для каждого секто- ра задаются пользователем (как в команде записи данных). Результат: ST0/ST1/ST2/C/H/R/N ПРИМЕЧАНИЕ: GPL=0 понимается как 100h. Просмотр на равно ───────────────── Команда: MT MFM SK 1 0 0 0 1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ - 9 - C/H/R/N/EOT/GPL/STP Исполнение: Поля данных на дорожке читаются и сравниваются по- байтно с данными, поставляемыми ЦП или DMA. Если условие просмотра не удовлетворено, просмотр продолжается в секторе R+STP. Результат : ST0/ST1/ST2/C/H/R/N Просмотр на меньше или равно ──────────────────────────── Команда: MT MFM SK 1 1 0 0 1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ C/H/R/N/EOT/GPL/STP Исполнение: аналогично команде "Просмотр на равно" Результат : ST0/ST1/ST2/C/H/R/N Просмотр на больше или равно ──────────────────────────── Команда: MT MFM SK 1 1 1 0 1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ C/H/R/N/EOT/GPL/STP Исполнение: аналогично команде "Просмотр на равно" Результат : ST0/ST1/ST2/C/H/R/N Перекалибровка ────────────── Команда: 0 0 0 0 0 1 1 1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 0 DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ Исполнение: пепекалибруется выбранный дисковод. Все 4 дисковода могут перекалибровываться одновременно. 8272A может выполнять операции чтения или записи на любом другом дисково- де. Таким образом, команда опроса состояния прерывания должна использоваться, чтобы различать прерывание, вызванное завершением перекалибровки от прерывания при завершении операции. FDC не будет разрешать любую команду на перекалибруемом дисководе до того, как проведен опрос состояния прерывания для установки за- вершения прерывания. Опрос состояния прерывания ────────────────────────── Команда: 0 0 0 0 1 0 0 0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ Результат: STO0/C (Примечание: когда IC = 10 (недопустимо), C не бу- дет возвращаться). 8272A будет генерировать запрос прерывания в любом из следующих случаев: A) Фаза результата начинается для команд чтения/записи/форматиро- вания/просмотра. B) Изменился сигнал готовности на одном из дисководов. C) Выполняется установка/перекалибровка. D) Требуется не-DMA пересылка данных. Программа обработки прерывания может легко отличать эти случаи: Если NDM = 1, Тогда это - запрос пересылки данных, Иначе Если CB = 1 Тогда начинается фаза результата, Иначе - 10 - Если SE = 0 Тогда это - изменение сигнала готовности, Иначе это - конец установки или перекалибровки. Определить ────────── Команда: 0 0 0 0 0 0 1 1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ SRT HUT └───────────────────────┴───────────────────────┘ HLT ND └─────────────────────────────────────────┴─────┘ SRT - интервал скорости шага. Устанавливает минимальную задержку между двумя импульсами шага дисковода. (1-16 мс, SRT = 0Fh соответствует 1 мс). Более старые дисководы PC не выполняли 3- или 4-мс скорость шага, а некоторые не могут выполнить с 2- или даже 1-мс скорость шага. HUT - время разгрузки головки. Задержка между завершением опера- ции чтения - записи и подъемом головки. (16-240 мс, HUT = 0 соответствует 16 мс). Оно обычно устанавливается в 240 мс. HLT - время загрузки головки. Задержка между командой загрузки головки и началом операции чтения - записи. (2-254 мс, HLT=1 - 2 ms). Обычно устанавливается в 2 мс. ND - 0: режим DMA. 1: не-DMA режим. Все таймеры внутри 8272A фактически синхронизируются по сигналу WR CLK, так что все значения, показанные здесь, правильны для WR CLK 500 КГц, или скорость пересылки данных составит 500 КБод. Понижение частоты WR CLK будет удлинять все внутренние счетчики задержек. Опрос состояния дисковода ───────────────────────── Команда: 0 0 0 0 0 1 0 0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ Результат: ST3. Установка ───────── Команда: 0 0 0 0 1 1 1 1 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 0 0 0 HDS DS1 DS0 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ C См. перекалибровку. 1.2.4 Контроллер гибкого диска AT Первоначальные PC и XT контроллеры гибкого диска были способный поддерживать 4 дискеты. AT FDC, хотя и совместимы по регстрам, мож- жет поддерживать только 2, так что можно устанавливать два FDC в AT. Большинство версий BIOS не поддеоживает второй FDC, и у нас никогда не было IBM AT, чтобы проверить, что же конкретно делает IBM AT BIOS. Первый FDC занимает диапазон адресов 3F0-3F7h, второй FDC на- ходится в 370-377h. Вся приведенная здесь информация по первому FDC также применима и ко второму при установке этого диапазона адресов. 8272A (или его интегральный аналог) отображается к портам 3F4h (MSR) и 3F5h (DR). Как вы уже видели, 8272A не может сам управлять состоянием двигателя дисковода, так что порт 3F2 (цифровой регистр вывода) обеспечивает такие операции. (См. рис. 1.2.4a). - 11 - ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ D │ C │ B │ A │ IE │ EC │ DS1 │ DS0 │ └──┬──┴─────┴─────┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘ └────────┬────────┘ │ │ └──┬──┘ │ │ │ └─Выбор дисковода │ │ └──────────Контроллер доступен │ └────────────────Прерывания разрешены └───────────────────────────────Двигатель доступен Рис. 1.2.4a Цифровой регистр вывода (3F2h). Биты DS1 и DS0 цифрового регистра вывода отменяют бит DS, определя- емый в команде 8272A, так что можно изменять (заменять) дисковод в те- чение выполнения команды. Хотя это опасный метод, многие способы защи- ты от копирования базируются на этой особенности. Установка бита EC в нуль сбрасывает контроллер, так любая опера- ция (например , форматирование дорожки или запись данных) могут быть остановлены в любой момент. (Хотя должна соблюдаться особая осторож- ность, чтобы выполнить резкое завершение без биений). Бит IE педназначен для применения в многоконтроллерных системах. Т.к. все FDC используют тот же самый IRQ 6 и канал 2 DMA, без- действующий контроллер может ( или не может - нельзя сказать опреде- ленно) установить эту линию в низкий уровень, таким образом запрещая другим контроллерам доступ к системному обслуживанию. Бит доступа к двигателю дисковода - другой источник способа защи- ты (См. 1.4.2). Второй дополнительный регистр находится в 3F7 (регистр управления дискеты). (Этот регистр не существует на старых XT, но может часто быть обнаружен на поздних аналогах). Как мне хорошо известно, только два младших бита значимы для записи, и одиночн старший бит - для чтения. 3F7 (запись) выбирает скорости пересылки: 00 - 500 КБод (MFM) 01 - 300 КБод (MFM) 10 - 250 КБод (MFM) 11 - 125 КБод (FM) Первые три значения (00-10) приведены в руководствах. Хотя последнее значение неописано, оно работало на любом AT FDC, с которыми я ког- да-либо сталкиваться. (Это существенная скорость пересылки - она разрешает чтение-запись SD-дисков в дисководе на 360 КБ). Значения 00-10 предназначаются для применения с MFM-дискетами, так что они имеют импульсы в 1/4 длины цикла. Синхронизация на 125 КБод имеет импульсы в 1/8 длины цикла. Т.к. FDC не имеют FM-синхронизации на 175 КБод, диски одинарной плотности не могут записываться на дисководе с высокой плотностью (360 оборотов/мин.). Благодаря высокой защищености от ошибок FM-фор- мата данных, диски одинарной плотности еще могут читаться в дисково- де на 360 об./мин. FM-дискеты могут быть как читаться, так и писаться на 360Kб (300 об./мин.) дисководе. 3F7 (чтение) - изменение состояния дисковода: установка бита 7 (проверяют al, 80h) значит, что дискета заменялась. Индикатор смены MEDIA будет сбрасываться после первой установки цилиндра, отличного от нулевого. Очевидное применение регистра 3F7 в защите от копирования от- носится к определению позиции битовой ячейки. Т.к. FDC переключается на новомю скорость пересылки почти мгновенно, переключая скорость в расчетный момент, можно сдвигать битовую ячейку на малую часть ее длины. Эта методика разрешает читать любую часть дорожки, если су- ществует по крайней мере одно поле достоверных данных и известно приблизительно местоположение представляющей интерес области. 1.2.5 "Нормальные" форматы дискеты PC Первые стандарты PC предназначались для однодвухсторонних пяти- - 12 - дюймовых дискет на 40 дорожек. Следуя спецификации INTEL, они имели 8 512-байтовых секторов на дорожке, таким образом, используя 4096 байтов (65%) из 6250-байтовой несформатной емкости дорожки. Сектора на дорожке располагались последовательно (интерлив 1:1), как во всех других стандартных форматах. Позднее диски с 9 и 10 сектораами на дорожке используют соответственно 74% и 82% общего пространства. Те- оретическое ограничение для форматируемой емкости дорожки двойной плотности - примерно 89.2% (5575 байтов или 10.89 сектора на дорож- ке). Такое увеличение емкости дисков сделалось возможным благодаря высокой стабильности вращения в современных приводах. Хотя допуска- ется отклонение в 2.5% от номинального времени вращения, мы никогда не встречали дисковод для гибких дискет с отклонением временем вра- щения выше 0.2%. Такая стабильность позволяет уменьшить длину межсекторного промежутка. Например , если возможное отклонение скорости вращения - ё2.5%, необходимо резервировать мехсекторный промежуток, достаточный, чтобы поддерживать любую длину сектора. Предположм, что дискета форматиро- вана на дисководе с наименьшией скоростью вращения, и будет модифи- цирована на дисководе с самой быстрой скоростью вращения. В таком случае необходимо резервировать по крайней мере ( 512 + 62 ) * ( 2 * 2.5% ) байт для межсекторного промежутка (62 - минимальный размер заголовка сектора для формата MFM). Это приведет к минимальному про- межутку в 29 байтов, так что даже формат 10 секторов на дорожку бу- дет нормальным при таких отклонениях. Чтобы поддерживать совместимость (по крайней мере, частичную) формата на 40 дорожек с 80-дорожечным дисководом высокой плотности (HD), первый моделируются путем пропуска всех нечетных дорожек. Сле- довательно, 360K диски, записываемые в 360K дисководе всегда можно прочитать на HD-дисководе, но 360K дискета записываемая в HD-диско- воде может быть несовместимый с 360K дисководами. Т.к. ключевые дискеты почти всегда выполняются на 360K дисково- дах, мы будем не будем рассматривать все форматы высокой плотности вообще. 1.3 Способ времянезависимой защиты Мы будет рассматривать дисковые метки защиты, которые не могут быть проверены без применения других устройств, как сам FDC. Этот способ должен работать на любом системном оборудовании с совместимым FDC. Примечание: всегда, когда мы будем упоминать бит-копировщик, это будет относиться последней знакомой нам версии. Во всяком случае, это не последние существующие версии. Мы имеем копии (запрещенные по причинам, упомянутым в предисловии) Copy II реализации PC 7.10, CopyWrite редакции сентября 1988 и TeleDisk версии 2.11. Примечание: утверждение "не может воспроизводиться", применяемое автором к той или другой метке защиты, указывает не на физические ограничения аппаратных средств PC, но на ограниченность моих знани- ий. Поэтому я буду благодарен всем, кто расширит их. 1.3.1 Дополнительные или отсутствующие сектора Так как первые дискеты МС-ДОС имели много неиспользуемого прост- ранства на дорожке, очевидная идея - сохранять дополнительный сектор на дорожке наряду с стандартными секторами МС-ДОС. Даже гораздо луч- ше упакованные форматы 10 секторов на дорожке способны размещать до- полнительный 256-байтный сектор. Другое место для размещения таких дополнительных секторов - 41-я дорожка, которая не используется ДОС, но к которой может обращаться большинство дисководов. На 80-дорожеч- ных дисководах дополнительные сектора могут скрываться на нечетных дорожках. Теперь, такая метка сама по себе не может рассматриваться как хо- - 13 - рошая защита от копирования, потому что дополнительные сектора могут быть легко обнаружены командой чтения идентификатора сектора. Любой поставляемый на рынок бит-копировщик (CopyWrite, CopyIIPC, TeleDisk, и т.д.) способен сделать это. (Хотя, сектора на нечетных дорожках не будут обнаружены некоторыми программ). Хорошие разновидности этой схемы приведены в разделах 1.3.4 - 1.3.8. 1.3.2 Слабые двоичные разряды Другой старый хороший способ защиты - данные , которые будут раз- личными в последующих операциях чтения (т.н. "слабые данные"). Сла- бые данные могут быть любыми данными, расположенными в цифровой "не- определенной области" или длинным рядом нулей с пропущенными битами синхронизации. В первом случае, FDC будет управляться произвольным шумом. В последнем случае, произвольные изменения во вращении диско- вода будут перемещать битовую ячейку за синхронизацию (такие данные не могут записываться без модификации аппаратуры FDC, так что мы не будем обсуждать их). Один хороший способ генерировать слабые данные состoит в том, чтобы помещать их около дефекта поверхности. Такие слабые данные не будут исчезать после команды записи данных в этот сектора. К сожале- нию (ха!), заводские дефекты поверхности теперь редки, так что нужно делать их вручную. Мы видели много таких способов - от царапанья диска ржавым гвоздем до прожигания верхности инфракрасным лазером. Однако, это едва ли программистская проблема. Слабые двоичные разряды могли создаваться также программно. Пер- вый способ - управлять битом выбора/отмены дисковода в регистре циф- рового вывода (3F2h). Например, если вы хотите создавть слабый байт на дисководе A:, вы должны начать операцию записи, подождать, пока желаемый байт не будет перемещен на дисковод (но не на FDC !) и пов- торить посылку цифровому регистру значений 1Dh (выбор дисковода 1) и 1Ch (выбор дисковода 0) во время перемещения байта. Такая операция будет модулировать все данные (включение биты синхронизации) записы- ваемые на диск прямоугольными импульсами, перемещая их в неопреде- ленную область. Второй метод требует почти такой же операции с регистром управле- ния дискеты (3F7h). Переключение скорости передачи данных будет пе- реразмещать данные и биты синхронизации и деформировать их, также перемещая в неопределенную область. Бит-копировщик, сталкиваясь со слабыми данными, встает перед ин- тересной дилеммой: являются ли слабые биты следствием неопознанного дефекта поверхности, и операция должна повторяться, пока первона- чальные данные не будут восстановлены, или это метка защиты, которая должна быть воспроизведена во имя его полной славы? Из всех бит-копировщиков, с которыми мы сталкивались, только CopyWrite был способнен справляться со слабыми данными, хотя это преобразовывает один слабый байт в 10- 12 байтов, так что первона- чальную метку можно легко отличить от копии. Таким образом, слабые данные - не такая плохая вещь для дешевой защиты и будет, очевидно, популярна в СССР на отечественном программном обеспечении. 1.3.3 Данные в промежутке Можно легко сохранять небольшое количество данных в промежутке после поля данных сектора (GAP 3). Делая этот этот сектор не переза- писываемым, метка будет постоянно находиться в промежутке в полной сохранности. Верхний край для числа байтов данных, которое записыва- ется в GAP 3, задается значением GPL в команде формата дорожки. Про- верка такой метки проста (по крайней мере, для первого сектора на дорожке) - читают команду дорожки с N на единицу больше фактического значения в идентификаторе сектора, что будет загружать данные проме- жутка в память. Сохранение данных в промежутке требует больее сложной процедуры. - 14 - Предположим, что вы желаете сохранить 10 байтов данных в промежутке первого 512-байтового сектора на дорожке 0 головки 0. Нужно сначала форматировать дорожку 0, определяя код длины 3 (1024 байтов) для первого идентификатора сектора, но код длины 2 для параметров форма- та. Затем начинается операция записи на имитации 1024-байтового сек- тора, но останавливается после пересылки 526 байтов (данные сектора с 512 байтами + 2-байтовая CRC + 10-байтовый промежуток + 4-байтовый конец для безопасности). Затем необходимо начать операцию формата дорожки кодом длины 2 (как для параметров формата, так и для иденти- фикатора сектора) и остановить ее где-нибудь внутри GAP 2 (идентифи- катор сектора уже записан, но поле данных еще не существует). Как операция формата, так и записи могут останавливаться либо сбросом контроллера (посылка 0 на регистр цифрового вывода 3F2h), или изме- нением выбранного дисковода (см. 1.3.2). К сожалению, иногда бытвает трудно различить "пустой" промежуток от промежутка, содержащего данные защиты. Старые дисководы имели от- носительно большие коэффициенты ослабления сигнала записи, так что промежутки, записываемые на таких дисководах, заполнялись произволь- ным мусором, который можно неверно интерпретировать, как двоичные данные защиты. Проектировщик схем защиты может улучшить этот не- достаток, используя слабые биты (1.3.2) внутри промежутка защищенных данных. Среди упомянутых бит-копировщиков только CopyWrite был способен обнаруживать данные в промежутке. Одно любопытное исключение - пер- вый сектор дорожки 0, головка 0, который преднамеренно игнорируется CopyWrite как место для данных промежутка. 1.3.4 Сектора без метки адреса данных Сектора без метки адреса данных будут генерировать ошибку отсутствия данных (будет установлен бит MA в ST0 и бит MD в ST2) во время оперций чтения и записи. Команда чтения идентификатора сектора будет на таком секторе будет завершаться обычно. Сохранение сектора без адресной метки данных требует простой опе- рации формата, которая должна останавливаться после записи адресной метки идентификатора сектора, но перед записью адресной метки данных (как в 1.3.3). Должна соблюдаться особаяая осторожность чтобы уда- лить ранее существовавшую адресную метку данных, используя диски, стертые или электромагнитно, или предварительным форматом с различ- ной скоростю пересылки данных. Альтернативный способ (который не бу- дет работать для первого сектора на дорожке): сначала форматируйте дорожку со значением GPL, выбранным так, чтобы помстить поле данных там, где синхронизатор адреснаой метки поля данных должен начинаться на целевом диске. Во-вторых, переформатируйте дорожку с желательным значением GPL и остановом прежде, чем запишется адресная метка сек- тора. 1.3.5 Сектора без адресной метки идентификатора сектора Сектора без адресной метки идентификатора можно записать в следу- ющим образом: Форматируйте дорожку, сохраняя код длины N + 1 для сектора, расположенного перед интересующим. Затем считайте содержание этого фиктивного сектора и запишите его обратно, останавливая операцию, когда адресная метка идентификатора сектора уже переписана, а ад- ресная метка данных - еще нет. Теперь вы имеете сектор без адресной метки идентификатора. Такой сектор не будет принимать участие в лю- бых FDC-операциях. Он не может быть, вообще говоря, считан любой ко- мандой без специальных мер, так как он почти не существует. Ни один из проверенных бит-копировщиков не был способный распоз- навать такие сектора (но любой аппаратный бит-копировщик способен сделать это), так что отсутствие адресной метки идентификатора может рассматриваться как хорошая защита . Однако, трудности проверки де- - 15 - лают такую метку в "живых" схемх защиты крайне маловероятной. (Но см. 1.3.10). 1.3.6 Сектора с плохой адресной меткой идентификатора сектора Эта метка отличается от 1.3.5 только степенью искажения адресной метки идентификатора. Здесь FDC еще способен распознавать адресную метку идентификатор, но CRC проверяет неправильно. Эта метка не мо- жет быть обнаружена командой чтения идентификатора сектора, но будет устанавливать бит DE в ST1 и сбрасывать бит DD в ST2 в течение вы- полнения команды чтения сектора, так что значения C/H/R/N будут из- вестн и проверка не составит никакой проблемы. Сектор с плохой ад- ресной меткой идентификатора будет, однако, появляться в команде чтения дорожки. Тогда точные значения будут C/H/R/N не известны, но их еще можно получить следующей процедурой: зная точной позиции поля данных из размеров времени, можно вычислить приблизительную позицию адресной метки идентификатора и читать ее, используя методику досту- па к битовой ячейке (см. 1.2.4) . Ценной модификацией этой метки для разработчикаика защиты от коп- рования будет недопустимая CRC идентификатора сектора и отсутствие адресной метки данных. Такой сектор будет вызывать завершение коман- ды чтения дорожки, таким образом предотвращая обнаружение бит-копи- ровщиком "нормальных" меток такого типа, размещенных после него. Чтобы записать плохой идентификатор, нужно останавливать операцию формата во время записи CRC адресной метки идентификатора сектора. (Отметим, что FDC имеет внутренний буфер приблизительно на 3 байта, так что контроллер начинает запись CRC не когда последний байт иден- тификатора подан в DR, но спустя некоторое время). CopyWrite распоз- нает сектор с плохой адресной меткой идентификатора и копирует его. 1.3.7 Поле данных, передаваемое по адресной метке индекса Метки защиты этого типа появились, когда кто-то спросил: что бу- дет делать FDC^ если команда формата дорожки будет определять общую длину данных на дорожке немного болше, чем дорожка может вместить? Рассмотрим следующие результаты полученные при форматировании DD-диска в HD-дисководе с 13-ю 256-байтовыми секторами и различными значениями Gap 3 (смещения cектора измерялись по индексному от- верстию). Обратите внимание, что время вращения используемого диско- вода - 166.52 мс. Таблица 1.3.7 ┌───────────────┬────────────────────┬──────────────────────┐ │ Значение │ Начало смещения │ Смещение сектора 13h │ │ GAP 3 │ 1 сектора │ начало конец │ ├───────────────┼────────────────────┼──────────────────────┤ │ 01h │ 3.849 │ 156.981 165.46 │ │ 08h │ 3.844 │ 160.334 2.29 │ │ 10h │ отсутствует │ 164.180 6.14 │ │ 14h │ отсутствует │ отсутствует │ │ 18h │ отсутствует │ 1.519 10.00 │ └───────────────┴────────────────────┴──────────────────────┘ Можно увидеть, что имеется маленькая область в начале дорожки, которая не используется данными сектора (фактически, она использу- ется адресной меткой индекса). Эта область может перезаписываться последним сектором на дорожке (как на второй строке таблицы 1.3.7), но если последний сектор значнительно накладывается на начало дорож- ки, сектор 1 будет стерт (строка 3). Если начало поля данных послед- него сектора будет переходить через индексное отверстие (строка 4), контроллер запишет GAP 4 до следующего местонахождения индексного отверстия, таким образом перезаписывая все существующие сектора на дорожке. Если начало адресной метки идентификатора сектора пререхо- - 16 - дит через индексное отверстие, (строка 5), этот сектор также будет сохранен, перезаписывая все записанные перед этим сектора. Сектор, передающийся по IAM, может вызывать значительные проблемы для бит-копировщика, не распознающего его существование, поэтому большинство меток защиты создаются повторной операцией формата, ко- торая будет разрушать данные в секторе за IAM. Ни одит из проверен- ных бит-копировщиков не был способный воспроизводить такие сектора (то есть , сохранять и данные сектора и его позицию) , поэтому такая метка экстенсивно используемый в схемах защиты копии в СССР. 1.3.8 Многоскоростные дорожки Как мы видели в предыдущем разделе, FDC игнорирует IAM в начале дорожки, так что сектора могут появляться в любом месте внутри до- рожки. Следовательно, очевидная идея - получить участки дорожки, за- писываемые с различной скоростью пересылки данных. (лучше всего вы- полнить это не при переключении скорости пересылки в течение опера- ции формата, а двумя последующими форматами с различной скоростью). Например, записать 9 секторов по 512 байтов на дорожке 0 в 300КБод (HD-дисковод), один 512-байтовый секторами в 500КБод. (Т.к. эта до- рожка дискеты самая дальняя, использование высокой скорости пересыл- ки на DD-дисках не будет слишком уменьшать сохранность данных). Хотя различные скорости пересылки данных полностью поддерживаются AT, подобная метка может генерироваться при FM- и MFM-форматах дан- ных (то есть, 9 MFM сектора и 1 FM) на PC и XT. Ни один из проверен- ных бит-копировщиков не был способный воспроизвести многоскоростную дорожку, так что это - хороший способ защиты. Однако, нам не пока не встречалась защита, основанная на такой метке. 1.3.9 Доступ к данным через промежуток Теперь мы подошли к наиболее популярному (и, возможно, наилучше- му) способу защиты - доступу к данным через промежуток. Т.к. все идентификаторы секторов и поля данных синхронизируются отдельно, две битовых ячейки в двух последующих полях могут перемещаться произ- вольно. Этот сдвиг управляется произвольными изменениями в WR CLK и скорости вращения дисковода, так что они не могут контролироваться. (Конечно, кно-нибудь может изобрести аналоглвый копировщик, который будет способен делать это. Я нкогда не слышатл о таком.) Любая опе- рация чтения дорожки с достаточно большой длиной кода (скажем, 6) будет давать "след дорожки". Число различных "следов" может грубо оцениваться следующим образом: примите, что каждое соединение от- дельно синхронизированных полей может иметь 2 различных состояния (очевидно, это недооценка). На дорожке обычной DD-дискеты имеются 19 таких соединений (9 идентификаторов, 9 данных, 1 IAM), так что число различных следов для каждой дорожки - по крайней мере 2^19 ў 500,000. Чтобы cделать жизнь еще более интересной для аналогового копирующего устройства, можно еще увеличить длину кода (скажем, до 7). Это не увеличит число следов, но включит дорожку более одного раза. Т.к. даже лучшее уст- ройство должно где-нибудь начинать и останавливать операцию, данные будет частично разрушены. Это устанавливает одно ограничение на при- менение защищенного диска: ключевая дорожка не может перезаписы- ваться. Другая менее интересная разновидность этой идеи - читать следую- щую адресную метку идентификатора сектора через GAP 3 и проверять, изменилась ли она. Такая проверка запрещает запись только одного сектора. Очевидно, проектировщик защиты не ограничтся получением "следов" командой чтения дорожки (и таким образом, обращением к уровню чипа аппаратуры). Это может быть выполнено также и простым добавлением фиктивного сектора с кодом длины 6 или 7 в конце дорож- ки, и задча будет выполнена BIUOS'ом. - 17 - Ни одна из этих проверок не может воспроизводиться на стандартном оборудовании PC. Однако, большое количество программ, использующих вторую разновидность, можно перехитрить при расширении GAP 3 после представляющего интерес сектора и при сохранении идентификатора сек- тора этого промежутка, как это необходимо при чтении через промежу- ток. Если программа не проверяет точные позиции секторов на дорожке, она будет принимать такую "копию" как ключевой диск. 1.3.10 Сумасшедшие идеи Все обсаждавшиеся пред этим способы защиты допускали неразрушаю- щую проверку. Теперь вообразим метку защиты, которая не может прове- ряться или обнаруживаться бит-копировшиком без предварительной за- писи на ключевой дис. Вспомните сектора без идентификатора - (1.3.5). Что будет мы будем делать, если на представляющей интерес дорожке нет других секторов? (По крайней мере, в той же скорости пе- ресылки). Этот сектор не будет читаться до формата дорожки, который установит адресные метки идентификаторов. Любая попытка доступа внутрь такаой программы отладчиком будет разрушать метку защиты, по- тому что точный выбор определенного времени остановки операции фор- мата будут сбит самим временем отладки. Этот простой пример показы- вает что имеются метки защиты, которые не могут быть прочитаны без разрушения части их содержимого или без знания способа проверки за- щиты. 1.4 Основанные на таймере способы защиты Все PC оборудованы весьма хорошим чипом таймера INTEL 8253 или его функциональным аналогом. Работая с частотой 1,193,180 Гц, он позволяет измерять интервалы времени с разрешением нс (то есть, мо- жет измерять время выполнения ОДИНОЧНОЙ команды отделения по 16 386 МГц , который может рассматривать(принимать) к 2.4 гц). Такой таймер более чем адекватен для выбора определенного времени на гибких дисках (Передача одиночного байта данных с самой большой скорости (500 КБ) требует 16 гц), так что точность таких интервалов будет ог- раничиваться не разрешающей способностью таймера, а произвольными изменениями скорости вращения. 1.4.1 Порядок контроля секторов Измеряя время завершения команд чтения дорожки, можно получить точную позицию каждого сектора на дорожке. В терминах битов, вы мо- жете получить точность положения сектора, по крайней мере, в однин бит. Т.к. команда формата дорожки FDC управляет позицией сектора с разрешением в байт, нельзя воспроизвести такой точный порядок секто- ров. Таким образом, расположение секторов может служить как метка защиты. Однако, эта проверка будет слишком чувствительна к быстро- действию ЦП и стабильности вращения дисковода и будет часто искажать ключевой диск. Следовательно, позиция сектора обычно используется как добавление к другой метке защиты (см. 1.3.9). 1.4.2 Измерение скорости пересылки данных Скорость пересылки данных выбираемая регистром управления дискеты (3F7h) - только начальная частота, используемая FDC для декодирова- ния битов. Специальная аналоговая схема, называемая замкнутый по фа- зе цикл (PLL), регулирует скорость дорожки, в которой фактически прибывают биты. PLL допускает отклонения, по крайней мере, в 4% от центральной частоты (разрешенное отклонение в скорости вращения дисководов - ё2%). Практически, PLL будет справляться с 10%-м откло- нением на MFM-дискетах и почти 100% на FM. Скорость поступления двоичных разрядов определяется как угловой плотностью данных на диске, так и скоростью вращения дисковода, так - 18 - что для определения характеристик диска (угловой плотности данных) необходимо измерять время пересылки одного сектора и время вращения. Максимальная точность такого измерения может быть легко оценена: один 512-байтовый сектор будет перемещаться с частотой 500 КБ в те- чение 8.2 мс, разрешающая способность таймера 0.84 мкс даст относи- тельную точность 0.01%. Относительная погрешность в определении вре- мени вращения будет по крайней мере на порядок меньше. При 10 для крайней безопасности, 0.1% кажется приемлемой оценкой. Так, все дисководы для гибких дискет PC будут разбиваться на 40 (2 * 2%/0.1%) различных групп, и дискеты, записываемые на дисководе одной группы, можно легко отличать от дискет, записываемых на другой. К сожалению (ха !), большинство современных дисководов попадает в ё0.2%-ю область и, таким образом, разобьются на 4 группы вместо 40, почти уничтожая всю "защиту". Некоторые FDC (например, мой старый WD HDC/FDC 1986 года) разрешают простой трюк с регистром цифрового вы- вода (3F2h): выведите 0Ch в 3F2h (остановить двигатель привода A:), ждите остановки 10 мс, выведите 1Ch в 3F2h (запустить двигатель при- вода A:) и немедленно выполните операцию записи. Скорость вращения дисковода будет немного меньше номинальной в течение примерно 20 мс, разрешая записать один сектор. Мой новый IDE HDC/FDC 1990 года, од- нако, ждет, пока скорость вращения не достгнет номинальной, задержи- вая операцию записи. Интересные разновидности этой метки могут получаться при неболь- ших модификациях аппаратных средств PC (см. 1.5.3). 1.5 Защита, основанная на специальных аппаратных средствах Я не специалист в аппаратных средствах защиты копии, так что обсуждение почти целиком базируется на rumors и предположениях. 1.5.1 Модифицированные MFM-форматы Чип FDC INTEL 8272A не имеет возможности изменения программным обеспечением длины GAP1, GAP2 и GAP5, но будет принимать дискеты с этими промежутками, отличающимися от стандарта и имеет способность измерять фактическую длину промежутка. Например, GAP2 (промежуток после идентификатора) может измеряться или командой чтения дорожки с кодом длины больше фактической длина сектора, или при измерением различия во времени завершения чтения идентификатора идентификатора и команды чтения данных. Дискеты с различными значениями промежутка могли создаваться для PC на других системах ЭВМ, которые могут изме- нять эти параметры. (Мы слышали, что некоторое системы DEC способны делать это.) 1.5.2 Переразмещенные дорожки данных Некоторые контроллеры и дисководы имеют большее строгое управле- ние позиционированием головки для чтения/записи, чем этого требует PC. (Я уверен, что требуемые модификации аппаратных средств не слиш- ком большие.) Это может быть первоначально выполнено не для целей защиты от копирования, но чтобы обеспечивать способность читать дискеты, записываемые на плохо регулируемых дисководах. (Мы слышали, что ICL FDC имеет такую способность.) Следовательно, на такой систе- ме можно подготовить ключевой диск с нестандартным размещением доро- жек (и программное обеспечение будет проверять это). Такой диск не сможет проверяться на другой системе ЭВМ, так что эта методика могла бы иметь только ограниченное применение. 1.5.3 Нестандартные скорости пересылки Как мы видели в 1.4.2, FDC допускает значительные изменения в уг- ловой плотности данных на дорожке (и имеет способность измерять их). Небольшие модификации FDC или дисковода дают возможность управлять - 19 - вручную средней частотой WR CLK генератора или скоростью вращения дисковода и таким образом дают возможность записывать данные с нем- ного нестандартный (но еще приемлемый для большинства FDC) скорость пересылки данных. Некоторые изготовители аппаратных средств (например, научного оборудования со встроенными микро-ЭВМ) делают этот "подарок" заказ- чикам. (Мы видели 3-дюймовые дисководы со скоростью 360 об./мин., которые были несовместимые с "нормальным" на 300 об./мин.) 1.6 Примеры схем защиты Приводимые здесь схемы защиты основанный на нашем личном опыте защиты программ (кроме некоторых указанных) не могут рассматриваться как законченые и не способны покрывать все аспекты и версии защиты программного обеспечения. Все защищенные программы, обсужденные здесь, исследовались с помощью Copy Unprotector Toolkit (C.U.T). 1.6.1 Помощник, чувствующий IBM Это действительно простая и непосредственная защита, датированный 1986 г. Она не имеет никакого практического значения и приведена здесь только чтобы показать развитие приемов защиты в течение последних лет. Это "защищенное" программное обеспечение проверяет присутствие сектора 8Fh с длиной кода 2 на дорожке 39d стороны 1 ключевой дискеты. Никакие другие проверки не делаются. Т,к. первые версии CopyWrite который мы видели, появились в 1985 (и были способ- ные копировать такие метки), только надежды на "этих русских" могут быть причиной разработки такой защиты. 1.6.2 SuperLok Диск с программой SuperLok, который я смог приобрести, датиро- вался 10 октября 1986 г. Защищенный диск содержал три дорожки, отме- ченые как "ненормальные" ( все на головке 0). Первой была дорожка 5, которая имела необычный интерлив и два сектора с короткими данными , так что идентификаторы секторов представляли следующую последова- тельность (все значения шестнадцатеричные): 05/00/01/02, 05/00/06/02, 05/00/8A/03, 05/00/02/02, 05/00/07/02, 05/00/65/03, 05/00/03/02, 05/00/08/02, 05/00/04/02, 05/00/09/02, 05/00/05/02 Как было замечено, SuperLok не проверяет эту дорожку вообще, так что это представляет вид ловушки для заинтересованного исследовате- ля. Вторая защищенная дорожка на диске была 12-ая. Она содержала сек- тор со скрытой адресной меткой (см. 1.3.6) с идентификатором 7B/46/05/00, который SuperLok проверял командой чтения данных. Другая проверка защиты на этой дорожке была выполнена командой чте- ния дорожки с кодом длины 6 (один 8192-байтовый сектор) и 3 (два 4096-байтовых сектора). 16-битовая контрольная сумма вычислялась в обоих случаях и сравнивалась с сохраненным значением (см. 1.3.9). Третья дорожка защиты содержала четыре специальных сектора. Пер- вый из их имел скрытую адресную метку сектора и не имел адресной метки данных, предотвращая обнаружение другими программами. Три сек- тора имели скрытые адресные метки: 21/47/05/00, A5/86/81/04 и EB/76/EE/04. После их проверки, SuperLok читает дорожку с кодами 6 и 3, снова вычисляя 16-битовые контрольные суммы. Таким образом, SuperLok представляет хорошую защиту от копирова- ния. Ни один из проверенных бит-копировщиков не был способный копи- ровать такой диск "буквально" без модификации областей данных SuperLok. Вероятно, такая копия просто невозможна (см. 1.3.9). Позже я имел возможность чтобы делать быстрый glance на диске с - 20 - SuperLok, датированном 1990 г. Адресные метки скрытых секторов были удалены (?), доступ к чтению дорожки с меткой защиты заменялся (?) дополнительным сектором с кодом длины 6 в самом конце дорожки. Весь (?) доступ к диску был выполнен BIOS'ом. 1.6.3 Cops CopyLock II COPS CopyLock II основывает проверки защиты на замерах времени. Он использует дополнительный сектор 00/00/6A/01 на дорожке 0 головки 0, чтобы сохранять переменные данные защиты. ( COPS CopyLock создает защищенные программные продукты "повышенного качества" без ключевого диска, так что нет необходимости сохранять что-либо еще, кроме се- рийного номера в теле программы). Второй дополнительный сектор на дорожке 0 (00/00/F6/02), у которого была частично перезаписана IAM, использовался, чтобы проверить идентификатор адресной метки сектора 1 через промежуток (см. 1.3.9). Чтобы предотвратить обход защиту, совершаемый по 1.3.9, CopyLock проверяет позицию всех (?) секторов на дорожке выбором определенного времени команды чтения идентифика- тора сектора. Дорожки 1 к 6 очень схожи: они имеют два дополнительных сектора в конце: XX/00/14/01, который не используется (?), и XX/00/13/02, у которого перезаписана IAM, и проверяется тем же способом, что и 00/00/F6/02. Дорожки 7 к 9 имеют по одному дополнительному сектору (XX/00/14/01). Эти сектора не используются (?). На исследованной дискете имелся поверхностный дефект в дорожке 32 головки 1, но CopyLock не проверила его наличие, так что мы не думаем что это дру- гая метка защиты. Это - еще один пример хорошей схемы защиты, хотя и достигнутой средствами, отличными от 1.6.2. Однако , с помощью C.U.T. ыы смогли восстановить незащищенную версию представляющей интерес программы (Это был Paradisk из JV ParaGraph ) в течение приблизительно 2-х часов. Мы не думаем, что разработка CopyLock заняла меньше времени. Хотя мы имеем копию COPS CopyLock III, еще не было времени для ее исследования. 1.6.4 Щит PC Эта дисковая схема защиты отечественного производства, изобретен- ная Александром Симкиным, получила большую рекламу в Советском Сою- зе. Мы имели возможность исследовать пол-дюжины версий этой програм- мы, и, таким образом, наблюдать ее постепенное развитие. Первые версии Щита PC использовали простую метку сектора, переза- писывая IAM только на дорожке 0. Это обманывало почти все бит-копи- ровщики, которые определяли эту метку как короткие данные (половина сектора - после индексного отверстия, не так ли ?) и, таким образом, разрушали ее. Однако, эта метка еще может легко записываться обычны- ми вызовами BIOS'а (не говоря уже о новых версиях бит-копировщиков), и эта защита "раскалывалась". Следующий предпринятый шаг - обращаться к идентификатору адресной метки первого сектора через промежуток (влияние COPS?). Первоначаль- но для этого дополнительного сектора выбирался код длины 2 (512 бай- тов). Эта метка "раскалывалась" методикой, описанной в 1.3.9 (снова , не требуется ничего кроме BIOS'а). И теперь , последний шаг - код длинаы дополнительного сектора увеличивался до 6 (влияние SuperLok?), делая трудным (но еще не невозможным) воспроизведение этой метки BIOS'ом. Очевидно, это не препятствие для хорошего бит-копировшика, и C.U.T будет легко имитировать такую метку. Дальнейшая разработка этой схемы защиты может пойти по двум нап- равлениям: включение проверки позиции сектора (как в 1.6.3) или пол- ное чтение дорожки с промежутками (как в 1.6.2). Второе направление, хотя требует сохранения всей дорожки для целей защиты, кажется более вероятным, потому что такая разработка не требует доступа к FDC на уровне чипа (все известные нам версии Щита PC пользуются исключи- - 21 - тельно процедурами доступа к диску BIOS'а). 2. Защита, основанная на жестком диске Ключи на гибих дисках могут обеспечивать хороший уровень защиты, но они слегка утомительны для ежедневного применения, занимают впустую дисковод и могут быть легко повреждены неподходящим обраще- нием. Следовательно, большинство защищенных от копирования пакетов могут устанавливаться на жестком диске, используя любые метки защиты жесткого диска (эта глава) или метки основной платы (глава 3). Бла- годаря существованию большого числа несовместимых физически интер- фейсов жесткого диска, метки жесткого диска - обычно "наименьший об- щий знаменатель" различных возможностей и более просты в изготовле- нии. 2.1 Защита на уровне чипа Этот уровень доступа к жесткому диску весьма маловероятен, так что мы только кратко обсдим AT WDC (контроллер винчестерного диска), с которым Мы имеем небольшой личный опыт обращения. К первому AT WDC можно обращаться по адресам 1F0-1F7 и 3F6 (этот порт находится в ад- ресном пространстве первых AT FDC, и является причиной для конфликта аппаратных средств в системах с FDC и WDC, выполненных на различных дополнительных платах). Второй AT WDC занимает адреса 170-177 и 376. AT WDC использует IRQ 14 (int 76 в МС-ДОС) и не использует DMA. Структура AT WDC выглядит очень схоже с чипом INTEL 82062, действую- щем в расширенном режиме с 4-байтовым кодом ECC (код исправления ошибок), добавленным к каждому сектору. (К сожалению, мы имеем толь- ко информацию по усовершенствованию на 82062, так что приводимок описание ниже не может быть точным или законченным). 1F0 - регистр данных, используемый для буфера чтения/записи сек- торов (512 байтов). Хотя для данных сектора может использоваться словный доступ только байтовый приемлем для чтения/записи байтов ECC. 1F1 - чтение: регистр флагов ошибок ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ BBD │ CRC │ 0 │ ID │ 0 │ AC │ TK0 │ DM │ └──┬──┴──┬──┴─────┴──┬──┴─────┴──┬──┴──┬──┴──┬──┘ │ │ │ │ │ └ АМ данных не найдена │ │ │ │ └─── Неудачная перекалибров- │ │ │ │ ровка:сектор 0 не найден │ │ │ └──────────── Команда прервана │ │ └──────────────────────── ID AM не найден │ └──────────────────────────────────── Неисправимая ошибка │ или AM данных не надена └────────────────────────────────────────── Найден плохой блок Рис 2.1a Регистр флагов ошмбок (1F1) 1F1 - запись: пуск уменьшает запись текущего цилиндра / 4, 0FFh отключает RWC. 1F2 - регистр счетчика секторов. Используется, чтобы определять число пересылаемых секторов в многосекторных операциях - 1, то есть , значение 1 значит 2 сектора. WDC будет принимать более одной до- рожки, состоящей из секторов, модифицируя номера головки и цилиндра, соответственно. В течение операции формата дорожки определяет число секторов на дорожке (0FFh - 255 секторов). 1F3 - регистр номера сектора. В течение операции формата дорожки определяет значение GAP 1 и GAP 3 (см. описание MFM в 1.1.3) минус 3 байта. 1F4 - 8 младших битов номера цилиндра. (Tech Help! 4.0 здесь не- верен). - 22 - 1F5 - 2 старшего разряда номера цилиндра (биты 0-1 используются). Некоторые WDC не будут принимать более 2-х бит в этом регистре, та- ким образом, поддерживая жесткие диски с более 1023 цилиндрами, но большинство BIOS'ов всегда удаляет эти старшие биты. 1F6 - выбор сектора/привода/головки. ┌─────┬───────────┬─────┬───────────────────────┐ │ EXT │ SIZE │ DRV │ HEAD │ └──┬──┴────┬──────┴──┬──┴─────┴─────┼─────┴─────┘ │ │ │ └────── Выбор головки (0-15) │ │ └───────────────────── Выбор привода (0-1) │ └──────────────────────────────── Код размера сектора: │ 00 = 256 байтов │ 01 = 512 байтов │ 10 = 1024 байтов │ 11 = 128 байтов └──────────────────────────────────────── 0: использовать CRC 1: использовать ECC Рис. 2.1b SDH (Sector/Drive/Head) регистр (1F6). В первоначальной спецификации 82062 3 бита резервировались для поля выбора головки и 2 бита - для выбора дисковода, но эти сигналы обрабатывались внешними схемами, так что это действительно не имеет значения. 1F7 - чтение : регистр состояния ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │BUSY │READY│ WF │ SC │ DRQ │ ECC │ CIP │ERROR│ └──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘ │ │ │ │ │ │ │ └ Резюме ошибки (OR со │ │ │ │ │ │ │ всеми битами 1F1) │ │ │ │ │ │ └────── Команда в работе │ │ │ │ │ └─────────── Данные ECC-исправлены │ │ │ │ └────────────────── Запрос данных (буфер │ │ │ │ ожидает данные) │ │ │ └──────────────────────── Установка выполнена │ │ └────────────────────────────── Ошибка записи │ └──────────────────────────────────── WDC готов └────────────────────────────────────────── WDC занят (все дру- гие биты неверны) Рис. 2.1c Регистр состояния (1F7) 1F7 - запись: регистр команд. Здесь записывается код команды. Система команд AT FDC (которая является надмножеством 82062), вклю- чает следующие: 0 0 0 1 R3 - R0 Установка дорож- └─────┴─────┴─────┴─────┴───────────────────────┘ ки 0 0 1 1 1 R3 - R0 Установка └─────┴─────┴─────┴─────┴───────────────────────┘ 0 0 1 0 I M E T Читать сектор └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 0 1 1 0 M E T Писать сектор └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 0 1 0 0 0 0 0 0 Просмотр иденти- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ фикатора 0 1 0 1 0 0 0 0 Форматирование └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ - 23 - 1 0 0 1 0 0 0 0 Диагностика └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ 1 0 0 1 0 0 0 1 Установка параме- └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ тров привода R3-R0 определяют скорость шага дисковода, которая будет 0.035+0.5 * R мс для 5 МГц скорости синхронизации. (Наши эксперименты с AT WDC указывают, что это значение игнорируется контроллером, который уста- навливает головку в максимальное возможное быстродействие, используя аппаратную интерфейсную линию Seek Completed). I = 0 разрешает запрос прерывания на активном DRQ, как и после завершения команды, I = 1 отключает прерывание на активном DRQ. M = 1 определяет многосекторную пересылку, число секторов нахо- дится в регистре счетчика сектора. E = 1 отключает исправление данных и передает 4 байта ECC наряду с данными секторов. T = 0 допускает повторные пересылки, то есть, если определяемый сектор не был обнаружен после 6 оборотов, WDC выполняет автомати- чески команду просмотра идентификатора, модифицирует внутренний но- мер цилиндра, выполняет в случае необходимости установку и повторяет операцию. При T = 1 ошибка возникает, если определяемый сектор не был обнаружен после 2 оборотов. 3F6 - запись: регистр опций. Запись 02h отключает WDC IRQ 14 от системной шины, 00h продолжает нормальные операции, 04h сбрасывает контроллер. Система команд WDC гораздо менее гибкая, чем у 8272, и разрешает выбирать вручную только номер сектора. Хотя адресная метка идентифи- катора сектора содержит также значения цилиндра, головки и размера, они производятся из регистра команд WDC и не можгут явно опреде- ляться. Поэтому все команды WDC (за исключением команды просмотра идентификатора) используют параметры установки, эти поля трудно из- менять. Особенности AT WDC, очевидно, выгодны для нормальных операций ЭВМ, но сокращают возможности защиты от копирования дополнительными/ пропущенными секторами (см. 1.3.1) и порядком проверки секторов (см. 1.4.1). Исправление данных ECC позволяет выполнить третий трюк защи- ты жесткого диска, изменяя бит в определенной позиции, который будет скрываться ECC. Все эти метки могут также генерироваться и проверяться при использовании доступа к диску на уровне BIOS, так что обращение на уровне чипа обращаются к WDC не дает никаких существенных преиму- ществ, которые могут покрыться плохой переносимостью программ. 2.2 Защита на уровне BIOS Это, очевидно, основной уровень для защиты, основанной на жестком диске. Достаточная защита из резидентных программ, контролирующих вызовы int 13h BIOS, может достигаться в этом случае при отслежива- нии обращения к прерыванию, 13h, являющегося точкой входа BIOS при обращении к жесткому диску (см. пример в Приложении B). Другая при- чина для такого удаления наблюдения - возможная потребность для точ- ного выбора точного времени операции в определении интерлива. 2.2.1 Изменения интерлива Этот метод очень сходен с защитой гибких дисков, описанной в 1.4.1. В отличие от дискет, где сектора, как правило, размещаются последовательно (интерлив 1:1), сектора жестких дисков часто разме- щены форматерами низкого уровня прецизионно, чтобы обеспечивать са- мую быструю из возможных скорость пересылки данных. Следовательно более трудно увидеть метку интерлива на жестком диске, чем на диске- те. - 24 - 2.2.2 Измененные номера сектора Снова, это двойник 1.3.1. Однако, жесткие диски обычно имеют плотнее упакованные сектора данных, и чтобы добавить сектор с нестандартным номером, нужно перемещать один из секторов данных. Этоа ситуация может легко определяться. 2.2.3 Неиспользуемые дисковые области На уровне BIOS имеются две неиспользуемых области на почти любом жестком диске: в самом начале и в самом конце дисковода. Первый сек- тор каждого жесткого диска (по крайней мере, не SCSI или ESDI) занят таблицей разделов, в то время как все другие сектора на цилиндре 0 дорожки 0 не используются в IBM и схеме разделов Microsoft. Еще, эта область может быть занята частным программным обеспечением выделения разделов. Например, DiskManager (dmdrvr.bin) использует сектор 0/0/8 для расширенной таблицы разделов, Olivetty МС-ДОС начинает первый раздел DOS в 0/0/2, и т.д. Вторая неиспользуемая область - цилиндр диагностики пользователя , который размещается за в последним дисковым цилиндр на ЭВМ AT и на последнем цилиндре на PS/2 (Но они еще совместимы, не так ли?). Что-нибудь, записанное здесь, имеет очень небольшие возможности сох- раниться долго, потому что любая дисковая программа теста низкого уровня может обрабатывать эту дорожку как ей захочется. (Я уверен, Norton DiskTreet и Gibbson Research's SpinRite определяют здесь оп- тимальный интерлив). 2.3 Защита на уровне DOS Защита на уровне BIOS жесткого диска обеспечивает не слишком мно- го вариантов, в то время как DOS имеет удобные (и относительно пере- носимые) способы вызова разделов DOS (интерфейсом прерываний 25h/26h, ExtendedOpen (6Ch) в DOS 4.0+), которые могут использо- ваться для защиты от копирования. Структура файловой системы DOS широко известна, так что мы будем только кратко упоминать наиболее существенные ее части. Сектор 0 - сектор загрузчика. Он содержит код который, загружает операционную систему и таблицу, описывающую характеристики раздела, как число секторов, число секторов на кластер, номер FAT, число вхо- дов в корневой каталог. Таблица размещения файлаов (FAT), который следует за загрузчим, содержит 12- или 16-битовые значения для каждого модуля распределе- ния (кластера), указывая номер следующего кластера в цепочке. Обыч- но, на диске есть более одной (две) FAT, обеспечивая, таким образом, сохранность данных. (Однако, DOS не обрабатываем копии FAT отдельно, а просто сохраняет первую FAT в дополнительных областях, дублируюя любую ошибку в 1-ой копии во всех FAT, так что защита, в действи- тельности, воображаемая). Корневой каталог (после последней копии FAT) содержит таблицу с 32 байтами (называемый входом каталога) для каждого файла в катало- ге. Вход каталога содержит имя файла, размеры, дату модификации фай- ла и первый номер кластера. Другие каталоги используют те же самые методы распределения, как обычные файлы. 2.3.1 Зависимость от номера кластера Стандартные инструментальные средства ДОСА не разрешают пок- ластерный контроль файлов, так что эта информация может использо- ваться чтобы кодировать изображение программы и/или данных. Старто- вый номера кластера файла может быть получен CP/M-образом, вызвав прерывание 11h ( FindFirst через FCB). (Вызовы CP/M могут скрываться от большинства наблюдателей выполнением дального вызова к адресу - 25 - 0:0C0h с кодом функции в CL вместо AL). Выяснение других номеров кластеров требует просмотра FAT (См. пример в Приложении C). 2.3.2 Неиспользуемые (зарезервированные) дисковые области В действительности, имеется только одна такая область по смещению 0Ch входа каталога (длиной 10 байтов). К сожалению , эта область часто используется "DOS-совместимый" операционными системами. Напри- мер, Digital Research DOS использует это поле для хранения пароля файла, PC-MOS/386 (Software Liks) запоминает здесь идентификатор владельца файла, права доступа и дату/время создания. Другая зарезервированная область, которая может существовать на диске, является остатком последнего сектора в FAT 1, который сохра- няется DOS (и даже копируется во все другие копии FAT). 2.3.3 Неиспользуемые (округленные до размера кластера) дисковые области Т.к. DOS распределяет дисковое пространство в кластерах (которые содержат 2^N секторов), в то время как размер файла измеряется в байтах, большинство файлов имеет неиспользуемым (и обычно невидимый на уровне файловой системы) хвост, который может использоваться для целей защиты. Курьезная ошибка в DOS, которая разрешает установку (функция DOS 42h) за концом файла, делает доступ к этому хвосту простым даже из языков высокого уровня (См. Приложение D). 3. Система защиты на основной плате и системе BIOS Другая часть PC, которая, наряду с дискетой и жестким диском, яв- ляется всегда доступной для защиты от копирования - основная плата. Хотя основные платы теперь производятся в тысячами, почти каждыая из их имеет (или может приобрести) индивидуальные качества. 3.1 Способ, основанный на данных Каждая основная плата имеет собственный BIOS. Так что это может использоваться для защиты копии , хотя мы рекомендуем это прежде всего для производителей аппаратных средств, а не для независимых разработчиков программного обеспечения. (Так, начинка BIOS содержит много неописанных функций и таблиц, идентификация которых может яв- ляться видом защиты от копирования). Другая область данных доступная на основных платах - энергонезависимая память CMOS, которая, как правило имеет много (около 6) неиспользуемого пространства. Некоторые виды чипов (например, из C&T) могут отображать часть памяти CMOS в адресном пространстве системного ОЗУ, таким образом, создавая появ- ление "программ, перепрограммирующих ROM BIOS". 3.2 Способ, основанный на времени Более интересные метки защиты - основанный на определении внут- реннего времени основной платы. Три основных подсистемы доступны для таких измерений: ЦП, память и ввод/вывод (см. Приложение E). Разли- Таблица 3.2. Метки основной платы на четырех различных ЭВМ. ┌─────────────────────┬────────────┬─────────────┬───────────┐ │ Система │ ЦП │ Память │ Вв./выв. │ ├─────────────────────┼────────────┼─────────────┼───────────┤ │ 25 МГц 80386 (A) │ 35972 │ 24576 │ 47292 │ │ 25 МГц 80386 (B) │ 35972 │ 24576 │ 49154 │ │ 20 МГц 80386 │ 44958 │ 30112 │ 59990 │ │ 12 МГц 80286 │ 3544 │ 41018 │ 46646 │ └─────────────────────┴────────────┴─────────────┴───────────┘ - 26 - чия могут быть удивительно большими (см. таблицу 3.2). Значения, показанные в таблице 3.2, средние для многих запусков программы из Приложения E. Фактические значения отличаются от сред- него приблизительно ё4. Системы (A) и (B) имели два последовательных заводских номера, но еще могут легко отличаться меткой ввода/вывода (порт 0Ch, использовался 1-ый контроллер DMA). Необходимо также от- метить, что метка ввода/вывода очень чувствительна к режиму действия ЦП, в то время, как метки ЦП и памяти почти одинаковы в обоих режи- мах. 4. Приложение A. Простая программа 8272A Текст программы находится в test_fdc.c. Для компиляции требуется Турбо-С 2.0 и Турбо Ассемблер (любая версия). Минимальные аппаратные средства, требуемые чтобы выполнить этот пример - 8 МГц/0 WS ЭВМ 80286 оборудованная, по крайней мере, одним дисководом для гибких дискет, привод для выполнения теста определяется параметром програм- мы (0 для A:, 1 для B:). Этот тест может использоваться на втором FDC, что обеспечиваться заменой FDC_BASE в исходном файле на 0x370. Команда 'T' команда (анализировать дорожку) будет работать непра- вильно с дисководами на 300 оборотов в минуту (360K, 720K, 1.44M) пока REVOLUTION_TIME в источнике программы не не будет заменена на 200L*2*1193 (но тогда она будет работать неправильно с 1.2M дисково- дами). #if ! defined( __TURBOC__ ) #error Программа использует Turbo C 2.00 с внутренним асемблером! #else #pragma inline #endif #if ! defined( __SMALL__ ) #error Компилировать на модели Small! #endif #include #include #include #include #include #include #include #include #include #include #include /* * FDC_BASE выбирает номер контроллера. * 0x3F0 для первого FDC, 0x370 для второго FDC. */ #define FDC_BASE 0x3F0 #define FDC_MSR (FDC_BASE+4) #define FDC_DATA (FDC_BASE+5) #define FDC_DIGITAL (FDC_BASE+2) #define FDC_RATE (FDC_BASE+7) /* * RATE_??? должен быть установлен по FDC_RATE для выбора значения WR CLK . */ #define RATE_250 2 #define RATE_300 1 #define RATE_500 0 /* * Следующие определения описывают формат регистра состояния 8272A. - 27 - */ typedef struct { unsigned ds : 2 ; /* Выбор привода */ unsigned h : 1 ; /* Выбор головки */ unsigned nr : 1 ; /* Не готов */ unsigned ec : 1 ; /* Проверка оборудования*/ unsigned se : 1 ; /* Установка закончена */ unsigned ic : 2 ; /* Код прерывания */ } ST0 ; typedef struct { unsigned ma : 1 ; /* Нет адресной метки */ unsigned nw : 1 ; /* Защита записи */ unsigned nd : 1 ; /* Сектор не найден */ unsigned _1 : 1 ; unsigned or : 1 ; /* Ошибка переполнения */ unsigned de : 1 ; /* Ошибка данных */ unsigned _2 : 1 ; unsigned en : 1 ; /* Ошибка конца дорожки */ } ST1 ; typedef struct { unsigned md : 1 ; /* Нет адресной метки */ unsigned bc : 1 ; /* Плохая дорожка */ unsigned sn : 1 ; /*Сканирование не успешно*/ unsigned sh : 1 ; /* Сканирование успешно */ unsigned wc : 1 ; /* Неверный цилиндр */ unsigned dd : 1 ; /* Ошибка данных сектора*/ unsigned cm : 1 ; /* Контрольная метка */ unsigned _1 : 1 ; } ST2 ; typedef struct { unsigned ds : 2 ; /* Выбор привода */ unsigned h : 1 ; /* Выбор головки */ unsigned ts : 1 ; /* Двухсторонняя */ unsigned t0 : 1 ; /* Дорожка 00 */ unsigned rdy: 1 ; /* Готов */ unsigned wp : 1 ; /* Защита записи */ unsigned ft : 1 ; /* Неисправность */ } ST3 ; /* * FDC_MSR fields */ typedef struct { unsigned A_buzy : 1 ; unsigned B_buzy : 1 ; unsigned C_buzy : 1 ; unsigned D_buzy : 1 ; unsigned cb : 1 ; /* Котнтроллер занят */ unsigned ndm : 1 ; /* Режим не-DMA */ unsigned dio : 1 ; /* Направления I/O */ unsigned rqm : 1 ; /* Требуется для мастера*/ } STATE ; /* * Параметры выбранной команды. */ typedef struct { unsigned hut : 4 ; /*Время выгрузки головки*/ unsigned srt : 4 ; /* Уровень шага */ unsigned nd : 1 ; /* не-DMA */ unsigned hlt : 7 ; /*Время загрузки головки*/ - 28 - } SPECIFY ; /* * Блок параметров дискеты BIOS, адресуемый вектором 1Eh. */ typedef struct { SPECIFY specify ; char motor_wait ; /* 55-мс инкремены */ char sector_size ; /* Длина кода */ char EOT ; char GAP1 ; /* запись/чтение */ char DTL ; /* FFh */ char GAP2 ; /* формат */ char fill_char ; /* формат */ char head_settle ; /* мс */ char motor_startup ; /* 1/8 сек. */ } BIOS_DISK ; /* * Параметры операций чтения/записи 8272A */ typedef struct { char c ; char h ; char r ; char n ; char eot ; char gpl ; char dtl ; } RW_INPUT ; /* * Тиков до выключения мотора привода */ #define MOTOR_COUNT (*(unsigned char far *)MK_FP(0,0x440)) /* * write_data() and read_data() пишет/читает данные 8272A */ #define write_data(x) \ {\ while( ( inportb( FDC_MSR ) & 0xc0 ) != 0x80 ) ;\ outportb( FDC_DATA, (x) ) ;\ } #define read_data(x) \ {\ while( ( inportb( FDC_MSR ) & 0xc0 ) != 0xc0 ) ;\ (x) = inportb( FDC_DATA ) ;\ } /* * Определение LOOK_TIME разрешение времени исполнения команд * 8272A. */ #define LOOK_TIME /* * Определение HANG_ABORT разрешение жесткого завершения процедуры * после ABORT_WAIT тиков системного таймера. */ #define HANG_ABORT - 29 - #ifdef LOOK_TIME #define START start_time = get_exact_time() #define END elapsed_time = get_exact_time() - start_time #else #define START #define END #endif #ifdef HANG_ABORT #define HANG_START(x) hang_start(x) #define HANG_END hang_end() #else #define HANG_START(x) #define HANG_END #endif char FDD = 0 ; /*Номер текущего привода*/ #define ABORT_WAIT (2*16) /* Около 2 сек. */ #define BUFFER_SIZE (32*1024) #define MAX_SECTORS 40 /* * REVOLUTION_TIME установлена для 1.2 Mb AT HD привода. * Для всех остальных приводов меняется от 166L до 200L */ #define REVOLUTION_TIME (166L*2*1193) BIOS_DISK far *bios_disk ; union { char c ; ST0 x ; } st0 ; union { char c ; ST1 x ; } st1 ; union { char c ; ST2 x ; } st2 ; union { char c ; ST3 x ; } st3 ; unsigned char r_c ; /* # цилиндра из предыдущей операции */ unsigned char r_h ; /* Головка ... */ unsigned char r_r ; /* Сектор ... */ unsigned char r_n ; /* Размер кода сектора */ unsigned long start_time ; unsigned long elapsed_time ; unsigned char *buffer ; unsigned buffer_bytes = 0 ; volatile unsigned long bios_time = 0 ; jmp_buf hang_reset ; char *current_function ; unsigned rest_ticks ; /* До прекращения */ char abort_on_hangup ; FILE *out ; unsigned char mfm = 0x40 ; /* 0 для FM, 0x40 для MFM */ void interrupt (*old_int_0eh)( void ) ; void interrupt (*old_int_08h)( void ) ; /* * get_exact_time() возвращает 32-битовое время после пуска програм- * мы. Для перевода в секунды делить на 2,386,360. */ static unsigned long get_exact_time( void ) { asm pushf - 30 - asm sti asm jmp $+2 asm cli asm mov al, 0c2h asm out 43h, al asm jmp $+2 asm jmp $+2 asm in al, 40h asm mov bl, al /* Байт счетчика состояния */ asm jmp $+2 asm jmp $+2 asm in al, 40h asm mov ah, al asm jmp $+2 asm jmp $+2 asm in al, 40h asm xchg ah, al asm neg ax asm mov dx, word ptr bios_time asm rcl bl, 1 asm cmc asm rcl dx, 1 asm popf return( ( (unsigned long)_DX << 16 ) + _AX ) ; } void interrupt int_0eh( void ) { /* * Это никогда не должно произойти! */ outportb( 0x20, 0x20 ) ; printf( "Неопределенное прерывание FDC!\n" ) ; } void report_st0( void ) { printf( "Привод %c, головка %d :\n", 'A' + st0.x.ds, st0.x.h ) ; switch( st0.x.ic ){ case 0: break ; case 1: printf( "Ненормальное завершение операции\n" ) ; break ; case 2: printf( "Неверная команда\n" ) ; break ; case 3: printf( "Изменилось условие готовности диска\n" ) ; break ; } if( st0.x.se ) printf( "Установка завершена\n" ) ; if( st0.x.ec ) printf( "Поломка привода\n" ) ; if( st0.x.nr ) printf("Привод не готов или выбор головки 1 на одностороннем приводе\n"); } void hang_start( char *f ) - 31 - { current_function = f ; rest_ticks = ABORT_WAIT ; abort_on_hangup = 1 ; } void hang_end( void ) { abort_on_hangup = 0 ; } void install_fdc_driver( void ) { _AH = 0 ; _DL = FDD ; geninterrupt( 0x13 ) ; /* Сброс диска BIOS */ delay( 500 ) ; disable() ; bios_time = biostime( 0, 0 ) ; old_int_0eh = getvect( 0x0e ) ; old_int_08h = getvect( 0x08 ) ; outportb( 0x21, inportb( 0x21 ) | 0x40 ) ; /* Запрет прерывания */ setvect( 0x0e, int_0eh ) ; asm mov dx, offset int_08h_routine asm push ds asm mov ax, cs asm mov ds, ax asm mov ax, 02508h asm int 21h asm pop ds bios_disk = (void far *) getvect( 0x1e ) ; enable() ; asm jmp exit asm int_08h_routine label near asm extrn DGROUP@:word asm push ax asm push ds asm mov ds, cs:DGROUP@ asm add word ptr bios_time, 1 asm adc word ptr bios_time + 2, 0 asm push ds asm mov ax, 40h asm mov ds, ax asm mov byte ptr ds:[40h], 0ffh asm pop ds asm mov al, 20h asm out 20h, al asm cmp abort_on_hangup, 0 asm je done asm dec rest_ticks asm jg done longjmp( hang_reset, -1 ) ; done:; asm pop ds asm pop ax asm iret exit:; } void - 32 - specify( SPECIFY parms ) { START ; HANG_START( "Определить" ) ; parms.nd = 1 ; write_data( 0x03 ) ; write_data( *(char *)&parms ) ; write_data( *((char *)&parms + 1 ) ) ; HANG_END ; END ; printf( "HUT = %d ms\n", parms.hut * 16 ) ; printf( "HLT = %d ms\n", parms.hlt * 2 ) ; printf( "SRT = %d ms\n", 16 - parms.srt ) ; printf( "%sDMA mode\n", parms.nd ? "non-" : "" ) ; } void sence_is( void ) { write_data( 0x08 ) ; read_data( st0.c ) ; if( st0.c != 0x80 ) /* Неверная команда */ read_data( r_c ) ; } void wait_interrupt( void ) { do outportb( 0x20, 0x0a ) ; /* Требуется 8259A IRR */ while( ( inportb( 0x20 ) & 0x40 ) != 0x40 ) ; outportb( 0x20, 0x08 ) ; /* Стандартное состояние*/ } int recalibrate( void ) { START ; HANG_START( "Перекалибровка" ) ; write_data( 0x07 ) ; write_data( FDD ) ; wait_interrupt() ; do sence_is() ; while( st0.x.se != 1 ) ; HANG_END ; END ; if( ! st0.x.se || st0.x.ic != 0 ) report_st0() ; if( r_c == 0 ) return 0 ; return -1 ; } int seek( unsigned char cyl ) { START ; HANG_START( "Установка" ) ; write_data( 0x0f ) ; write_data( FDD ) ; write_data( cyl ) ; wait_interrupt() ; do sence_is() ; while( st0.x.se != 1 ) ; - 33 - HANG_END ; END ; if( ! st0.x.se || st0.x.ic != 0 ) report_st0() ; if( r_c == cyl ) return 0 ; return -1 ; } void start_operations( void ) { SPECIFY temp ; temp = bios_disk->specify ; temp.nd = 1 ; MOTOR_COUNT = UCHAR_MAX - 1 ; outportb( FDC_DIGITAL, FDD ) ; delay( 1 ) ; outportb( FDC_DIGITAL, 0x04 | FDD ) ; delay( 1 ) ; outportb( FDC_RATE, RATE_300 ) ; specify( temp ) ; outportb( FDC_DIGITAL, ( 0x10 << FDD ) | 0x0C | FDD ) ; delay( bios_disk->motor_startup * 120 ) ; } void reset_old_fdc( void ) { setvect( 0x0e, old_int_0eh ) ; setvect( 0x08, old_int_08h ) ; outportb( 0x21, inportb( 0x21 ) & ~0x40 ) ; biostime( 1, bios_time ) ; MOTOR_COUNT = 1 ; _AH = 0 ; _DL = 0 ; geninterrupt( 0x13 ) ; } void read_ST3( void ) { START ; HANG_START( "read_ST3" ) ; write_data( 0x04 ) ; write_data( FDD ) ; read_data( st3.c ) ; HANG_END ; END ; } /* * Работа контроллера в не-DMA режиме обслуживается ЦП, поэтому * read_operation,write_operation и format_track запрещают пре- * рывания на длительное время. Прошу прощения. */ void read_operation( int code, int head, RW_INPUT *param, char far *buf, unsigned max_len ) { char *p = (void *)param ; int i = sizeof( RW_INPUT ) ; - 34 - buffer_bytes = 0 ; START ; HANG_START( "операция чтения/записи" ) ; /* * Послать параметр команды w/ в 8272A */ write_data( code ) ; write_data( ( ( head & 1 ) << 2 ) | FDD ) ; for( ; i > 0 ; i--, p++ ) write_data( *p ) ; /* * Ждать до начала фазы исполнения */ while( !( inportb( FDC_MSR ) & 0x20 ) ) ; disable() ; /* * Разрешение на передачу данных */ asm mov cx, max_len asm mov dx, FDC_MSR asm les di, buf asm xor si, si asm mov bx, 1+1 asm cld input_loop: asm in al, dx asm test al, 20h asm jz exit asm test al, 80h asm jz input_loop asm inc dx asm in al, dx asm stosb asm inc si asm dec dx asm loop input_loop exit:; enable() ; asm mov buffer_bytes, si /* * Ждать конца фазы выполнения */ wait_interrupt() ; /* * Читать результат */ read_data( st0.c ) ; read_data( st1.c ) ; read_data( st2.c ) ; read_data( r_c ) ; read_data( r_h ) ; read_data( r_r ) ; read_data( r_n ) ; HANG_END ; END ; } void write_operation( int code, int head, RW_INPUT *param, char far *buf, unsigned max_len ) { char *p = (void *)param ; int i = sizeof( RW_INPUT ) ; - 35 - buffer_bytes = 0 ; START ; HANG_START( "операция чтения/записи" ) ; /* * Послать параметр команды w/ в 8272A */ write_data( code ) ; write_data( ( ( head & 1 ) << 2 ) | FDD ) ; for( ; i > 0 ; i--, p++ ) write_data( *p ) ; /* * Ждать до начала фазы выполнения */ while( !( inportb( FDC_MSR ) & 0x20 ) ) ; disable() ; /* * Быть готовым передать данные */ asm push ds asm mov cx, max_len asm mov dx, FDC_MSR asm lds si, buf asm xor di, di asm cld wait_start: asm in al, dx asm test al, 20h asm jz wait_start output_loop: asm in al, dx asm test al, 20h asm jz exit asm test al, 80h asm jz output_loop asm inc dx asm outsb asm inc di asm dec dx asm loop output_loop /* * FDC требует больше данных, но данных слева нет, так что они * разрушаются. */ outportb( FDC_DIGITAL, 0 ) ; delay( 5 ) ; outportb( FDC_DIGITAL, ( 0x10 << FDD ) | 0x0C | FDD ) ; asm pop ds asm mov buffer_bytes, di HANG_END ; END ; enable() ; specify( bios_disk->specify ) ; return ; exit:; enable() ; asm pop ds asm mov buffer_bytes, di /* * Ждать конец фазы исполнения */ wait_interrupt() ; /* - 36 - * Read result */ read_data( st0.c ) ; read_data( st1.c ) ; read_data( st2.c ) ; read_data( r_c ) ; read_data( r_h ) ; read_data( r_r ) ; read_data( r_n ) ; HANG_END ; END ; } void format_track(int head, int n, int sc, int gpl, int d, char far *buf, int lim ) { START ; HANG_START( "Формат дорожки" ) ; /* * Передать параметры команды записи в 8272A */ write_data( 0x0D | mfm ) ; write_data( ( ( head & 1 ) << 2 ) | FDD ) ; write_data( n ) ; write_data( sc ) ; write_data( gpl ) ; write_data( d ) ; /* * Ждать до начала фазы исполнения */ while( !( inportb( FDC_MSR ) & 0x20 ) ) ; disable() ; /* * Быть готовым к передаче данных */ asm push ds asm mov cx, lim asm mov dx, FDC_MSR asm lds si, buf asm xor di, di asm cld output_loop: asm in al, dx asm test al, 20h asm jz exit asm test al, 80h asm jz output_loop asm inc dx asm lodsb asm out dx, al asm inc di asm dec dx asm loop output_loop /* * FDC нужно больше данных, но больше данных нет, так * что они разрушаются */ delay( 1 ) ; outportb( FDC_DIGITAL, 0 ) ; delay( 5 ) ; outportb( FDC_DIGITAL, ( 0x10 << FDD ) | 0x0C | FDD ) ; asm pop ds - 37 - asm mov buffer_bytes, di HANG_END ; END ; enable() ; specify( bios_disk->specify ) ; return ; exit:; enable() ; asm pop ds asm mov buffer_bytes, di /* * Wait execution phase end */ wait_interrupt() ; /* * Читать результат */ read_data( st0.c ) ; read_data( st1.c ) ; read_data( st2.c ) ; read_data( r_c ) ; read_data( r_h ) ; read_data( r_r ) ; read_data( r_n ) ; HANG_END ; END ; } void read_sector( int head, RW_INPUT *param ) { read_operation( 0x06 | mfm, head, param, buffer, BUFFER_SIZE ) ; } void read_deleted( int head, RW_INPUT *param ) { read_operation( 0x0C | mfm, head, param, buffer, BUFFER_SIZE ) ; } void read_track( int head, RW_INPUT *param ) { read_operation( 0x02 | mfm, head, param, buffer, BUFFER_SIZE ) ; } void write_sector( int head, unsigned count, RW_INPUT *param ) { write_operation( 0x05 | mfm, head, param, buffer, count ) ; } void write_deleted( int head, unsigned count, RW_INPUT *param ) { write_operation( 0x09 | mfm, head, param, buffer, count ) ; } void read_address( int head ) { START ; - 38 - HANG_START( "Read_address" ) ; write_data( 0x0a | mfm ) ; write_data( ( ( head & 1 ) << 2 ) | FDD ) ; wait_interrupt() ; read_data( st0.c ) ; read_data( st1.c ) ; read_data( st2.c ) ; read_data( r_c ) ; read_data( r_h ) ; read_data( r_r ) ; read_data( r_n ) ; HANG_END ; END ; } void explain_ST3( void ) { printf( "ST3 is %02X\n", st3.c ) ; printf( "Drive %c is %s\n", 'A' + st3.x.ds, st3.x.rdy ? "ready" : "not ready" ) ; printf( "Selected head %d\n", st3.x.h ) ; if( st3.x.ft ) printf( "!!!!! Drive fault !!!!!\n" ) ; if( st3.x.wp ) printf( "Write protected\n" ) ; if( st3.x.t0 ) printf( "Currently at track 0\n" ) ; } void print_rw_return( void ) { printf( "STn : %02x %02x %02x\n", st0.c, st1.c, st2.c ) ; printf( "Drive %c, head %d :\n", 'A' + st0.x.ds, st0.x.h ) ; switch( st0.x.ic ){ case 0: break ; case 1: printf( "Abnormal operation termination\n" ) ; break ; case 2: printf( "Illegal command\n" ) ; break ; case 3: printf( "Disk drive ready condition changed\n" ) ; break ; } if( st0.x.se ) printf( "Seek ended\n" ) ; if( st0.x.ec ) printf( "Drive fault\n" ) ; if( st0.x.nr ) printf( "Drive not ready or head 1 selected on single-sided drive\n" ); if( st1.x.en ) printf( "End of track error\n" ) ; if( st1.x.de ) if( st2.x.dd ) printf( "User data CRC error\n" ) ; else printf( "Sector ID CRC error\n" ) ; if( st1.x.or ) printf( "Overrun error\n" ) ; if( st1.x.nd ) - 39 - printf( "Sector not found\n" ) ; if( st1.x.nw ) printf( "Write protect error\n" ) ; if( st1.x.ma ) printf( "Missing address mark\n" ) ; if( st2.x.cm ) printf("Control mark-Deleted data on Read Data or reverse\n"); if( st2.x.wc ) printf( "Sector ID cylinder number do not match\n" ) ; if( st2.x.sh ) printf( "Scan condition hit\n" ) ; if( st2.x.sn ) printf( "Scan not satisfied\n" ) ; if( st2.x.bc ) printf( "IBM bad track\n" ) ; if( st2.x.md ) printf( "Missing data address mark\n" ) ; } void read_rw_input( RW_INPUT *p ) { int temp ; printf( "C = " ) ; scanf( "%x", &temp ) ; p->c = temp ; printf( "H = " ) ; scanf( "%x", &temp ) ; p->h = temp ; printf( "R = " ) ; scanf( "%x", &temp ) ; p->r = temp ; printf( "N = " ) ; scanf( "%x", &temp ) ; p->n = temp ; printf( "EOT = " ) ; scanf( "%x", &temp ) ; p->eot = temp ; printf( "GPL = " ) ; scanf( "%x", &temp ) ; p->gpl = temp ; printf( "DTL = " ) ; scanf( "%x", &temp ) ; p->dtl = temp ; } void print_chrn( void ) { printf( "Cyl = %2x Head = %2x Sect = %2x Siz = %1x\n", r_c, r_h, r_r, r_n ) ; } /* analyze_track() очень простой анализатор дорожки, основанный на ко- манде чтения идентификатора сектора. Он не способен найти специально спрятанные секторы. */ void analyze_track( int cyl, int head ) { struct _x { char c, h, r, n ; long time_diff ; } ; struct _x sector_table[ MAX_SECTORS ] ; struct _x *p ; int i ; long start, end ; int track_size ; - 40 - RW_INPUT param ; seek( cyl ) ; fprintf( stderr, "Analyzing track %02X ... Please Wait\n", cyl ) ; param.c = 0xff ; param.h = 0xff ; param.r = 0x0 ; param.n = 0xff ; param.eot = 0xff ; param.gpl = 0x20 ; param.dtl = 0xff ; disable() ; /* * Следующий read_sector заканчивается сразу после чтения индексного * отверстия */ do { param.r-- ; read_sector( head, ¶m ) ; } while( ( ! st1.x.nd ) && ( ! st1.x.ma ) ) ; if( st1.x.ma ){ print_rw_return() ; return ; } start = get_exact_time() ; for( i = 0, p = sector_table ; i < MAX_SECTORS ; i++, p++ ){ read_address( head ) ; end = get_exact_time() ; enable() ; p->c = r_c ; p->h = r_h ; p->r = r_r ; p->n = r_n ; if( st1.x.ma ){ print_rw_return() ; return ; } p->time_diff = end - start ; if( p->time_diff > REVOLUTION_TIME ) break ; } track_size = i ; fprintf(out, "%02X sectors found on track %02X side %1X\n", track_size, cyl, head ) ; fprintf(out, " Cyl Hd Sec N Pos (ms) Off (ms) Cyl Hd Sec N Pos (ms) Off (ms)\n" ) ; for( i = 0, p = sector_table ; i < track_size ; i++, p++ ){ fprintf( out, "#%2d - %02X %1X %02X %1X %7.3lf %7.3lf%c", i, p->c, p->h, p->r, p->n, p->time_diff / ( 1193.18 * 2 ), ( i == ( track_size - 1 ) ? ( sector_table->time_diff + REVOLUTION_TIME - p->time_diff ) : ( (p+1)->time_diff - p->time_diff ) ) / ( 1193.18 * 2 ), i % 2 ? '\n' : '\t' ) ; } if( i % 2 == 1 ) fputc( '\n', out ) ; } void analyze_disk( int from, int count ) { int i ; - 41 - for( i = 0 ; i < count ; i++, from++ ){ analyze_track( from, 0 ) ; analyze_track( from, 1 ) ; } } #define LINE_SIZE 16 #define SCREEN_SIZE 10 #define PAGE_SIZE (SCREEN_SIZE*LINE_SIZE) void draw_buffer( char *start, unsigned lines, unsigned num_start ) { int i ; for( ; lines-- > 0 ; num_start += LINE_SIZE, start += LINE_SIZE ){ printf( "%04X ", num_start ) ; for( i = 0 ; i < LINE_SIZE ; i++ ) printf( "%02X ", start[ i ] ) ; for( i = 0 ; i < LINE_SIZE ; i++ ) putchar( isprint( start[ i ] ) ? start[ i ] : '.' ) ; putchar( '\n' ) ; } } void buffer_operations( void ) { unsigned current_offset = 0 ; int need_redraw = 1 ; unsigned offset, len ; unsigned pattern ; char temp[ 80 ] ; int handle ; START ; while( 1 ){ if( need_redraw ){ draw_buffer(buffer + current_offset, SCREEN_SIZE, current_offset); printf( "[H,U,D,G,F,S,E,R,W,C,X]\n" ) ; } need_redraw = 1 ; switch( toupper( (char)bioskey( 0 ) ) ){ case 'X': case 0x1b: END ; return ; case 'U': if( current_offset >= PAGE_SIZE ) current_offset -= PAGE_SIZE ; else need_redraw = 0 ; break ; case 'D': if( current_offset < BUFFER_SIZE - 2 * PAGE_SIZE ) current_offset += PAGE_SIZE ; else need_redraw = 0 ; break ; case 'G': printf( "Go to offset : " ) ; scanf( "%x", ¤t_offset ) ; if( current_offset >= BUFFER_SIZE - PAGE_SIZE ) current_offset = 0 ; break ; - 42 - case 'F': printf( "Fill from offset : " ) ; scanf( "%x", &offset ) ; printf( "Length : " ) ; scanf( "%x", &len ) ; printf( "Pattern : " ) ; scanf( "%x", &pattern ) ; setmem( buffer + offset, len, pattern ) ; break ; case 'S': printf( "Set at offset : " ) ; scanf( "%x", &offset ) ; printf( "Value : " ) ; scanf( "%x", &pattern ) ; buffer[ offset ] = pattern ; break ; case 'E': printf( "Set at offset : " ) ; scanf( "%x", &offset ) ; printf( "Byte count : " ) ; scanf( "%x", &len ) ; for( ; len-- > 0 ; offset++ ){ printf( "Value : " ) ; scanf( "%x", &pattern ) ; buffer[ offset ] = pattern ; } break ; case 'R': printf( "Read to offset : " ) ; scanf( "%x", &offset ) ; printf( "Byte count : " ) ; scanf( "%x", &len ) ; printf( "File : " ) ; scanf( "%s", temp ) ; if( ( handle = _open( temp, O_RDONLY | O_BINARY ) ) == -1 ){ perror( temp ) ; break ; } if( _read( handle, buffer + offset, len ) != len ) perror( temp ) ; _close( handle ) ; break ; case 'W': printf( "Write from offset : " ) ; scanf( "%x", &offset ) ; printf( "Byte count : " ) ; scanf( "%x", &len ) ; printf( "File : " ) ; scanf( "%s", temp ) ; if( ( handle = _creat( temp, 0 ) ) == -1 ){ perror( temp ) ; break ; } if( _write( handle, buffer + offset, len ) != len ) perror( temp ) ; _сlose( handle ) ; break ; case 'C': printf( "Set at offset : " ) ; scanf( "%x\n", &offset ) ; printf( "Type string : " ) ; gets( temp ) ; memcpy( buffer + offset, temp, strlen( temp ) ) ; - 43 - break ; case 'H': printf( "U - Up D - Down G - Go to F - Fill S - Set byte\n" "E - Enter R - Read W - Write X - Exit C - set string\n"); default: need_redraw = 0 ; } } } main( int argc, char *argv[] ) { int cyl, hd ; char key ; RW_INPUT rw ; char name[ 20 ] ; int n, sc, gpl, d, lim ; if( ( buffer = malloc( BUFFER_SIZE ) ) == NULL ){ printf( "Can't allocate buffer space !\n" ) ; return( 3 ) ; } if( argc > 1 ) FDD = atoi( argv[ 1 ] ) ; delay( 1 ) ; install_fdc_driver() ; start_operations() ; do { switch( setjmp( hang_reset ) ){ case 0: break ; default: fprintf(stderr,"Unexpected hangup in function %s\n", current_function); start_operations() ; } fprintf( stderr, "-----[H,S,D,R,A,E,B,T,F,L,P,W,C,O,I,M,Z] : " ) ; do key = toupper( bioskey( 0 ) ) ; while( key == 0 ) ; printf( "%c\n", key ) ; switch( key ){ case 'H': printf("H - Help S - Seek D - ST3 R - Read sector\n" ) ; printf("A - Read ID E - Recalibrate B - BufOps T - Analyze track\n" ) ; printf("F - AnalizeDisk L - ReadDeleted P - Data rate W - WriteData\n" ) ; printf("C - read traCk O - fOrmat I - wrItedelete M - MFM/FM toggle\n" ) ; printf("Z - Exit\n" ) ; break ; case 'S': printf( "*Seek to cylinder : " ) ; scanf( "%x", &cyl ) ; seek( cyl ) ; break ; case 'D': read_ST3() ; explain_ST3() ; break ; case 'R': printf( "*Read data\n" ) ; printf( "Head : " ) ; scanf( "%x", &hd ) ; - 44 - read_rw_input( &rw ) ; read_sector( hd, &rw ) ; print_rw_return() ; printf( "%d (%#4x) bytes read\n", buffer_bytes, buffer_bytes ) ; printf( "Operation time is not reliable !\n" ) ; break ; case 'L': printf( "*Read deleted data\n" ) ; printf( "Head : " ) ; scanf( "%x", &hd ) ; read_rw_input( &rw ) ; read_deleted( hd, &rw ) ; print_rw_return() ; printf( "%d (%#4x) bytes read\n", buffer_bytes, buffer_bytes ) ; printf( "Operation time is not reliable !\n" ) ; break ; case 'C': printf( "*Read a track\n" ) ; printf( "Head : " ) ; scanf( "%x", &hd ) ; read_rw_input( &rw ) ; read_track( hd, &rw ) ; print_rw_return() ; printf( "%d (%#4x) bytes read\n", buffer_bytes, buffer_bytes ) ; printf( "Operation time is not reliable !\n" ) ; break ; case 'W': printf( "*Write data\n" ) ; printf( "Head : " ) ; scanf( "%x", &hd ) ; printf( "Byte count : " ) ; scanf( "%x", &cyl ) ; read_rw_input( &rw ) ; write_sector( hd, cyl, &rw ) ; print_rw_return() ; printf( "%d (%#4x) bytes written\n", buffer_bytes, buffer_bytes ); printf( "Operation time is not reliable !\n" ) ; break ; case 'I': printf( "*Write deleted\n" ) ; printf( "Head : " ) ; scanf( "%x", &hd ) ; printf( "Byte count : " ) ; scanf( "%x", &cyl ) ; read_rw_input( &rw ) ; write_deleted( hd, cyl, &rw ) ; print_rw_return() ; printf( "%d (%#4x) bytes written\n", buffer_bytes, buffer_bytes ); printf( "Operation time is not reliable !\n" ) ; break ; case 'A': printf( "*Read address\n" ) ; printf( "Head = " ) ; scanf( "%x", &cyl ) ; read_address( cyl ) ; print_rw_return() ; print_chrn() ; break ; case 'E': printf( "*Recalibrating\n" ) ; recalibrate() ; break ; case 'B': - 45 - buffer_operations() ; break ; case 'T': printf( "Analyze cylinder : " ) ; scanf( "%x", &cyl ) ; printf( "Head : " ) ; scanf( "%x", &hd ) ; out = stdout ; analyze_track( cyl, hd ) ; break ; case 'F': printf( "Analyze from cylinder : " ) ; scanf( "%x", &cyl ) ; printf( "Cylinder count : " ) ; scanf( "%x", &hd ) ; printf( "File for output : " ) ; scanf( "%s", name ) ; if( ( out = fopen( name, "wt" ) ) == NULL ){ perror( name ) ; break ; } analyze_disk( cyl, hd ) ; fclose( out ) ; break ; case 'P': printf( "Select rate : 0 - 500 KBS, 1 - 300 KBS, 2 - 250 KBS\n" ) ; scanf( "%x", &cyl ) ; outportb( FDC_RATE, cyl ) ; break ; case 'O': printf( "*Format a track using IDs from buffer\n" ) ; printf( "Head : " ) ; scanf( "%x", &hd ) ; printf( "N = " ) ; scanf( "%x", &n ) ; printf( "SC = " ) ; scanf( "%x", &sc ) ; printf( "GPL = " ) ; scanf( "%x", &gpl ) ; printf( "D = " ) ; scanf( "%x", &d ) ; printf( "TC = " ) ; scanf( "%x", &lim ) ; format_track( hd, n, sc, gpl, d, buffer, lim ) ; print_rw_return() ; printf( "%x bytes transferred as IDs\n", buffer_bytes ) ; break ; case 'M': if( mfm ) mfm = 0 ; else mfm = 0x40 ; printf( "Current mode is %s\n", mfm ? "MFM" : "FM" ) ; break ; case 'Z': reset_old_fdc() ; return( 0 ) ; default: printf( "No action for this key : %c\n", key ) ; break ; } printf( "Elapsed time is %8.3f ms\n", elapsed_time / ( 1193.180 * 2 ) ) ; } while( 1 ) ; } - 46 - 5. Приложение B. Простая программа тестирования HDC Текст программы находится в файле hd_scan.c, который также требу- ет TC 2.0 и TASM для компиляции. Программа принимает идентификатор дисковода BIOS (80 для 1-го дисковода, 81 для 2-го). Если этот тест зависает после печати сообщения : " Cyls = XXX, Heads = XXX, Sectors = XXX ", все программы оптимизации диска должны быть удалены из памяти до вы- полнения этого теста. static char __rights__[]="Модуль тестирования жесткого диска. (C) 1991 Сергей С. Пашкович" ; #ifndef __TINY__ #error Необходимо компилировать в модели TINY! #endif #pragma inline #include #include #include #include #define VERSION "0.0" #define MAX_ROMS 100 #define MAX_SECTORS 64 #define SECTOR_SIZE 512 #define TIMER_CLOCK_RATE (2*1193180ul) #define INT_13() geninterrupt(0x13) #define BIOS_13() (*BIOS_entry)() unsigned HDD ; unsigned cyls, heads, sectors ; FILE *report ; void interrupt (*BIOS_entry)( void ) ; unsigned ROM_count = 0 ; unsigned long revolution_time ; unsigned char error_code ; double ForcedRPS = 0.0 ; struct _Z_ { unsigned char sector ; unsigned long position ; } ; static struct _Y_ { unsigned ROM_start ; /* para */ unsigned ROM_end ; /* para */ } ROM_list[ MAX_ROMS ] ; static struct _X_ { unsigned char code ; char *message ; } disk_errors[] = { 0x00, "Функция выполнена успешно", 0x01, "Неверное значение или неверная функция", 0x02, "Не найдена адресная метка", 0x03, "Защита записи", 0x04, "Сектор не найден", 0x05, "Ошибка установки", 0x07, "Ошибка параметра активации", 0x08, "Сбой DMA", 0x09, "Попытка доступа к DMA свыше 64K", - 47 - 0x0A, "Плохой флаг сектора", 0x0D, "Плохой номер сектора (формат)", 0x0E, "Обнаружены контрольные данные адрес. метки", 0x0F, "Неверное значение уровня арбитража DMA", 0x10, "Неисправимая ошибка ECC", 0x11, "Данные скорректированы ECC", 0x20, "Поломка контроллера диска", 0x40, "Ошибка операции установки", 0x80, "Жесткий диск не готов", 0xBB, "Неопределенная ошибка", 0xCC, "Ошибка записи", 0xE0, "Регистр ошибок обнулен", } ; char * get_error_name( unsigned char code ) { int i ; static char buf[ 80 ] ; for( i = 0 ; i < sizeof disk_errors / sizeof( struct _X_ ) ; i++ ) if(code == disk_errors[ i ].code) return disk_errors[ i ].message; sprintf( buf, "Unknown error code 0x%02X", code ) ; return buf ; } unsigned long get_exact_time( void ) { asm xor ax, ax asm mov es, ax asm pushf asm sti repeat_request: asm mov al, 0c2h asm cli asm out 43h, al asm jmp $+2 asm jmp $+2 asm in al, 40h asm jmp $+2 asm mov bl, al /* Счетчик байта состояния */ asm in al, 40h asm jmp $+2 asm mov ah, al asm in al, 40h asm mov dx, es:[46Ch] asm sti asm xchg ah, al asm cmp dx, es:[46Ch] asm jne repeat_request asm popf asm neg ax asm shl bl, 1 asm cmc asm rcl dx, 1 return ( (unsigned long)_DX << 16 ) + _AX ; } void init_timer_channel( unsigned char channel ) { asm pushf - 48 - asm cli asm mov al, channel asm mov cl, 6 asm shl al, cl asm or al, 36h asm out 43h, al asm mov dx, 40h asm add dl, channel asm jmp $+2 asm jmp $+2 asm xor al, al asm out dx, al asm jmp $+2 asm jmp $+2 asm out dx, al asm popf } int parce_parameters( int argc, char **argv ) { printf( "HD_scan v. " VERSION " (C) 1991 Serge S. Pachkovsky\n" ) ; printf("Values reported can be incorrect for a caching disk controllers\n" ) ; if( argc < 2 ){ printf( "HD_scan BIOS_disk_ID [Drive RPS]\n" ); return -1 ; } if( sscanf( argv[ 1 ], "%x", &HDD ) != 1 ){ printf( "Invalid disk ID %s\n", argv[ 1 ] ) ; return -1 ; } if( argc > 2 ) if( sscanf( argv[ 2 ], "%lg", &ForcedRPS ) != 1 ){ printf( "Invalid drive revolutions_per_secons %s\n", argv[ 2 ] ); return -1 ; } return 0 ; } int init_files( void ) { if( ( report = fopen( "HD_scan.rep", "wt" ) ) == NULL ){ perror( "HD_scan.rep" ) ; return -1 ; } fprintf(report, "Hard disk scan v " VERSION " report on disk %X\n", HDD ); return 0 ; } int read_HDD_params( unsigned char disk ) { unsigned dx, cx ; _DL = disk ; _AH = 0x08 ; INT_13() ; dx = _DX ; cx = _CX ; heads = dx >> 8 ; sectors = cx & 0x3F ; - 49 - cyls = ( cx >> 8 ) | ( ( ( cx >> 6 ) & 3 ) << 8 ) ; return 0 ; } int scan_ROMs( void ) { unsigned seg ; unsigned char far *ptr ; struct _Y_ *x = ROM_list ; for( seg = 0xC800 ; seg < 0xE000 ; seg += 0x80 ){ ptr = MK_FP( seg, 0 ) ; if( ptr[ 0 ] == 0x55 && ptr[ 1 ] == 0xAA ){ /* Найден признак расширенной памяти */ x->ROM_start = seg ; x->ROM_end = seg + (unsigned)ptr[ 2 ] * (512/16) - 1 ; x++ ; ROM_count++ ; } } x->ROM_start = 0xF000 ; x->ROM_end = 0xFFFF ; ROM_count++ ; return 0 ; } void interrupt (*trace_to_BIOS( void interrupt (*start)(), unsigned char disk ))() { static void interrupt (*old_01)( void ) ; static void interrupt (*entry_point)( void ) ; static unsigned char trace_on ; static void interrupt (*trace_dst)( void ) ; entry_point = start ; old_01 = getvect( 0x01 ) ; trace_on = 1 ; asm push cs asm lea ax, trace asm push ax asm mov ax, 1 asm push ax asm call setvect asm add sp, 6 asm pushf asm push cs asm lea ax, normal_return asm push ax asm pushf asm pop ax asm or ah, 1 asm push ax asm popf asm pushf asm push word ptr start + 2 asm push word ptr start + 0 asm mov ah, 8 asm mov dl, disk asm iret asm normal_return label near setvect( 0x01, old_01 ) ; asm jmp end_routine asm trace label near - 50 - asm push bp asm mov bp, sp asm push ax asm push bx asm push cx asm push ds asm push es asm mov ax, cs asm mov ds, ax /* * BP -> 0 old BP * 2 IP * 4 CS * 6 Flags * 8 IP or flags * 10 CS * 12 flags */ asm cmp trace_on, 1 asm je proceed_trace stop_trace: asm and byte ptr ss:[bp+6+1], NOT 1 /* clear TF */ exit_trace: asm pop es asm pop ds asm pop cx asm pop bx asm pop ax asm pop bp asm iret proceed_trace: asm mov ax, ss:[bp+2] asm shr ax, 1 asm shr ax, 1 asm shr ax, 1 asm shr ax, 1 asm add ax, ss:[bp+4] asm lea bx, ROM_list asm mov cx, ROM_count look_next_ROM_block: asm cmp ax, ds:[bx].ROM_start asm jb bad_ROM asm cmp ax, ds:[bx].ROM_end asm ja bad_ROM asm mov ax, ss:[bp+2] asm mov word ptr entry_point + 0, ax asm mov ax, ss:[bp+4] asm mov word ptr entry_point + 2, ax asm jmp stop_trace bad_ROM: asm add bx, 4 asm loop look_next_ROM_block asm les bx, dword ptr ss:[bp+2] asm mov ax, word ptr es:[bx] asm cmp al, 0cfh asm je trace_iret_command asm cmp al, 09dh asm je trace_popf_command asm cmp al, 0cdh asm je trace_intn_command asm cmp al, 0cch asm je trace_int3_command asm jmp exit_trace - 51 - trace_iret_command: asm or byte ptr ss:[bp+12+1], 1 asm jmp exit_trace trace_popf_command: asm or byte ptr ss:[bp+8+1], 1 asm jmp exit_trace trace_int3_command: asm inc word ptr ss:[bp+2] asm mov ah, 3 asm jmp trace_interrupt trace_intn_command: asm cmp ah, 10h asm jb OK_to_trace asm cmp ah, 13h asm jb exit_trace asm je OK_to_trace asm cmp ah, 1dh asm jb exit_trace OK_to_trace: asm add word ptr ss:[bp+2], 2 asm jmp trace_interrupt trace_interrupt: asm xor bx, bx asm mov es, bx asm mov bl, ah asm shl bx, 1 asm shl bx, 1 asm mov ax, word ptr es:[bx] asm mov word ptr trace_dst, ax asm mov ax, word ptr es:[bx+2] asm mov word ptr trace_dst+2, ax asm pop es asm pop ds asm pop cx asm pop bx asm pop ax asm pop bp asm pushf asm push word ptr cs:trace_dst + 2 asm push word ptr cs:trace_dst + 0 asm iret end_routine:; return entry_point ; } int detect_BIOS_entry( unsigned char disk ) { if( scan_ROMs() == -1 ) return -1 ; if( ( BIOS_entry = trace_to_BIOS( getvect( 0x13 ), disk ) ) == NULL) return -1 ; return 0 ; } int detect_HDD_params( void ) { if( read_HDD_params( HDD ) == -1 ) return -1 ; fprintf( report, "Cyls = %u, Heads = %u, Sectors = %u\n", cyls + 1, heads + 1, sectors ) ; printf( "Cyls = %u, Heads = %u, Sectors = %u\n", cyls + 1, heads + - 52 - 1, sectors ) ; if( detect_BIOS_entry( HDD ) == -1 ) return -1 ; fprintf( report, "Hard disk BIOS entry point %Fp\n", BIOS_entry ) ; printf( "Hard disk BIOS entry point %Fp\n", BIOS_entry ) ; return 0 ; } int init_system_timer( void ) { init_timer_channel( 0 ) ; return 0 ; } void read_sector(unsigned cyl,unsigned head,unsigned sector,char buf[512]) { unsigned cx, dx ; cx = ( sector & 0x3F ) | ( cyl << 8 ) | ( ( cyl >> 8 ) << 6 ) ; dx = ( head << 8 ) | HDD ; _ES = FP_SEG( buf ) ; _BX = FP_OFF( buf ) ; _CX = cx ; _DX = dx ; _AX = 0x0201 ; BIOS_13() ; switch( error_code = _AH ){ case 1: case 3: case 5: case 7: case 8: case 9: case 0x0D: case 0x0F: case 0x20: case 0x40: case 0x80: case 0xBB: case 0xCC: case 0xE0: fprintf( stderr, "%s", get_error_name(error_code)); exit( 255 ) ; } } int detect_rotation_speed( void ) { int i ; unsigned long start, end ; char buf[ 512 ] ; _AX = 0 ; _DL = HDD ; BIOS_13() ; read_sector( 0, 0, 1, buf ) ; start = get_exact_time() ; for( i = 0 ; i < 500 ; i++ ){ _AX = 0 ; _DL = HDD ; BIOS_13() ; read_sector( 0, 0, 1, buf ) ; } end = get_exact_time() ; revolution_time = ( end - start ) / i ; - 53 - fprintf( report, "Measured drive RPM is %lu\n", 60*TIMER_CLOCK_RATE / revolution_time ) ; printf( "Measured drive RPM is %lu\n", 60 * TIMER_CLOCK_RATE / revolution_time ) ; if( ForcedRPS != 0.0 ){ revolution_time = TIMER_CLOCK_RATE / ForcedRPS ; fprintf( report, "Forced drive RPM is %lu\n", 60 * TIMER_CLOCK_RATE / revolution_time ) ; printf( "Forced drive RPM is %lu\n", 60 * TIMER_CLOCK_RATE / revolution_time ) ; } return 0 ; } int check_sectors( unsigned cyl, unsigned head, struct _Z_ *p ) { int secs = 0 ; unsigned i ; char buf[ 512 ] ; for( i = 1 ; i <= sectors ; i++ ){ read_sector( cyl, head, i, buf ) ; if( error_code != 0 ) fprintf( report, "\t%s at sector %4u/%2u/%2u\n",get_error_name( error_code ), cyl, head, i ) ; if( error_code == 0 || error_code == 0x0A || error_code == 0x10 || error_code == 0x11 ){ p++->sector = i ; secs++ ; } } return secs ; } int get_read_times(unsigned cyl,unsigned head,int sectors,struct _Z_ *table) { int i ; char buf[ 512 ] ; unsigned long index ; read_sector( cyl, head, sectors + 1, buf ) ; index = get_exact_time() ; for( i = 0 ; i < sectors ; i += 2 ){ read_sector( cyl, head, table[ i ].sector, buf ); table[ i ].position = get_exact_time() - index; } for( i = 1 ; i < sectors ; i += 2 ){ read_sector( cyl, head, table[ i ].sector, buf ) ; table[ i ].position = get_exact_time() - index ; } return 0 ; } int cmp_positions( const struct _Z_ *a, const struct _Z_ *b ) { if( a->position < b->position ) return -1 ; if( a->position > b->position ) return 1 ; return 0 ; } - 54 - int process_read_times( int sectors, struct _Z_ *table ) { int i ; for( i = 0 ; i < sectors ; i++ ) while( table[ i ].position > revolution_time ) table[ i ].position -= revolution_time ; qsort( table, sectors, sizeof( struct _Z_ ), cmp_positions ) ; return 0 ; } int report_track_ordering( int sectors, struct _Z_ *table ) { int i ; for( i = 0 ; i < sectors ; i++ ){ if( i % 17 == 0 ){ putc( '\t', report ) ; if( i != 0 ) putc( '\t', report ) ; } fprintf( report, "%02d ", table[ i ].sector ) ; if( i % 17 == 16 ) putc( '\n', report ) ; } if( i % 17 != 0 ) putc( '\n', report ) ; return 0 ; } int scan_track( unsigned cyl, unsigned head ) { struct _Z_ sector_table[ MAX_SECTORS ] ; int sectors ; if( ( sectors = check_sectors( cyl, head, sector_table ) ) == -1 ) return -1 ; if( get_read_times( cyl, head, sectors, sector_table ) == -1 ) return -1 ; if( process_read_times( sectors, sector_table ) == -1 ) return -1 ; fprintf( report, "%4u/%2u\t", cyl, head ) ; if(report_track_ordering(sectors, sector_table ) == -1 ) return -1; return 0 ; } int scan_surface( void ) { unsigned cyl, head ; for( cyl = 0 ; cyl <= cyls ; cyl++ ){ for( head = 0 ; head <= heads ; head++ ){ fprintf( stderr, "\rTrack %4d, head %2d ", cyl, head ) ; if( scan_track( cyl, head ) == -1 ) return -1 ; fflush( report ) ; } } fputc( '\n', stderr ) ; return -1 ; } - 55 - int main( int argc, char *argv[] ) { if( parce_parameters( argc, argv ) == -1 ) return 1 ; if( init_files() == -1 ) return 2 ; if( detect_HDD_params() == -1 ) return 3 ; if( init_system_timer() == -1 ) return 4 ; if( detect_rotation_speed() == -1 ) return 5 ; if( scan_surface() == -1 ) return 6 ; return 0 ; } 6. Приложение C. Как выяснить номер кластера Текст программы находится в файле cluster.c, требуются TC 2.0 и TASM для компиляции. Полное имя файла (то есть, включая дисковод и каталог, даже файл находится в текущем каталоге) должно вводиться, как параметр. Хотя этот пример проверялся на ОС MS DOS 3.20, 3.30, 4.00 и 5.00 (beta), он может не работать под другими версиями MS DOC или совместимыми системами, потому что он основывается на структуре неописанного DiskInfoBlock. static char __rights__[] = "(C) 1991 Serge S. Pachkovsky" ; #pragma inline #include #include #include #include #include #include #include #include #include #include #include #include typedef struct _DI { unsigned char drv ; /* Номер привода 0 - A: */ unsigned char subunit ; /*От головки устройства*/ unsigned sect_siz ; /* Размер сектора */ unsigned char hi_sector ;/*Скрытый сектор в клас*/ unsigned char cls_shift ;/* Сдвиг класт. к сект.*/ unsigned boot_siz ; /* Резервные секторы */ unsigned char fats ; /* Номер FAT */ unsigned max_dir ; /* Номер корневого кат.*/ unsigned data_sec ; /*Первый кластер данных*/ unsigned hi_clust ; /* Кластеры + 2 */ union { struct { unsigned char fat_size ; /* Секторов в FAT */ unsigned root_sec ; /* Начало крневого кат.*/ void far *device ; /* Адрес заголовка устр.*/ unsigned char media ; /* Media описатель */ unsigned char access ; /* 0 если доступно */ struct _DI far *next ; /* Следующий инф. блок */ } dos3 ; struct { unsigned fat_size ;/* Секторов в FAT */ unsigned root_sec ;/* Начало крневого кат. */ void far *device ; /* Адрес заголовка устр.*/ unsigned char media ; /* Media описатель */ - 56 - unsigned char access ; /* 0 если доступно */ struct _DI far *next ; /* Следующий инф. блок */ } dos4 ; } dos_dependent ; } DISK_INFO ; typedef struct { char file_name[ 8 ] ; char file_ext[ 3 ] ; char file_attribute ; char __unused[ 10 ] ; unsigned time ; unsigned date ; unsigned cluster ; long file_size ; } DIRECTORY_ENTRY ; static DISK_INFO source_disk_info ; enum { FAT_16, FAT_12 } ; static int fat_type = FAT_16 ; static int near get_disk_info( char *name ) { unsigned char drive ; DISK_INFO far *p ; drive = toupper( *name ) - 'A' ; asm push ds asm mov dl, drive asm inc dl asm mov ah, 32h asm int 21h asm cmp al, 0 asm jne error asm mov dx, ds asm pop ds p = MK_FP( _DX, _BX ) ; source_disk_info = *p ; return 0 ; error: asm pop ds return -1 ; } static unsigned near get_fat( char huge *fat, unsigned clust ) { unsigned temp_clust ; unsigned x ; switch( fat_type ){ case FAT_12: temp_clust = ( clust * 3 ) / 2 ; x = *(unsigned huge *)( fat + temp_clust ) ; if( ( clust & 1 ) == 0 ) x &= 0xfff ; else x >>= 4 ; if( ( x & 0xfff ) > 0xff0 ) x |= 0xf000 ; break ; case FAT_16: x = *( (unsigned huge *)fat + clust ) ; - 57 - break ; } return x ; } static unsigned near search_start_cluster( char *name ) { char dir[ MAXDIR ] ; char drive[ MAXDRIVE ] ; char file[ MAXFILE ] ; char ext[ MAXEXT ] ; struct xfcb xfcb ; struct fcb *fcb = &xfcb.xfcb_fcb ; char dta[ sizeof( DIRECTORY_ENTRY ) + 9 ] ; char far *old_dta ; printf( "Looking for first cluster number of %s\n", name ) ; setdisk( toupper( *name ) - 'A' ) ; if( getdisk() != toupper( *name ) - 'A' ){ printf( "setdisk(%d) failure\n", toupper( *name ) - 'A' ) ; return 0xffff ; } fnsplit( name, drive, dir, file, ext ) ; if( strlen( dir ) > 1 ) dir[ strlen( dir ) - 1 ] = 0 ; if( chdir( dir ) == -1 ){ printf( "chdir(%s) failure\n", dir ) ; return 0xffff ; } old_dta = getdta() ; setdta( dta ) ; xfcb.xfcb_flag = 0xff ; setmem( xfcb.xfcb_resv, sizeof( xfcb.xfcb_resv ), 0xff ) ; xfcb.xfcb_attr = FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_ARCH ; fcb->fcb_drive = 0 ; setmem( fcb->fcb_name, 8 + 3, ' ' ) ; memcpy( fcb->fcb_name, file, strlen( file ) ) ; if( strlen( ext ) != 0 ) memcpy( fcb->fcb_ext, ext + 1, strlen( ext + 1 ) ) ; asm push ds asm mov dx, ss asm mov ds, dx asm lea dx, xfcb asm mov ah, 11h asm int 21h asm pop ds if( _AL != 0 ){ printf( "DOS fun 11h failure (%u)\n", _AL ) ; return 0xffff ; } setdta( old_dta ) ; return ((DIRECTORY_ENTRY *)( dta + 8 ))->cluster ; } /* * abs_read/abs_write section */ static int abs_error = 0 ; static int near abs_read( int drive, int start, int len, void far *buf ) - 58 - { asm push si asm push di asm push ds asm mov al, drive asm mov cx, len asm mov dx, start asm lds bx, dword ptr buf asm int 25h asm pop dx asm pop ds asm pop di asm pop si asm jnc no_error asm mov abs_error, ax return -1 ; no_error: return 0 ; } static char huge * near load_fat( void ) { char huge *fat ; char huge *p ; unsigned fat_size ; unsigned sectors_per_call ; unsigned fat_start ; if( _osmajor >= 4 ) fat_size = source_disk_info.dos_dependent.dos4.fat_size ; else fat_size = source_disk_info.dos_dependent.dos3.fat_size ; if( ( fat = (char huge *)farmalloc( source_disk_info.sect_siz * fat_size ) ) == NULL ) return NULL ; sectors_per_call = 0x8000u / source_disk_info.sect_siz ; for( p = fat, fat_start = source_disk_info.boot_siz ; fat_size > 0 ; fat_size -= min( sectors_per_call, fat_size ), p += sectors_per_call * source_disk_info.sect_siz, fat_start += sectors_per_call ) if( abs_read( source_disk_info.drv, fat_start, min(fat_size, sectors_per_call ), (void far *)p ) == -1 ){ farfree( (void far *)fat ) ; return NULL ; } return fat ; } static void near detect_fat_type( void ) { if( _osmajor <= 2 ) fat_type = FAT_12 ; else if( source_disk_info.hi_clust > 4086 ) fat_type = FAT_16 ; else fat_type = FAT_12 ; } static unsigned near collect_chain( char huge *fat, unsigned start_cluster ) { unsigned clusters ; - 59 - printf( "Clusters chain is :\n" ) ; for( clusters = 0 ; start_cluster < 0xfff0 ; clusters++ ){ start_cluster = get_fat( fat, start_cluster ) ; printf( "%5u ", start_cluster ) ; if( clusters % 10 == 9 ) putchar( '\n' ) ; } if( clusters % 10 != 0 ) putchar( '\n' ) ; return clusters ; } int main( int argc, char *argv[] ) { unsigned start_cluster ; char huge *fat ; /*Должен быть HUGE для DOS 4.x*/ unsigned clusters ; char directory[ MAXPATH + 1 ] ; int disk ; char *name ; if( argc != 2 ){ printf( "Type : Cluster full_file_name\n" ) ; return -1 ; } name = argv[ 1 ] ; disk = getdisk() ; getcurdir( disk + 1, directory + 1 ) ; directory[ 0 ] = '\\' ; if( directory[ 1 ] == '\\' ) directory[ 1 ] = 0 ; if( get_disk_info( name ) == -1 ){ printf( "Get disk info failure\n" ) ; chdir( directory ) ; setdisk( disk ) ; return -1 ; } if( ( start_cluster = search_start_cluster( name ) ) == 0xffff ){ printf( "Get start cluster failure\n" ) ; chdir( directory ) ; setdisk( disk ) ; return -1 ; } printf( "Starting cluster number is %u\n", start_cluster ) ; if( start_cluster == 0 ){ chdir( directory ) ; setdisk( disk ) ; return -1 ; } if( ( fat = load_fat() ) == NULL ){ printf( "Load FAT failure\n" ) ; chdir( directory ) ; setdisk( disk ) ; return -1 ; } detect_fat_type() ; clusters = collect_chain( fat, start_cluster ) ; printf( "Total number of clusters is %d\n", clusters ) ; farfree( (void far *)fat ) ; chdir( directory ) ; setdisk( disk ) ; return 0 ; } - 60 - 7. Приложение D. Доступ к хвосту файла Текст программы находится в файле tail.c. Хотя любой ANSI транслятор должен принимать ее, этот пример основывается на нео- писанном поведении функции seek() и может не работать с транслятора- ми, отличными от TC 2.0. #include #include #include #include #include #define GRANULARITY (512) #define ID_LENGTH 30 int main( int argc, char *argv[] ) { int handle ; long length ; char buf[ ID_LENGTH + 1 ] ; int size ; if( ( handle = open( argv[ 0 ], O_RDWR | O_BINARY ) ) == -1 ){ perror( argv[ 0 ] ) ; return -1 ; } length = filelength( handle ) ; if( GRANULARITY - length % GRANULARITY < ID_LENGTH ){ printf( "File %s has no sufficient tail !\n", argv[ 0 ] ) ; close( handle ) ; return -1 ; } if( argc == 2 ){ /* Write to tail */ lseek( handle, 0, SEEK_END ) ; size = min( strlen( argv[ 1 ] ) + 1, ID_LENGTH ) ; if( write( handle, argv[ 1 ], size ) != size ){ perror( argv[ 0 ] ) ; close( handle ) ; return -1 ; } } else { lseek( handle, ID_LENGTH, SEEK_END ) ; write( handle, " ", 1 ) ; lseek( handle, length, SEEK_SET ) ; read( handle, buf, ID_LENGTH ) ; printf( "File tail is \"%s\"\n", buf ) ; } chsize( handle, length ) ; close( handle ) ; return 0 ; } 8. Приложение E. Как отличать основные платы Текст программы находится в файле sysboard.c, требуются TC 2.0 и TASM для компиляции. Текущее значение параметра INSTRUCTIONS (16384) будет заставлять счетчик времен переполняться на частоте менее 20 МГц процессора 80386. static char __rights__[] = "Модуль идентификации основной платы"; - 61 - #pragma inline #include #include #include #include #define INSTRUCTIONS (16*1024u) void init_timer_channel( unsigned char channel ) { asm pushf asm cli asm mov al, channel asm ror al, 1 asm ror al, 1 asm or al, 36h asm out 43h, al asm mov dx, 40h asm add dl, channel asm jmp $+2 asm jmp $+2 asm xor al, al asm out dx, al asm jmp $+2 asm jmp $+2 asm out dx, al asm popf } unsigned read_sound_timer( void ) { /* * ! Эта функция не должна изменять регистры, кроме AX ! */ asm mov al, 80h asm out 43h, al asm jmp $+2 asm jmp $+2 asm in al, 42h asm jmp $+2 asm mov ah, al asm in al, 42h asm xchg ah, al asm neg ax return _AX ; } #define disable_count() (outportb(0x61,inportb(0x61)&(unsigned char)~1)) #define enable_count() (outportb(0x61,inportb(0x61)|(unsigned char)1)) void instruction_fill( char far *buf, unsigned cnt, char instr[ 2 ] ) { while( cnt-- > 0 ){ *buf++ = instr[ 0 ] ; *buf++ = instr[ 1 ] ; } *buf++ = 0xCB ; /* retf */ - 62 - } unsigned measure( char far *routine ) { unsigned time ; init_timer_channel( 2 ) ; disable_count() ; asm push cs asm lea ax, ret_point asm push ax asm les bx, routine asm push es asm push bx asm xor dx, dx asm mov bx, 1 asm cli enable_count() ; asm mov ax, bx asm retf asm ret_point label near disable_count() ; time = read_sound_timer() ; asm sti return time ; } int main( int argc, char *argv[] ) { char far *buf ; unsigned idle_time ; unsigned CPU_mark, mem_mark, DMA_mark ; if( ( buf = farmalloc( INSTRUCTIONS * 2 + 1 ) ) == NULL ){ perror( "No memory" ) ; return -1 ; } instruction_fill( buf, 0, NULL ) ; idle_time = measure( buf ) ; instruction_fill( buf, INSTRUCTIONS, "\xF7\xF3" ) ; CPU_mark = measure( buf ) - idle_time ; /* div bx */ instruction_fill( buf, INSTRUCTIONS, "\xC4\x07" ) ; mem_mark = measure( buf ) - idle_time ; /* les ax, [bx] */ instruction_fill( buf, INSTRUCTIONS, "\xE6\x0C" ) ; DMA_mark = measure( buf ) - idle_time ; /* out 0Ch, al */ printf( "Idle time = %u\n", idle_time ) ; printf( "CPU mark = %u\n", CPU_mark ) ; printf( "Memory mark = %u\n", mem_mark ) ; printf( "DMA mark = %u\n", DMA_mark ) ; return 0 ; }