Как называется программа отладчик ассемблера
Перейти к содержимому

Как называется программа отладчик ассемблера

  • автор:

Turbo Assembler

Часто бывает так, что программа, успешно пройдя этапы трансляции и компоновки, все же работает не так, как ожидалось или вообще не работает. Это значит, что формально, с точки зрения правил языка программирования, программа написана правильно (в ней нет синтаксических ошибок), однако алгоритм ее в чем-то неверен. Для отладки такой программы следует воспользоваться услугами интерактивного отладчика. Интерактивным он называется потому, что вся работа с ним осуществляется в непрерывном диалоге с пользователем.

Познакомимся с отладчиком TD.EXE из пакета TASM, воспользовавшись программой из первого примера.

Как уже отмечалось, для полного использования возможностей отладчика следует при трансляции программы указать в числе других ключ /zi, а при компоновке — ключ /v:

tasm /z/zi/n p,p,p tlink /v p,p

Кроме того, следует убедиться, что в вашем рабочем каталоге имеется и загрузочный (Р.ЕХЕ) и исходный (P.ASM) файл, поскольку отладчик в своей работе использует оба этих файла. Вызовем отладчик командой

td p

На экране появится кадр отладчика, в котором видны два окна — окно Module с исходным текстом отлаживаемой программы и окно Watches для наблюдения за ходом изменения заданных переменных в процессе выполнения программы (см.рис.).

Окно Watches нам не понадобится, и его можно убрать, щелкнув мышью по маленькому квадратику в левом верхнем углу окна или введя команду Alt+F3, предварительно сделав это окно активным. Переключение (по кругу) между окнами осуществляется клавишей F6.

Верхняя строка кадра отладчика представляет собой главное меню. Для перехода в меню необходимо нажать клавишу Alt и первую (выделенную цветом) букву требуемого пункта. Для выбора затем конкретного действия надо с помощью клавиш со стрелками вверх и вниз- выделить нужный пункт и нажать клавишу Enter.

В нижней строке отладчика приведены его основные команды, вызов которых осуществляется нажатием на функциональные клавиши F1. F10. В действительности команд гораздо больше; некоторые из них можно реализовать только с помощью пунктов главного меню, другие вызываются комбинациями функциональных и управляющих (Alt, Ctrl или Shift) клавиш.

Рассмотрим типичные действия, к которым приходится прибегать по ходу отладки программы.

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

Нажав одну из клавиш F8 или F7, мы выполним одно предложение программы. Различие этих команд весьма существенно: команда F7 (trace, трассировка) позволяет войти внутрь вызываемых подпрограмм, а также выполнять циклы шаг за шагом. Команда F8 (step, шаг), наоборот, выполняет подпрограммы и циклы как одно неразрывное действие, что заметно ускоряет пошаговую отладку программы, если мы, например, уверены, что вызываемая подпрограмма выполняется правильно.

Можно выполнить сразу и целый фрагмент программы, т. е. несколько предложений. Для этого надо поместить курсор _ перед тем предложением, на котором требуется сделать остановку (или на любой символ внутри него), и нажать клавишу F4 (here, сюда). Выполнятся все строки программы до той, на которой установлен курсор; значок сплошной треугольник переместится на эту строку. Далее можно опять выполнять программу построчно, нажимая на клавишу F8, или, установив в требуемом месте курсор, выполнить следующий фрагмент, нажав F4.

Для повторного выполнения программы ее следует «рестартовать». Для этого надо выбрать в главном меню пункт Run-Program reset или просто нажать Ctrl+F2.

Важнейшим элементом отладки программы является наблюдение значений тех или иных полей данных, особенно тех, которые заполняются программой динамически, т. е. по ходу ее выполнения. Для того чтобы вывести на экран содержимое поля данных, надо поместить курсор на имя этого поля (например, mesg в нашем примере) и выбрать пункт меню Data-Inspect. В появившемся окне ввода переменной (см.рис.) можно скорректировать имя интересующего нас поля данных или ввести новое; если имя правильное, достаточно нажать клавишу Enter.

В кадр отладчика будет выведено окно с характеристиками и содержимым указанной переменной (см.рис.). Отладчик сообщает, что переменная mesg хранится в памяти по адресу 5D82:000, т. е. имеет сегментный адрес 5D82h и смещение OOOOh, и описана как последовательность из 12 байт. Тут же приводятся значения всех байтов нашей строки, включая их начертание на экране, а также десятичное и 16-ричное представление.

