Жанр: Учеба

Программно-аппаратная организация компьютера IBM PC

сторінка №12

ля этого следует ввести команду D, не указывая адреса
начала ячейки.
Рисунок 6.2. иллюстрирует ряд возможных особенностей, которые следует
иметь в виду, используя команду "D" в рамках программы DEBUG. Одна из этих
особенностей состоит в том, что в левую часть поля вывода помещается
шестнадцатеричная информация, а в правую - информация в коде ASCII. Вторая
особенность заключается в том, что кодовая комбинация не имеющая
символьного представления в коде ASCII изображается в правой части поля
вывода с помощью точки. Отсюда, в частности, вытекает важное следствие:
для того, чтобы использовать эхо-печать программа DEBUG контролирует
поступающие на ее вход кодовые комбинации, не имеющие представления в коде
ASCII; эти комбинации могут быть восприняты принтером в качестве
управляющего кода. Вследствие этого, нельзя расчитывать, что правая часть
поля вывода (информация в коде ASCII) будет давать объективную картину.
Все, что может быть приведено к стандартным печатным символам, или
является печатным символом (со старшим битом в единичном состоянии) будет
отпечатано, все остальное будет преобразовано в точки.(Здесь уместно
заметить, что программа DiskLook, входящая в состав пакета программ,
записанного на дискету, прилагаемую к настоящей книге, отображает в
точности все шестнадцатеричные коды и коды ASCII, поскольку выводит
информацию только на экран и не использует эхо-печать).
До сих пор мы рассматривали способы отображения данных, или поиска
априори известных данных, хранящихся в ПЗУ с помощью программы DEBUG. Ниже
мы будем решать существенно более сложную задачу - задачу
деассемблирования и интерпретации программного кода на машинном языке.

6.3. Анализ содержимого ПЗУ - метод деассемблирования

