История развития программного обеспечения

Дизассемблер, управляемый данными

При утрате исходного текста программы, при адаптации для новых типов микропроцессоров, ОЭВМ и сопроцессоров системного и инструментального, реже прикладного, программного обеспечения (ПО) и в ряде других случаев используются инструментальные средства следующих видов: отладчики, дисковые редакторы [1], редакторы объектных файлов, редакторы сообщений, программы «разваливания» библиотек на модули, интегрированные инструментальные средства [2], дизассемблеры (реассемблеры, деассемблеры или обратные ассемблеры). Единая терминология из-за немногочисленности публикаций устанавливается медленно, однако опрос разработчиков, показал, что общепринятым является термин дизассемблер. Это инструмент, позволяющий восстановить из объектного (машинного) кода исходный текст программы, т. е. перевести её в другое качественное состояние. Некоторые программисты рассматривают дизассемблеры только как инструмент для взлома чужих программ, поэтому проблемы, связанные с их разработкой, долго обходились молчанием. Автор считает, что, с одной стороны, вопросы этики относятся к уровню мира программистов, а не к инструментарию, а с другой, – у дизассемблеров имеются вполне благопристойные применения. Восстановленный исход ный текст нельзя приравнять к настоящему исходному тексту программы, так как, в общем случае, оказываются утерянными исходные идентификаторы (метки), комментарии, а также, возможно, макросы, псевдокоманды и директивы трансляции.

Если программа разрабатывалась на Макроассемблере и разработчиком не предпринимались специальные меры против дизассемблирования, то восстанавливаемый текст семантически адекватен исходной программе. При использовании языка высокого уровня (Си, ПЛ/М, Паскаль) восстановленная программа не адекватна исход ной и её читаемость тем хуже, чем выше уровень исходного языка программирования. Су ществуют достаточно успешные попытки создания прог рамм, восстанавливающих из объектного кода текст на языке высокого уровня (ЯВУ). В основе таких программ (1-я фаза) лежит высококачественное дизассемблирование, а затем (2-я фаза) выделение конструкций ЯВУ.

Основная проблема дизассемблирования программы — качественное разделение команд и данных (в статье рассматриваются дизассемблеры только для машин с фон-неймановскойархитектурой [3]. В ЭВМ с другими архитектурами этот процесс может иметь существенные отличия или вообще невозможен). При смешении в объектном коде команд и данных, данные воспринимаются дизассемблером как команды, что приводит к появлению в листинге дизассемблирования (называемого также псевдоассемблерным листингом) бессмысленных и искаженных участков текста, что часто позволяет визуально выделять такие участки. Длина сбойного участка тем больше, чем больше средняя длина команды у данного МП.

В компиляторах с ЯВУ области команд и данных разграничены, однако константы очень часто включаются непосредственно в кодовый сегмент.

По числу просмотров объектного кода дизассемблеры (по аналогии с трансляторами) делятся на одно-, двух- и многопроходные. Однопроходные, называемые также «дизассемблерами без меток», используются в основном в отладчиках и программных мониторах (команда L ) и разрабатываются, если необходимо быстро получить псевдоассемблерный текст. Второе название отражает их существенную особенность — в псевдоассемблерном листинге отсутствуют метки в командах переходов и вызовах подпрограмм (стоят абсолютные шестнадцатеричные адреса). Поэтому после ассемблирования такого восстановленного текста мы получаем в общем случае абсолютную программу. За один проход (просмотр объектного кода от начала до конца) при ограниченном объеме ОЗУ нельзя собрать всю информацию о метках, так как теряются «ссылки назад».

Метки, создаваемые дизассемблерами, обычно имеют вид Lxxxx , где — первая буква английского слова Label (метка), a xxxx — адрес перехода или подпрограммы. Метки данных имеют ту же структуру, но начинаются с буквы D . Такой вид меток наиболее удобен при разборе псевдолистинга. Программист с помощью редактора текстов может заменить эти метки на более информативные.

Наиболее распространены двухпроходные дизассемблеры, которые позволяют получать метки в листинге дизассемблирования, но, как правило, не решающие полностью проблемы разделения команд и данных. После них программист «вручную» подправляет сомнительные места.

Проблему разделения решают интерактивные и многопроходные дизассемблеры.

Интерактивный дизассемблер не разделяет команды и данные сам – это делает программист, пользуясь командами просмотра объектного кода (дамп и однопроходное дизассемблирование). При этом выявляются участки данных и определяется тип данных. Информация фиксируется в таблицах дизассемблера, учитывается им при последующих дизассемблированиях и в итоге получается достаточно качественный восстановленный исходный текст. Интерактивный дизассемблер позволяет программисту вводить свои имена меток и комментарии, поэтому разбор текста программы совмещается с процессом дизассемблирования. Большие трудности появляются при попытке отличить друг от друга числа с плавающей точкой одинарной и двойной точности, визуально хорошо выявляются только символьные данные. Однако если программа выводит сообщения на 7-сегментные индикаторы (или другие устройства со специальным кодированием символов), то для выявления сообщений необходима специальная программа символьного дампа.