В окне Inspecting можно изменить значение отображаемого поля данных. Для этого надо, сделав это окно активным и поместив курсор на отображение конкретного элемента нашего символьного массива, например элемента с индексом 12 (знак «!»), ввести команду Alt+F10. Эта команда для любого активного окна открывает его внутреннее меню с дополнительными возможностями. В данном случае внутреннее меню будет иметь вид, показанный на нижнем рисунке.

Нас будет интересовать пункт Change (изменение). Выбрав этот пункт, мы получим окно, в котором можно ввести требуемое значение изменяемого данного. На следующем рисунке показано это окно с введенным символом ‘>’, которым будет заменен восклицательный знак. Можно было вместо символа в одинарных кавычках ввести его 16-ричный код ASCII, если он известен (число ЗЕ для знака >). Допустим ввод и десятичного кода, если завершить его буквой d F2d).

Если теперь, не выходя из отладчика, выполнить программу до конца, на экран вместо ! будет выведен >.

Для того чтобы, находясь в отладчике, увидеть результат работы программы, надо ввести команду Alt+F5 (или выбрать пункт Window-User screen главного меню). Возврат в кадр отладчика осуществляется нажатием любой клавиши.

Необходимо отдавать себе отчет в том, что любые изменения, вносимые в текст программы в процессе работы с отладчиком, будут действовать только до конца данного сеанса (даже, точнее говоря, до рестарта программы). Отладчик изменяет не файл Р.ЕХЕ, хранящийся на диске, а только его копию в памяти. После завершения сеанса работы с отладчиком все, что было загружено в память, исчезает вместе с внесенными нами изменениями.

Начальное окно отладчика дает недостаточно информации для серьезной работы с программой. При отладке программы на уровне языка ассемблера необходимо контролировать все регистры процессора, включая регистр флагов, а также во многих случаях поля данных вне программы (например, векторы прерываний или системные таблицы). Для этого надо командой Alt+V, С (пункт главного меню View-CPU) открыть «окно процессора» (см.рис.).

Окно процессора состоит, в свою очередь, из пяти внутренних окон для наблюдения текста программы на языке ассемблера и в машинных кодах, регистров процессора, флагов, стека и содержимого памяти. С помощью этих окон можно полностью контролировать работу процессора при выполнении отлаживаемой программы. Для того чтобы можно было работать с конкретным окном, надо сделать его активным, щелкнув по нему мышью. Переходить из окна в окно можно также (по кругу), нажимая клавишу Tab. Для управления ходом программы используются функциональные клавиши, перечисленные в нижней строке кадра (F7 или F8 для пошагового выполнения, F4 для выполнения до курсора и т. д.). Курсор во всех внутренних окнах окна процессора выглядит в виде синей ленточки. Добавим еще, что, щелкнув мышью по значку стрелки вверх в правом верхнем углу окна процессора, можно увеличить это окно до размеров кадра отладчика.

Продемонстрируем некоторые возможности окна процессора. По ходу пошагового выполнения программы можно изменять содержимое регистров. Это дает возможность исправлять обнаруженные ошибки (если выяснилось, что в какой-то строке программы заполняется не тот регистр или не тем содержимым), а также динамически модифицировать программу с целью изучения ее работы.

Выполните программу до первой команды int 21h (предложение 7) и просмотрите содержимое регистров процессора. Вы увидите, что в старшей половине регистра АХ находится число 09h — номер вызываемой функции DOS. Младшая половина регистра АХ заполнена «мусором» — остатком от выполнения последней операции с регистром АХ. В регистре DX будет OOOOh — относительный адрес первого байта строки mesg в сегменте команд. Изменим этот относительный адрес. Для этого надо перейти в окно регистров, поместить курсор на строку, отображающую содержимое регистра DX, и ввести команду Alt+F10, открывающую внутреннее меню окна регистров (см.рис.).

Как видно из рисунка, меню окна регистров предоставляет возможность выполнить увеличение содержимого регистра на 1 (Increment), уменьшить его на 1 (Decrement), обнулить (Zero) и заменить на любое заданное значение (Change). Выбрав пункт Change, занесем в регистр DX число 5 (см.рис.).

Теперь, если выполнить очередную команду (int 21h), DOS выведет на экран строку, начало которой расположено в байте 5 сегмента данных. В нашей фразе «Hello, world!» байт 5 приходится на запятую (нумерация байтов в строке, естественно, начинается с нуля). В результате на экран будет выведена строка