Наш следующий шаг в решении задачи исследования содержимого ПЗУ и
использования программы DEBUG состоит в знакомстве с особенностями
процесса деассемблирования. Команда "U" программы DEBUG - означает
"деассемблирование" - осуществляет преобразование произвольных кодов
памяти в мнемонические коды языка ассемблера. Деассемблер реализует:
перевод абсолютных шестнадцатеричных кодов программ, в коды команд на
языке ассемблера (например: AX или DS). Деассемблер не делает две вещи.
Первое. Деассемблер не интерпретирует смысл программы и не обучает
пользователя. Для понимания листинга, выдаваемого деассемблером,
необходимо либо знать язык ассемблера, либо иметь желание покопаться в
справочном руководстве и, установив смысл мнемонических обозначений,
постараться понять суть дела. (Для этой цели могут быть использованы
следующие источники: "Руководство по языку ассемблера фирмы IBM",
справочники по системе команд микропроцессора 8086/8088, такие как
"Учебное пособие по микропроцессору 8086" С.Морса или "Описание
микропроцессора 8086" Ректора и Алекси).
Для понимания программы на языке ассемблера (или программы,
приведенной к языку ассемблера), знания одного языка Ассемблер
недостаточно, необходимо также иметь некоторое представление о
функциональной ориентации программы.
Постижение программы представляет собой занимательное умственное
упражнение, своего рода головоломку. Со временем эта процедура становится
все проще и проще, отчасти потому, что времени на поиск смыслового
описания команды уходит все меньше и меньше, а отчасти потому, что
усваиваются приемы программирования на ассемблере и способы достижения
целей. Далее мы остановимся на этих вопросах более подробно.
Второе, что не под силу программе деассемблера - это установка так
называемой абсолютной синхронизации. Известно, что команды машинного языка
для микропроцессора INTEL 8086/8088 имеют переменную длину - от одного до
шести байтов. После того как деассемблеру сообщена конкретная позиция
памяти он приступает к процедуре прямого декодирования, не отличая кодов
команд от данных. Достаточно ошибиться в выборе исходной позиции памяти
(например, попасть не на границу между командами или в область данных) и
результат окажется неверным.
Если начальная точка набора команд известна, то никаких проблем не
возникает. Если какие-либо предварительные сведения отсутствуют, то
следует провести необходимое исследование. Первое, что необходимо сделать
- это выбрать исходную точку в наборе команд и запустить операцию
деассемблирования. После этого следует предпринять попытку
интерпретировать результат работы деассемблера. Если осмысленные
программные участки отсутствуют (ниже мы приводим некоторые соображения по
их выявлению),то следует повторить процедуру, сместив начальную точку на
один, два или три байта по отношению к предыдущей попытке. Мы таким
образом пытаемся отыскать точку синхронизации, расположенную на стыке двух
команд, после чего может быть восстановлен оригинальный набор команд. Если
очередная попытка закончилась неудачно, то следует попробовать еще раз.
Поскольку большинство команд имеют длину один или два байта, то точку
синхронизации отыскать как правило несложно. Кроме того, необходимо
учитывать, что процессу деассемблирования присущ элемент
самосинхронизации. Даже если в качестве начальной точки для
деассемблирования выбрана точка, расположенная внутри команды, то часто
случается, что процесс деассемблирования при обработке последуюющих команд
самопроизвольно попадает в нужную точку. После этого все идет гладко, по
крайней мере в сторону увеличения адресов. (В обратную сторону это
приходится делать вручную).

Выполняя деассемблирование программы необходимо внимательно следить
за данными (это нечто противоположное командам), которые деассемблер
превращает в причудливые ассемблерные конструкции. Основной и наиболее
быстрый способ обнаружения данных состоит в использовании команды "D", для
представления информации в шестнадцатеричном коде и коде ASCII. Здесь в
первую очередь следует обращать внимание на самые очевидные вещи -
содержательно осмысленную последовательность символов ASCII (фразы),
такую, например, как "неверный вызов функции". После этого можно
приступать к выявлению более тонких признаков данных.
В обычных программах (т.е. в программах, размещаемых вне ПЗУ) наличие
полей, заполненных шестнадцатеричными нулями является признаком рабочей
области данных, то есть такой области, которая будет использоваться в ходе
выполнения программы; в процессе деассемблирования данные этой области
неактуальны. ПЗУ не может использоваться для хранения рабочих данных,
поскольку запись в эту память невозможна. Таким образом, наличие полей,
состоящих из шестнадцатеричных нулей нетипично для ПЗУ-программ.
Признаком возможного наличия данных являются байты или двухбайтовые
слова, для которых второй или старший байт содержит 0 или F. Дело в том,
что значительное число констант программ являются малыми числами,
положительными (т.е. начинающихся с 0), либо отрицательными (т.е.
начинающихся с F). Если будет обнаружена последовательность байтов,
удовлетворяющая этим требованиям, то весьма вероятно, что найдена область
памяти, используемая для хранения одного или двухбайтовых чисел. Эта часть
программы не может содержать команды.
Пытаясь осмыслить структуру листинга, выдаваемого деассемблером,
необходимо иметь в виду следующие положения. Обычные программы, прошедшие
этап редактирования связей, будут хранить команды в одном месте, а данные
в другом. Такой подход является элементом хорошего стиля программирования;
именно так работает редактор связей дисковой операционной системы.
Описанный принцип характерен для программ всех типов, однако для программ
типа EXE он справедлив в большей степени чем для программ типа COM. С
другой стороны, ПЗУ-программы, подобные тем, которые рассматривались нами
выше, часто разрабатываются вне концепций технологии программирования.
Данные в этих программах могут следовать вперемешку с командами.
Если предположительно обнаружен участок программы, в котором вслед за
командами следуют некоторые данные, то существует по крайней мере один
достоверный способ отличить конец последовательности команд от
расположенных вслед за ними данными, и таким образом избежать
необходимости рассматривать недостоверный протокол деассемблирования.
Последняя команда фактического участка программы должна представлять собой
ту или иную разновидность команды перехода или ветвления. Команды
ветвления включают любые команды переходов, кроме команд условных
переходов. В качестве общеупотребительной команды завершения программы
принято использовать команду возврата (RET) (возврат из вызванной ранее
подпрограммы).
Могут использоваться также и команды CALL (Вызов) и INT (Прерывание);
хотя их использование в качестве команды завершения последовательности
команд встречается достаточно редко.
Существует ряд особенностей, на которые следует обращать внимание при
решении вопроса подлинности предъявленного участка программы после его
деассемблирования. В первую очередь следует обратить внимание на
используемые регистры. Если операции реализуются на регистрах,
используемых как правило для выполнения арифметических операций (AX или
AL, AH;BX или BL, BH; CX или CL, CH; DX или DL, DH), но никакие действия в
отношении результата не предпринимаются, то это вызывает подозрение. Здесь
следует быть более внимательным. Один из результатов выполнения
арифметических операций состоит в установке флагов - следовательно,
естественно ожидать наличие команд условных переходов по состоянию флага,
таких как JNC (команда перехода по условию отсутствия установленных битов
флага).
Рассмотрим аспект некорректности использования регистров в
реконструируемой программе. Программы не используют регистры некоторым
хаотичным образом - им присуща определенная дисциплина. Поэтому весьма
непривычно, скажем, видеть загрузку или непосредственную ссылку на
сегментные регистры - особенно сегментный регистр программы (CS) и
сегментный регистр стека памяти (SS). Загрузка сегмента регистра данных
(DS) более вероятна, но также встречается редко. С другой стороны,
дополнительный сегментный регистр (ES) практически постоянно используется
программами, поэтому его загрузка - дело обычное. Если загрузка регистра
CS или SS все-таки обнаружена, то это должно быть сделано в программной
секции, формирующей операционную структуру выполняемой программы; в этом
случае вероятнее всего, что в одном месте будет произведена загрузка
нескольких сегментных регистров, в частности, CS, SS и DS.
Попытка реконструировать программу по листингу, выданному
деассемблером или даже просто попытка убедиться в подлинности
предъявленного набора команд, представляет собой захватывающее умственное
упражнение. Для этого,кстати, не требуется специальная подготовка. Как
правило, достаточно бывает просто здравого смысла. Усвоив все наши
рекомендации и приобретя некоторые практические навыки, Вы, при
необходимости, сможете все это проделать. В следующем параграфе мы
выполним декодирование (реконструкцию) программы Бейсик-ПЗУ; читатель,
таким образом, получит представление о том, как это делается и убедится в
том, что это не очень сложно.


6.4. Анализ содержимого ПЗУ - реконструкция интерпретатора

В качестве примера попробуем деассемблировать фрагмент Бейсик-ПЗУ.
Начнем с исходного параграфа, т.е. параграфа с адресом F600(16). На
рисунке 6.3 представлен результат деассемблирования первых 32 байтов.
Шестнадцатеричные нули в конце фрагмента программы (преобразованы
деассемблером в команды ADD (Сложить)) представляют собой данные. Они
"хорошо" иллюстрируют высказанное ранее положение о том, что нулевые
данные нетипичны для ПЗУ-программ. Команда DB (определить байт),
предшествующая полю нулей, появилась как реакция деассемблера на кодовую
комбинацию, которая не может быть преобразована в команду ассемблера
(такая ситуация, благодаря широкому набору команд, встречается довольно
редко). Итак, являются ли все данные, которым предшествует команда
безусловного перехода собственно осмысленными командами? Предварительный
ответ на этот вопрос может быть положительным, поскольку для завершения
выполнения подпрограммы используется команда возврата (RET). Пока все идет
хорошо.
Можно ли приписать какие-то осмысленные действия первым пяти командам
деассемблированной программы? Эти команды выглядят несколько странно,
поскольку начинаются с команды безусловного перехода (JMP). Большинство
программ, однако, первым делом выполняют ряд действий настроечного плана,
обращаясь для этого посредством команд вызова подпрограмм или переходов, к
нужным процедурам. Таким образом, безусловный переход в начале программы -
каким бы причудливым он не казался - не такая уж бессмыслица. Следующие
четыре команды - две пары обращения к подпрограммам с последующими
командами возврата в процедуру не особенно понятны, но, по крайней мере,
не противоречат друг другу и могут в какой-то степени служить признаком
наличия последовательности команд. Можно, следовательно, сделать
предположение о том, что мы имеем дело с реальной последовательностью
команд.
Из того, чем мы располагаем - даже при том, что на рисунке 6.3
представлена реальная последовательность команд - вряд ли можно извлечь
какую-то пользу. Только пять команд почти ничего не совершающих.
И все-таки есть ряд интересных моментов. Если это действительно
реальная последовательность, то нам названы три адреса, где могут
оказаться вещи более интересные. Речь идет об адресе в команде перехода и
адресах двух последующих команд обращения к процедуре. Следует ли нам
пойти по адресу перехода? Деассемблер дает нам этот адрес (относительно
начала нашей программы, т.е. адреса F600/16) - FE92/16.
Выполним деассемблирование небольшого участка памяти, начинающегося с
этого адреса (результат приведен на рис 6.4.)

-U F600:0000
F600:0000 E98F7E JMP 7E92
F600:0003 E8A76B CALL 6BAD
F600:0006 CB RET L
F600:0007 E80265 CALL 650C
F600:000A CB RET L
F600:000B C1 DB C1
F600:000C 0000 ADD [BX+SI],AL
F600:000E 0000 ADD [BX+SI],AL
F600:0010 0000 ADD [BX+SI],AL
F600:0012 0000 ADD [BX+SI],AL
F600:0014 0000 ADD [BX+SI],AL
F600:0016 0000 ADD [BX+SI],AL
F600:0018 0000 ADD [BX+SI],AL
F600:001A 0000 ADD [BX+SI],AL
F600:001C 0000 ADD [BX+SI],AL
F600:001E 0000 ADD [BX+SI],AL

Рис. 6.3. Деассемблирование начального участка Бейсика

Похоже, мы попали в точку. Во-первых, перед нами совокупность
осмысленных команд, не содержащая ни последовательностей операторов DB, ни
последовательностей операторов ADD.Во-вторых, эти команды весьма
напоминают команды настройки.Об этом говорит прежде всего команда CLI,
(CLI (Clear interrupt flag) - очистка флага прерывания).
Прерывание в микропроцессоре INTEL 8086/8088 воспринимается по
завершении выполнения некоторой команды. Немаскируемое прерывание в общем
случае обслуживается непосредственно после выполнения текущей команды.
Прерывание по вектору воспринимается только тогда, когда разряд IF (флаг
прерывания) в регистре FLAGS имеет значение, равное 1. Для того, чтобы
микропроцессор 8086 воспринял прерывание, необходимо выполнение следующих
трех условий:
1) разряд IF регистра FLAGS должен иметь значение, равное 1 (для
прерываний по вектору);
2) разряд разрешения прерываний в интерфейсе устройства должен
находиться в состоянии "Прерывание разрешено";
3) интерфейс должен зафиксировать некоторое событие, вызывающее
прерывание (например, поступление символа с клавиатуры, готовность к
выводу символа на дисплей, завершение передачи блока данных с диска и
т.п.) (Прим.перев.)).