Многопроходные дизассемблеры, используя точку входа в программу, строят таблицы её ветвей с помощью однопроходного дизассемблирования (запоминаются адреса из команд переходов и вызовов подпрограмм). Ветвь программы заканчивается командой: безусловного перехода, безусловного вызова подпрограммы, возврата из подпрограммы или перехода по адресу в регистре, так как после этих команд могут быть помещены данные. Пользуясь накопленной информацией, дизассемблер определяет границы (адрес начала и адрес конца) следующей ветви программы. Код, не попавший в эти границы, рассматривается как данные. Однако это могут быть, например, подпрограммы, обращение к которым осуществляется с помощью таблиц переходов. Поэтому процесс построения границ ветвей программы может быть значительно ускорен за счёт интерактивного «консультирования» дизассемблера со стороны программиста.

Интересная особенность этого процесса — возможность выявления неиспользуемых участков программы, что позволяет создавать средства для оптимизации её объема; это особенно важно, если программа затем записывается в ППЗУ/ПЗУ. Например, известно, что при компоновке программы, написанной на языке высокого уровня, к ней добавляется значительный объем неиспользуемых библиотечных модулей, особенно для языков типа Фортран.

Второй по значимости, но более глубокой является проблема, разрешения противоречия между статической природой кода программы и её динамическим выполнением. Решение этой проблемы трудно автоматизировать. Примером являются самомодифицирующиеся программы.

На входе дизассемблера может быть файл с объектным кодом программы в виде: загрузочного модуля, «образа памяти», НЕХ-файла, объектного модуля, библиотеки объектных модулей. Часто входные данные передаются ему в некоторой области памяти, например при необходимости посмотреть содержимое микросхемы ППЗУ, объёмом 2, 4, 8, 16 или 32 Кбайт. На инструментальной микроЭВМ, имеющей программатор, можно производить дизассемблирование программ с других микроЭВМ и различных микропроцессорных контроллеров. Последнее часто приходится делать при ремонте различного импортного оборудования и отсутствии документации на него.

Практика показала, что для этих целей достаточно иметь однопроходный дизассемблер, но его необходимо разработать за 3—4 ч. Для этого автор предлагает следующую методику.

Как правило, в системе команд большинства МП в первом байте команды находится код операции. Таким образом, возможно кодирование до 256 различных кодов операций. В реальных МП набор команд значительно меньше, но для уменьшения длины команды (соответственно увеличения быстродействия МП) в её первом байте вместе с кодом операции кодируются регистры и признаки. Если бы все команды МП были однобайтовыми, то мнемонику кода операции (команды) и её операнды можно было бы занести в одну таблицу, расположив их по возрастанию значений соответствующих машинных кодов, и, индексируя эту таблицу содержимым очередного байта машинного кода дизассемблируемой программы, извлекать из таблицы почти готовый элемент получаемого псевдоассемблерного листинга. Однако в системе команд реальных МП имеются команды длиной в два и более байта (что зависит от архитектуры МП), кроме того, команды могут иметь поля с изменяемой информацией: адреса, непосредственные данные, признаки режимов адресации и т. п. Поэтому в описанную выше таблицу для таких команд заносятся специальные символы, позволяющие дизассемблеру определить, что нужно сделать со следующими байтами команды. Например, для МП КР580ИК80А ( Intel 8080) таких символов нужно два: “#” – следующий байт команды является байтом непосредственных данных (строка в таблице для команды MVI А, <байт> выглядит так: MVI А, # ); “@” – следующие два байта команды содержат адрес перехода или слово непосредственных данных (строка в таблице для команды безусловного перехода: JMP @ ).

Такие записи в таблице назовем шаблонами, а эти специальные символы – ключами шаблона. При разборе очередной команды дизассемблер по наличию ключей в шаблоне вызывает соответствующую подпрограмму. Набор ключей шаблона при необходимости может быть легко расширен. В дизассемблере для сопроцессора ввода-вывода 8089 используется, например, около 10 различных ключей шаблона.

Информация о длине команды неявным образом содержится в шаблоне. Несуществующие коды операций в псевдоассемблерном листинге отображаются байтовыми константами – DB 0 XXH ( XX — содержимое текущего байта дизассемблируемой программы). Работа по перенастройке дизассемблера на систему команд другого МП сводится к анализу его системы команд, подготовке и замене таблицы шаблонов, добавлению (или удалению) подпрограмм для работы с новыми (старыми) ключами шаблона.