Еще одной полезной операцией является вывод содержимого области памяти, начиная с заданного адреса (дамп памяти). Таким образом можно изучать не только поля данных программы, но и содержимое вообще любых участков памяти компьютера, в частности системных таблиц. Посмотрим, например, как выглядит в памяти блок окружения программы.

Любая программа, загруженная в память, состоит из двух отдельных блоков: собственно программы и ее окружения, которое располагается обычно перед программой, хотя не обязательно вплотную к ней.

Окружение представляет собой область памяти, в которой в виде символьных строк записаны значения переменных, называемых переменными окружения, например

PROMPT= $p$g PATH=C:\DOS; C:\TOOLS; C:\NC;

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

Пользователь может включить в окружение строки определения дополнительных переменных с помощью команды SET. Часто в качестве значений таких переменных указываются пути к каталогам со вспомогательными файлами или ключи, задающие режим работы различных программ.

Как называется программа отладчик ассемблера

Отладчик фирмы Borland — это весьма мощное средство отладки программ. Этот отладчик разрабатывался еще для DOS-программирования и ориентирован в основном на языки фирмы Borland. Существенно то, что отладчик позволяет отлаживать программу как на уровне дизассемблированных команд микропроцессора, так и на символьном уровне, т.е. с использованием текста программы. В последнем случае требуется, чтобы при трансляции в исполняемый модуль была помещена отладочная информация.

Рассмотрим, например, программу на Рис. 3.4.1. Эта программа выводит в окно типы устройств (список доступных дисков). При трансляции с помощью TASM32 добавим также ключ /zi, а при компоновке (TLINK.32.EXE) ключ -v. В этом случае в исполняемом модуле будет сохранена информация, необходимая для символьной отладки.

Тут важно иметь в виду, что для символьной отладки, т.е. с использованием текста программы, необходим не только сам исполняемый модуль и отладочная информация в нем, но и сам программный текст. Дело в том, что в исполняемом модуле хранится информация, позволяющая сопоставлять машинные коды программ и текст программы. На Рис. 4.3.1 представлено окно отладчика с загруженной отладочной информацией программы DRIV.ASM (Рис. 3.4.1). Отладчик позволяет выполнять программу в пошаговом режиме (подробнее о режимах выполнения программы см. далее в главе), видя одновременно текст программы и дизассемблированный текст, и передвигаясь по нему, видеть результат каждого шага.

На Рис. 4.3.1 представлены три наиболее часто используемых окна отладчика: окно с текстом программы (модульное окно), окно CPU, где хранится дизассемблированный текст, а также текущая информация о регистрах и флагах, и окно сообщений, которое очень важно при отладке оконных GUI-приложений.

Рассмотрим команды отладчика и возможности отладчика.

Запуск отлаживаемого приложения. Выполняется по клавише F9 или по команде RUN из меню RUN. Программа выполняется в полном объеме, если только не указана точка останова (breakpoint) на строку в окне с текстом программы или окне CPU. Точка останова устанавливается при помощи клавиши F2. Открыв окно Watch (View/ Watches), Вы можете установить там слежение за переменными, которые Вам интересны. В этом случае точки останова окажутся очень полезными.

Выполнить программу до строки, где находится курсор. Команда выполняется при помощи клавиши F4 или посредством меню. Смысл данной команды заключается в том, что, заинтересовавшись каким-либо местом программы, Вы можете выполнить программу до данной строки и посмотреть результат выполнения. Фактически это та же точка останова, но работает более оперативно. Кроме того, можно заставить выполняться программу до данного адреса (Alt+F9).

Выполнить единичную команду. Команда выполняется по нажатию клавиши F7. Если мы находимся в окне с текстом программы, то выполняется оператор соответствующего языка. Если мы находимся в окне CPU, то выполняется инструкция микропроцессора.

Рис. 4.3.1. Окна Turbo Debugger.

Выполнение команды с переходом через процедуру. Данная команда выполняется по нажатию клавиши F8. Отличается от предыдущей команды тем, что не происходит вход в процедуру, но все инструкции в процедуре выполняются автоматически.

Выполнить процедуру. Если курсор находится на команде вызова процедуры, то можно выполнить лишь одну эту процедуру командой Alt+F8.

Выполнение с задержкой. Можно заставить отладчик выполнять программу с задержкой после каждой команды (Animation) — команда Animate меню Run.

При выполнении любой из перечисленных команд также могут быть заданы опции командной строки — Run/Arguments.

Рассмотрим теперь окна, которые используются при отладке в данном отладчике. С некоторыми из них мы уже познакомились, но все равно повторимся. Показать на экране то или иное окно можно посредством меню View. Мы опишем часть наиболее важных окон.