которая, как мы видели в главе 3 используется как средство блокировки
прерываний; таким образом вся последовательность выполнения расположенных
ниже команды прервана быть не может. Именно такая последовательность
команд характерна для начальных аргументов ответственных программ,
поскольку значения сегментных регистров целесообразно устанавливать
одновременно с обработкой прерываний.
Ниже производятся как раз те действия, о которых мы говорили выше, а
именно: загрузка сегментных регистров. С помощью четырех команд MOV
(команды пересылки данных) осуществляется загрузка трех сегментных
регистров из четырех: DS, ES и SS. Загрузка этих регистров осуществляется
весьма редко, поэтому естественно это сделать один раз в начале программы.
(Сегментный регистр программы CS к этому моменту уже загружен ( F600 :
0000, рис. 6.3.).
Вслед за группой, состоящей из четырех команд MOV, осуществляющих
загрузку сегментных регистров, следует две команды, выполняющие одну
логическую операцию. Первая команда - это команда "исключающего ИЛИ" XOR -
заносит в регистр AL значение "нуль" (поскольку в результате применения
"исключающего ИЛИ" к эквивалентным данным образуется новая величина), а
вторая команда заносит эту величину по конкретному адресу.
Программа не содержит никаких сведений относительно содержательной
интерпретации этих операций, но это вполне осмысленное сочетание команд,
выполняющих конкретную функцию.
Если бы мы располагали более полной информацией об особенностях
работы Бейсика, то нам, вероятно, было бы известно, что нулевая величина,
пересылаемая в память с помощью двух двух последовательных команд
представляет собой переключатель предохраняющий защищенные программы. При
загрузке защищенных программ, написанных на языке Бейсик, этот
переключатель устанавливается с целью предотвращения выдачи их на печать.
Это, однако, никак не следует из представленного листинга.
Первые семь команд рассматриваемого участка программы, таким образом,
выглядят вполне осмысленно. Они реализуют рациональные и согласованные
между собой действия и естественным образом распадаются на два класса:
команды со 2-й по 5-ю реализуют одну логическую операцию, а с 6-й по 7-ю -
другую. Предьявленные команды отвечают всем критериям реального участка
программы.

-U F600:7E92
F600:7E93 8A6000 CLI
F600:7E93 8A6000 MOV DX,0060
F600:7E96 8EDA MOV DS,DX
F600:7E98 8EC2 MOV ES,DX
F600:7E9A 8ED2 MOV SS,DX
F600:7E9C 32CO XOR AL,AL
F600:7E9E A26404 MOV [0464],AL
F600:7EA1 B591 MOV CH,91
F600:7EA3 BB0000 MOV BX,0000
F600:7EA6 BA9A06 MOV DX,069A
F600:7EA9 BBF2 MOV SI,DX
F600:7EAB 2E SEG CG
F600:7EAC AC LODSB
F600:7EAD 8807 MOV [BX],AL
F600:7EAF 43 INC BX
F600:7EBO 42 INC DX
F600:7EB1 FECD DEC CH

Рис. 6.4. Второй фрагмент реконструированного Бейсика

Мы в какой-то мере достигли относительной цели: поиск и выявление
некоторого программного кода, а также некоторое его осмысление. Это нам
вполне удалось. Можно было бы выполнить декодирование всего Бейсика, либо
наиболее существенных его участков. Однако мы уже получили представление о
том, как это удается и убедились, что это вполне возможно.

6.5. Две версии BIOS'а

То, что для IBM/PC используется по крайней мере две различные версии
BIOS не является широко известным фактом. В настоящем параграфе мы кратко
рассмотрим имеющиеся между нами различия и оценим их применительно к своим
нуждам.
В состав пакета программ, записанного на дискету и прилагаемого к
настоящей книге, входят две программы копирующие и анализирующие программы
BIOS.
Именно с помощью этих программ впервые было обнаружено наличие
модифицированных версий ПЗУ компьютеров, факт,который открыто не
признавался фирмой IBM. С помощью этих программ можно проверить свою
систему и сопоставить свою версию с другими.

Из всех программ, используемых для Вашего компьютера IBM/PC, наиболее
совершенными (не содержащими ошибок) должны быть программы,
предназначенные для размещения в ПЗУ. Если, например, будет выявлена
ошибка в какой-либо версии DOS или VisiCalc, то ее можно устранить путем
простой перезаписи дискеты. С программами, записанными в ПЗУ, дело обстоит
гораздо сложнее. Их можно записывать только путем замены корпусов
микросхем памяти, расположенных на главной плате компьютера IBM/PC; эта
операция не может быть произведена силами обычного пользователя. Наличие
ошибок в программах ПЗУ представляет собой серьезную проблему для фирмы
IBM.
Сама мысль о том, что можно сделать что-либо стоящее, не совершив при
этом ни единой ошибки, представляется фантастической и программы ПЗУ в
этом смысле не исключение. С момента появления компьютеров IBM/PC, фирма
IBM сочла необходимым внести ряд изменений в программы ПЗУ (речь идет о
серьезных ошибках), что обусловило появление двух версий ПЗУ. У
пользователя может возникнуть вопрос, какой версией оснащен его компьютер
и чем эта версия отличается от другой версии.
В самом конце программы BIOS фирма IBM поместила временную отметку,
так называемый маркер версии. Этот маркер практически соответствует дате
официального завершения разработки программ ПЗУ. Для его отображения на
экране можно воспользоваться программой DEBUG. После загрузки программы
DEBUG введите команду D F000:FFF5 L 8.
На экране вашего компьютера будет отображена последовательность
символов, обозначающая маркер версии: 04/24/81. Маркер второй версии имеет
вид 10/19/81. Если будет обнаружен маркер версии, отличной от этих двух,
это будет означать, что Вы располагаете версией BIOS-ПЗУ, отличной от
указанных выше.
В приложении 6.1 приведен текст программы, проверяющей маркер версии
BIOS-ПЗУ, и, в зависимости от его значения, выполняющей те или иные
действия. Аналогичные действия на Бейсике можно представить следующим
образом:

10 REM вывод на дисплей маркера версии ПЗУ
20 REM программа отображает маркер, не анализируя его
30 REM и не настраивается на версию ПЗУ
40 PRINT
50 DEF SEG=&HFFFF
60 PRINT "Маркер версии ПЗУ;
70 FOR OFFSET=5 то 12
80 PRINT CHR$(PEEK(OFFSET));
90 NEXT OFFSET

Если Ваш компьютер приобретен позже октября 1981, то это еще не
означает, что он снабжен последней версией BIOS-ПЗУ. Проверка компьютеров,
выпущенных через год после выпуска второй версии BIOS, показывает, что
новый, только что собранный компьютер, может иметь первую версию BIOS-ПЗУ.
Вы, вероятно, помните, что ПЗУ IBM/PC содержит две составляющие:
Бейсик-ПЗУ и BIOS-ПЗУ. Первое, что было обнаружено с помощью разработанной
автором программы, анализа содержимого ПЗУ, это отсутствие различий между
первой и второй версиями Бейсик-ПЗУ.
Все ошибки, обнаруженные в Бейсике, были устранены только за счет
дисковых версий Бейсика. Важно, чтобы Бейсик-ПЗУ оставался неизменным,
поскольку это может отразиться на особенностях выполнения широко
распространенных программ, написанных на Бейсике, и никем не
поддерживаемых. Возможна ситуация, когда одни и те же Бейсик-программы
будут по разному работать на различных машинах только из-за различия в
версиях ПЗУ.
Придерживаясь мнения на корректировку исправления Бейсик-программ
дискетной версии, фирма IBM обеспечила пользователю возможность
отслеживать все изменения, вносимые в язык. Если какая-нибуть программа
написана в расчете на оригинальный Бейсик, то следует лишь установить
дискету с этой версией Бейсика. Для новых версий Бейсика программа может
имитировать модификацию содержимого ПЗУ с помощью методики, описанной в
главе 3 при осуждении прерываний.
Все изменения, внесенные в ПЗУ, касались BIOS; ниже мы перечислим их
и Вы сможете оценить степень их важности. Если вы располагаете первой
редакцией "Руководства по техническому обслуживанию IBM/PC", то можете
убедиться в том, что текст BIOS-ПЗУ, приведенный в приложении А,
соответствует первой версии ПЗУ, о чем свидетельствует также маркер версии
на последней странице: 04/24/81. Вслед за текстом BIOS-ПЗУ приведены
некоторые замечания. Первое, третье и пятое замечания касаются некоторых
ошибок, найденных в BIOS и исправленных во второй его версии.
Первые три изменения, внесенные в BIOS-ПЗУ, касаются программ
самотестирования, автоматически запускающихся после включения питания на
IBM/PC. Эти программы выполняют четырнадцать различных тестовых проверок
правильности работы оборудования, прежде чем пользователь натворит "дел"
на неисправном компьютере.

Тест номер три ( входящий в состав этих программ) осуществляет
проверку правильности работы функции таймера контроллера пам

Список сторінок

Поділитися у соцмережах

☏ Замовлення реклами: +3805044-68-872

© Асоціація електронних бібліотек України

☝ Усі матеріали сайту (зокрема статті, зображення, рекламні оголошення тощо) призначені лише для попереднього ознайомлення. Усі права на публікації, представлені на сайті, належать їхнім законним власникам. Просимо Вас не зберігати копії інформації.