Для облегчения ввода шаблонов с помощью редактора текстов набираются пары «код команды — шаблон» в произвольном порядке. После ввода таблицы дизассемблер производит их сортировку по возрастанию кодов. Управление данными заключается в том, что в любой момент времени порядок выполнения операций определяется текущим состоянием обрабатываемых данных.

Дизассемблер, управляемый данными, реализован на языках программирования БЕЙСИК-80/БЕЙСИК-86 и может работать в операционных системах СР/М-80 ISIS - II , МикроДОС, ДОС 1810, MS - DOS , CP / M -86 и совместимых с ними (мобилен на уровне переноса текста [4]).

Указанный подход оправдал себя при разработке дизассемблеров для 8-разрядных микроЭВМ на базе МП КР580ИК80А, Z -80, F -8, MC 6800, семейства однокристальных микроЭВМ К1816ВЕ48, КР1816ВЕ51 и co процессора ввода-вывода 8089. Так, например, настройка дизассемблера на систему команд МП F -8 (при нулевом знакомстве с ней до начала работы) заняла 6 ч до получения работающей программы.

Структуры данных воспринимаются программистом лучше, чем структуры управления [5], поэтому отладка сводится к проверке корректности шаблонов.

Выделение с помощью ключа шаблона “@” адресов в командах переходов и создание таблицы адресов переходов позволяют построить однопроходный дизассемблер со ссылками вперед и двухпроходный, создающий псевдолистинг с метками.

Наличие меток не только облегчает разбор программы, но и позволяет превратить её после некоторого редактирования из абсолютной в перемещаемую. На первом проходе в таблице меток собирается информация о них. В промежуточном файле метки ( Lxxxx : ) ставятся перед каждой командой, а перед вторым проходом таблица меток сортируется по возрастанию адресной ча сти метки. Промежуточный файл считывается и лишние метки удаляются; этим упрощается реализация второго прохода дизассемблера и диагностируются ситуации, при которых метка указывает не на начало команды (проблема разделения команд и данных).

Для выделения текстов в объектном коде в области комментариев листинга дизассемблирования традиционно выдается символьное представление объектного кода. Это позволяет визуально выделять связанные тексты.

Описываемые дизассемблеры использовались в основном для восстановления управляющих программ и драйверов, записанных в ППЗУ и считываемых с помощью программатора в НЕХ-файл [6]. Поэтому не было необходимости в построении интерактивных дизассемблеров, однако метод это позволяет.

Следует отметить, что в составе документации многих ОС отсутствует документ, описывающий форматы загрузочного модуля, абсолютного и перемещаемого объектных модулей и внутренний формат библиотеки объектных модулей, знание которых облегчило бы не только создание дизассемблеров, но и ряда других инструментальных средств: отладчиков, программ дампа, контроля и редактирования объектных модулей, средств ведения проектов, оптимизаторов программ, компиляторов, программ обслуживания программаторов и эмуляторов, специальных загрузчиков и др. Разумно было бы ввести в документ «Руководство системного программиста» обязательный раздел «Структура объектного файла».

К сожалению, автору не удалось найти общую систему ключей шаблонов и вместо набора дизассемблеров; настроенных на конкретные МП, построить единую программу с подгружаемой таблицей шаблонов.

Симметричность таблицы шаблонов относительно входа и выхода позволяет применять указанный метод для быстрого построения конверторов из одной системы команд в другую (например, из КР1816ВЕ48 в КР1816ВЕ51, т. е. 8048 в 8051), однопроходных ассемблеров и ассемблеров-дизассемблеров.

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

Литература

  1. Системные программы МикроДОС. – М.: МЦНТИ, 1986.
  2. IS I S-II Software Toolbox.– Intel Corp. – USA. 1980. – № 121727.
  3. Mайерс Г. Архитектура современных ЭВМ. – М.: Мир, 1985. Т. 1.
  4. Машинно-независимые операционные системы. – М.: МЦНТИ, 1987.
  5. Кертис Б., Солоуэй Э. М. Брукс Р. Е. и др. Психология программных систем. ТИИЭР. 1986. Т. 74. – № 8. – С 42—60.
  6. Микропроцессоры: системы программирования и отладки / Под ред. В. А. Мясникова, М. Б. Игнатьева. – М.: Энергоатомиздат, 1985.

Статья опубликована в журнале «Микропроцессорные средства и системы» №5, 1988. с. 33—35.
Перепечатывается с разрешения автора
Статья помещена в музей 15.08.2006 года