Текст программы можно увидеть на экране, обратившись к пункту меню Module. При этом программа должна быть оттранслирована с опциями, обеспечивающими сохранение в исполняемом модуле отладочной информации. К сожалению, речь идет только о продуктах фирмы Borland: C++, Delphi, Assembler. Отладочная информация других фирм Turbo Debugger не распознает. В дальнейшем мы рассмотрим пример отладки программы, написанной на Borland C++.

Если отладочную информацию в исполняемом модуле Turbo Debugger не распознает, Вам при отладке придется использовать окно CPU. Окно разделено на пять частей (см. Рис. 4.3.2 против часовой стрелки):
1. Инструкции микропроцессора.
2. Сегмент данных.
3. Сегмент стека.
4. Состояние регистров.
5. Основные флаги.

Рис. 4.3.2. Окно CPU отладчика Turbo Debugger.

Окно слежения за переменными. Окно открывается через View/Watches. Щелкнув правой кнопкой мыши, мы можем по желанию добавить в окно переменные, за значением которых мы хотим наблюдать. При пошаговом выполнении программы, либо при выполнении с точками останова можно на каждом этапе контролировать значение переменных.

Окно стека. Показывает текущее состояние стека. Причем первая вызванная функция будет находиться на дне стека.

Окно точек останова. Здесь содержится информация обо всех установленных в программе точках останова. Здесь же можно добавить или установить точки останова.

Окно регистрации (Log). В нем хранится информация о происходящих в отладчике событиях.

Окно переменных. Отображает все переменные, доступные в данном месте.

Окно файлов. В этом окне можно просмотреть двоичный файл и исправить его при необходимости.

Окно отображения памяти. Дает возможность построчного просмотра памяти.

Окно сопроцессора. Данное окно отображает текущее состояние сопроцессора.

Окно истории выполнения (Execution History). Окно содержит историю выполнения программы.

Окно иерархии (Hierarchy). Выводит на дисплей иерархическое дерево для всех объектов и всех типах классов, используемых в текущем модуле. Пример окна, отображающего иерархию классов объектов, см. на Рис. 4.3.3.

Рис. 4.3.3. Пример окна Turbo Debugger, отображающего иерархию классов.

Окно потоков. Данное окно содержит информацию обо всех потоках, работающих в данный момент в программе.

Окно сообщений. С помощью данного окна можно отслеживать все сообщения, получаемые указанным окном. На Рис. 4.3.4 представлен пример использования окна сообщений. Можно выделить класс отслеживаемых сообщений или задать единичные сообщения. Кроме отслеживания, можно задать и другую реакцию — прервать выполнение, т.е. установить точку останова на определенное сообщение.

Рис. 4.3.4. Пример содержимого окна сообщений

Окно буфера обмена. С помощью данного окна можно следить за содержимым буфера обмена при выполнении программы.

II

Поговорим теперь об отладке программ, написанных на языке высокого уровня. Если при трансляции в исполняемом модуле была сохранена отладочная информация, то Turbo Debugger будет работать и с языком высокого уровня. Рассмотрим, например, простую консольную программу, демонстрирующую пузырьковую сортировку. Программа располагается на Рис. 4.3.6.

Puc. 4.3.5. Программа, расположенная на Рис. 4.3.6 в окне отладчика.

#include #include void sort(DWORD a[], DWORD ); void main() < DWORD a[10]; DWORD j,p,k,r; randomize(); for(j=0; jl; k--) < r=0; for(j=0; ja[j+1])< p=a[j+1] ; a[j+1]=a[j] ; a[j]=p; r=1; >; > if(r==0) break; > for(j=0; j

Рис. 4.3.6. Пример простой консольной программы.

На Рис. 4.3.5 показано окно отладчика Turbo Debugger с данной программой. В нижней части рисунка расположено окно Watches. В этом окне отслеживается значение элементов массива а[]. Обратившись к окну CPU, можно увидеть, как алгоритм пузырьковой сортировки, реализованный на языке Си, преобразуется в ассемблерный код.

III

Основной целью отладчика Turbo Debugger является отладка программы, имеющей отладочную информацию. Дело в том, что хотя отладчик и дизассемблирует программу, и мы можем видеть дизассемблированный код в окне CPU, такие дизассемблеры, как W32Dasm и IDA Pro намного превосходят в этой части Turbo Debugger. Отладчик же Ice, работая в нулевом кольце, также более удобен для анализа исполняемых модулей. Отладчик Turbo Debugger занимает свою нишу в отладке, но здесь он незаменим и очень удобен. В данном разделе мы намерены рассмотреть некоторые вопросы методики отладки.

  1. Обнаружение ошибки. Обнаружение ошибок в программе связано с тестированием программы и ее эксплуатацией.
  2. Поиск ее местонахождения. Данный этап часто оказывается самым трудоемким, и именно здесь может пригодиться использование отладчика. Профессиональные программисты знают, что в сложных алгоритмах найти ошибку путем простого анализа текста программы бывает очень сложно. Можно, конечно, выводить промежуточные результаты, но для этого может понадобиться слишком много таких выводов. Хороший отладчик в этом случае незаменим, поскольку может позволить отслеживать значение переменных на каждом шаге.
  3. Определение причины ошибки. Отбрасывая тот случай, когда причиной ошибки является неправильный алгоритм, рассмотрим типичные ассемблерные ошибки.
    1. Ошибка в порядке следования операндов. Например, MOV EAX,EBX вместо MOV EBX,EAX.
    2. При использовании рекурсивных алгоритмов или слишком большой вложенности вызовов процедур может быть исчерпан стек.
    3. При вызове процедур может быть испорчено содержимое того или иного регистра.
    4. Неосвобождение стека при выходе из процедуры.
    5. Неправильное использование условных переходов — JA вместо JNA и т.п.
    6. Часто при организации циклических алгоритмов программисты ошибаются относительно последних значений переменных.
    7. Неправильная установка флага направления.
    8. Ошибка при определении границ переменных и массивов. Эти ошибки часто приводят к тому, что портится содержимое и других переменных.
    9. Неправильное преобразование из одного типа операнда к другому. Например, после загрузки MOV AL,BL используется EAX и забывается об обнулении старших байтов регистра EAX.

    Fore kc .ru
    Рефераты, дипломы, курсовые, выпускные и квалификационные работы, диссертации, учебники, учебные пособия, лекции, методические пособия и рекомендации, программы и курсы обучения, публикации из профильных изданий

    Отладчик Debug и язык Ассемблер

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

    Разумеется, мы возьмем максимально простую систему машинных кодов — систему команд вычислительной машины, которая предложена в учебнике [1]; она называется “Кроха”. Полный перечень команд “Крохи” приведен в табл. 4, где для каждой операции также указана ее мнемоника (в исходном учебнике не использовалась). Мнемонические имена команд выбраны вполне стандартными и совпадают с принятыми в ассемблере компьютеров типа IBM PC.

    Для уменьшения технической работы используем некоторые упрощения; они не повлияют на понимание принципов функционирования программы-ассемблера, зато существенно ускорят программирование.

    · Каждая команда занимает отдельную строку, пустые строки запрещены. Согласно устройству “Крохи”, любая команда занимает ровно одну ячейку, значит, номер ячейки фактически совпадет с номером строки.

    · Части команды (метка, операция и три адреса) разделены между собой произвольным количеством пробелов (M: ADD X Y R).

    · Все метки (идентификаторы) условимся обозначать одной латинской буквой, после которой ставится двоеточие. Хотя это и несложно, проверять каждую новую метку на совпадение со старыми не будем; факт того, что это именно латинская буква, для простоты также не проверяется.

    · Константы (десятичные числа), заносимые ассемблером в ячейки, будем записывать после обозначения DN, например, DN 19. Правильность значения константы (для “Крохи” она должна быть целой и неотрицательной, не превышающей 4095) контролировать не будем.

    · Так как практическая эксплуатация нашего программного продукта не предполагается, диагностику ошибок в нем предусматривать не будем (известно, что часто это наиболее трудоемкая часть процесса разработки программы).

    · Вместо длинных 12-разрядных двоичных кодов, которые вполне оправданны в учебнике для демонстрации принципов фон Неймана, везде будем пользоваться более короткими восьмеричными кодами; подчеркнем, что данная система прекрасно гармонирует с командами “Крохи”, у которых и код операции, и адреса состоят именно из трех двоичных разрядов.

    Благодаря принятым упрощениям ассемблирующая программа на Паскале получается совсем небольшой (ниже приводится ее листинг).

    kop: Array[0..7] Of string[3] = (‘MOV’,’ADD’,’DIV’,’SUB’,’JE’,’MUL’,’JG’,’HLT’);

    prg: Array [0..7] Of String =

    tab: Array [0..15] Of Record

    sym: string[3]; num: byte

    i, Nid, k, e, c: integer;

    очередной идентификатор и находит в массиве tab его код>

    Function Get_code(i: integer; Var k: integer): byte;

    Var p, id: string; j, m, q: integer;

    While p[k] <> ‘ ‘ Do

    id := id + p[k]; k := k + 1

    For j := 0 To Nid Do

    If Then q := tab[j].num;

    FOR i := 0 To 7 Do

    tab[i].sym := kop[i]; tab[i].num := i

    For i := 0 To 7 Do

    If prg[i][2] = ‘:’ Then

    For i := 0 To Nid Do

    For i := 0 To 7 Do

    Case c Of

    Write(‘ ‘, Get_code(i, k))

    Val(Copy(prg[i], k, 255), k, e);

    Write(k Div e);

    k := k Mod e; e := e Div 8

    Программа работает следующим образом. Ядром ее данных служит массив tab с данными типа “запись”, в котором содержатся сведения об идентификаторах — их имена (в поле sym) и коды, которые вместо этих имен надо подставлять в программу (поле num). Например, операция ADD имеет код 1 (см. табл. 4), поэтому tab[1].sym = ‘ADD’ и tab[1].num = 1. Становится очевидным, что центральная идея работы программы-ассемблера состоит в замене каждого символьного имени соответствующим ему числовым кодом (на основе массива tab).

    После занесения в строки таблицы с номерами от 0 до 7 мнемоник команд “Крохи” добавим далее оператор определения константы DN и все найденные в программе метки, которые поставил пользователь в тексте. Метки пользователя распознаются по наличию после них двоеточия; чтобы обработанные метки не мешали ассемблеру в дальнейшем, программа их просто стирает. Обработка меток в литературе носит название первого прохода, поскольку сначала надо “пройти” всю программу и подготовить полную таблицу идентификаторов, и только потом повторно просмотреть текст (второй проход), заменяя с помощью массива tab все идентификаторы их кодами.

    При выполнении второго прохода наш ассемблер опирается на “мощную” функцию Get_code, которая выделяет из текста программы очередной идентификатор. Несмотря на сложный вид, идея функции достаточно проста: в заданной строке, начиная с заданной позиции, она пропускает пробелы, а затем, наоборот, “собирает” все отличающиеся от пробела символы, формируя тем самым имя очередного идентификатора. Когда имя выделено, оно ищется в таблице, и в качестве результата выдается его код (или 255, если имя в таблице отсутствует).

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

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

    Заметим, что итоговый восьмеричный код программы просто выводится на экран монитора и нигде не сохраняется.

    Для тестирования демо-ассемблера реализована традиционная задача — вычисление факториала числа n. Обозначим k вспомогательную переменную, которая является текущим множителем для факториала и меняется от 1 до n. Тогда итоговая программа приобретает вид, приведенный в табл. 5.

    Второй столбец данной таблицы может быть использован для проверки результатов ассемблирования, которые наша программа выводит на экран. Более подробно программа вычисления факториала для “Крохи” рассматривалась в недавней публикации [2].

    В качестве продолжения нашего эксперимента предлагаем читателям самостоятельно написать обратную программу — диcассемблер, которая по восьмеричному 4-разрядному коду восстанавливает ассемблерную мнемонику команд 3 . Для решения задачи советуем воспользоваться массивом, аналогичным массиву tab, разделив его, если потребуется, на части. Разумеется, первоначальные имена меток по коду не восстановить, поэтому можно присваивать им по мере появления последовательные значения букв латинского алфавита.

    А теперь подведем итоги того, что мы узнали.

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

    Примечание. В последнее время появляются и более причудливые схемы трансляции с языков высокого уровня. Например, в языке Ява (Java) компилятор транслирует текст программы в код некоторой виртуальной машины; тем самым итоговый результат компиляции также перестает быть машинно-зависимым. Вся “привязка” к конкретному компьютеру сосредоточена теперь в реализации Ява-машины, которая разрабатывается один раз и способна исполнить на данной платформе любую Ява-программу вне зависимости от того, на какой машине она была откомпилирована. Похожие идеи заложены и в идеологию платформы .NET (читается “дот-нэт”, или “точка-нэт”) фирмы Microsoft: компилятор с любого языка программирования генерирует код для единой виртуальной машины. Таким образом, делаются попытки сделать машинно-независимым не только исходный текст программы на языке высокого уровня, но и результат его компиляции.

    1. Гейн А.Г., Линецкий Е.В., Сапир М.В., Шолохович В.М. Информатика. М.: Просвещение, 1994.

    Введение в отладчик OllyDbg

    Введение в отладчик OllyDbg

    Как сам писал автор, Олег Юшук, “OllyDbg это 32-битный отладчик на уровне ассемблера под операционную систему Windows. Его функционал заточенный под работу с бинарным кодом делает этот отладчик чрезвычайно полезным в случае, когда необходимо отладить программу без доступа к ее исходному коду.” Олли это также динамический отладчик. Это значит, что она позволяет пользователю менять код программы прямо по-ходу исполнения. Это очень важно, в случаях когда мы экспериментируем с бинарником, пытаясь понять как тот работает. Помимо этого, Олли обладает множеством полезных функций, что делает ее одним из лучших отладчиков для реверс-инжиниринга (мы сейчас не говорим об отладке на уровне ядра).

    Описание

    Вот картинка основного окна Олли:

    Введение в отладчик OllyDbg

    Как видно, основное окно CPU разбито на четыре части: Disassembly (дизассемблер), Registers (регистры), Stack (стек) и Dump (дамп). Ниже будет представлено описание каждой секции.

    1. Disassembly (дизассемблер)

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

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

    Последняя колонка это комментарии, оставленные Олли. Иногда они могут содержать имя вызываемой API-функции (если Олли смогла ее определить), например CreateWindow или GetDlgItemX. Олли также пытается помочь нам в понимании кода, давая имена функциям, которые не являются API-функциями. Например на картинке выше, видно вызовы функций с именами “ImageRed.00510C84″ и “ImageRed.00510BF4″.

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

    2. Register (регистры)

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

    Register (регистры)

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

    Средняя секция содержит так называемые флаги, которые меняют свое значение в результате определенных операций (например при сравнение двух чисел: если они равны тогда во флаг записывается единица, нет – то ноль). Они тоже играют важную роль.

    Последняя секция содержит FPU-регистры или регистры математического сопроцессора.

    Они используются, когда процессор проводит операции над вещественными числами. В процессе реверс-инжиниринга не так часто приходится с ними сталкиваться, в основном когда имеешь дело с шифрованием.

    3. Stack (стек)

    Stack (стек)

    Стек – это секция памяти зарезервированная для хранения временного списка данных. Эти данные включают в себя указатели к адресам в памяти, строки, переменные и, очень важно, адреса возвратов – места в программе куда необходимо вернуться после выполнения функции. Когда в программе в каком-либо месте вызывается какая-либо функция мы передаем этой функции управление, что бы она могла выполнить то, что нужно. Но в конце это функции необходимо вернуться туда, откуда он была вызвана и продолжить дальше исполнять следующие команды. Для этого и нужен адрес возврата.

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

    На этой картинке, первый столбец это адрес в памяти каждого элемента, второй столбец – это шестнадцатеричный код – значение, которое хранятся в стеке и последний столбец это комментарии Олли, о каждом элементе – то что он смогла установить. Например, на картинке напротив верхнего элемента стека стоит комментарий “RETURN to kernel…” Это адрес, который процессор расположил на стеке, что бы знать куда вернуться, когда текущая функция будет исполнена.

    Если мы захотим изменить какой-либо элемент стека, нужно кликнуть по нему правой кнопкой мыши и в открывшемся меню выбрать пункт “modify”.

    4. Dump (дамп)

    Dump (дамп)

    Раннее в этом туториале, когда мы говорили о “сырых” опкодах которые процессор непосредственно считывает и исполняет, я упомянул, что мы может посмотреть эти коды в хекс-редакторе. Секция Dump в Олли как раз и предсталяет собой простой встроенный хекс-редатор. В нем мы можем посмотреть на сырой код программы, расположенный в оперативной памяти. Помимо шестнадцатеричных кодов, тут также можно увидеть символы, которые соответствуют тем или иным кодам, согласно ASCII. Итак, первая колонка в окне секции Dump – это адреса в памяти, вторая – сырой бинарный код, представленный в шестнадцатеричном формате, третья – символы.

    Тулбар

    Давайте теперь рассмотрим тулбар, который предоставляет нам Олли

    Тулбар

    Здесь расположены основные функции для запуска кода. Имейте также ввиду, особенно если вы новичек, все эти кнопки дублируются в вернем выпадающем меню в разделе “Debug”, так что если вы не уверены что означает та или иная кнопка – можете посмотреть там.

    Я сделаю несколько заметок об функциях некоторых кнопок. “Re-load” означает перезапуск приложения с последующей остановкой на точке входа. Все изменения, внесенные вами в код, будут удалены, некоторые брейкпоинты (точки останова) будут отключены и приложение прервет исполнение кода. “Run” и “Pause” делают то, что следует из их названия. “Run” – запускает исполнение кода, “Pause” – временно останавливает, с возможностью потом продолжить с этого момента. “Step In” означает выполнить одну строчку кода и затем остановиться. Если этой строчкой кода является вызов функции, то мы переходим внутрь этой функции. “Step Over” делает тоже самое, но не заходит внутрь функций, а исполняет их целиком за один раз. “Animate” это тоже самое как постоянно жать “Step-In” или “Step-Over” но делать это достаточно медленно, чтобы успевать самому видеть ход исполнения. Эта функция не так часто используется, но бывает интересно посмотреть за ходом исполнения кода, особенно в случаях когда мы имеем дело с самомодифицирующемся кодом и можем вживую наблюдать за его изменениями. Похоже, я забежал слишком вперед.

    Рассмотрим теперь иконки для управления окнами:

    иконки для управления окнами

    Каждая из этих иконок открывает окно, некоторые из которых пользуются часто, некоторые – редко. Что бы лучше самому с ними разобраться можно просто покликать на все подряд и посмотреть какое окно, открывает каждая кнопка. Каждая эта кнопка дублируется в верхнем выпадающем меню в разделе “View”, так что можете также заглянуть туда. Давайте пройдемся по нескольким основным окнам прямо сейчас.

    1. (M)emory (память)

    Окно “Memory”

    Окно “Memory” отображает все блоки памяти, выделенные программой. Это включает основные секции запущенного приложения (в данном случае “Showstr” в столбце “Owner”). Ниже вы также можете видеть множество других секций – это DLL, который программа загрузила в память и планирует использовать. Если дважды кликнуть по какой либо из этих строчек, откроется окно показывающее дизассемблированный код (или просто шестандцатеричный дамп) этой секции. Это окно также показывает тип каждого из блоков, права доступа, размер и адрес в памяти, где эта секция загружена.

    2. (P)atches (патчи)

    все патчи которые вы сделали

    Это окно отображает все патчи, которые вы сделали. Патч означает изменение в коде. Заметьте, что в столбце “State” отображается значение “Active”. Это означает, что все изменения, сделанные в коде, актуальны на данный момент. Если вы перезапустите приложение, кликнув на “Re-load”, все изменения сбросятся и в столбе “State” напротив каждого сделанного вами патча появится значение “Disabled”. Вы также можете активировать-деактивировать патчи если кликните на выбранный вами патч, удерживая кнопку пробела. С столбце “Old” отображается оригинальная строчка кода, в стобце “New” – измененная.

    3. (B)reakpoints (точки останова)

    поставленные вами брейкпоинты

    В этом окне отображены все поставленные вами брейкпоинты. Как мы увидим дальше это окно часто используется при отладке.

    4. (K)all Stack (вызовы в стеке)

    Как видно, автор программы выбрал не совсем верную букву для этой иконки, т.к. английское слово Call вообще-то начинается с “C” 🙂

    (K)all Stack (вызовы в стеке)

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

    В следующем туториале я выложу свою “прокаченную” версию Олли с расширенным функционалом:

    Олли с расширенным функционалом

    Контекстное меню

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

    Контекстное меню

    Я пройдусь по самым часто-используемым функциям, с остальными вы разберетесь сами, по мере накопления опыта.

    “Binary” – редактирование бинарных данных побайтого. Это место где вы можете изменить строчку “Не зарегистрировано” на “Зарегистрировано” 🙂

    “Breikpoints” – установка брейкпоитов (точек останова) на какую-либо строчку кода. Существует несколько типов брейкпоинтов, мы подробнее рассмотри их в следующем туториале.

    “Search for” предоставляет функции по поиску данных в бинарнике, таких как строки, вызовы функций и т.д.

    “Analysis” заставляет Олли заново провести процесс анализ кода, с которым вы сейчас работаете. Иногда Олли затрудняется отличить код от данных (т.к. с точки зрения процессора это все лишь набор битов), и эта функция заставляет Олли учитывать то, в каком месте кода вы сейчас находитесь и попытаться понять, как эта секция должна будет выглядеть.

    Также обратите внимание, что мое меню немного отличается от вашего, т.к. я установил себе некоторые плагины, которые добавляют в Олли определенный функционал. Я подробнее расскажу об этом в дальнейшем.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *