Ассемблер для Windows

         

Окно сопроцессора



Окно сопроцессора.

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



Окно ссылок на строки



Рисунок 4.4.4. Окно ссылок на строки.


Как видно из рисунка, можно скопировать в буфер выбранную строку или все строки.



Окно стека



Окно стека.

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



Окно точек останова



Окно точек останова.

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



Окончательный вариант программы


В старом Си для того, чтобы для хранения переменных компилятор использовал регистры, переменные следует объявлять как AUTO.



Операции с текстом





Операции с текстом.

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



Оператор switch или оператор выбора



2. Оператор switch или оператор выбора.

Оператор switch весьма часто употребляется в функциях окон. Хорошее знание его ассемблерной структуры поможет Вам легче отыскивать эти функции в море ассемблерного кода.

switch(i) { case 1: ... ... ... break; case 3: ... ... ... break; case 5: ... ... ... break; }

А вот соответствующий данной структуре ассемблерный код.

DEC EAX JZ L1 SUB EAX,2 JZ L2 SUB EAX,2 JZ L3 JMP L4 L1: ... ... ... JMP L4 L2: ... ... ... JMP L4 L3: ... ... ... L4:

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

CMP EAX,10 JE L1 CMP EAX,5 JE L2 CMP EAX,11 JE L3 ...



Описание заголовка РЕ



II

В таблице, представленной ниже мы даем описание заголовка РЕ.

СмещениеДлина поляНазвание поляОписание поля

0000h - неизвестный процессор.

014Ch - i386

014Dh - i486

014Eh - i586

0162h - MIPS Mark I (R2000, R3000)

0163h - MIPS Mark II (R6000)

0166h - MIPS Mark III (R4000)

Чаще всего данное поле указывает на процессор 386.

0000h - это программа;

0001h - файл не содержит перемещений и таблицы перемещаемых элементов;

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

0200h - загружать в память фиксированно. Указывает на то, что программу можно грузить только по адресу, записанному в Image Base, если это невозможно, то такой файл лучше вообще не запускать.

2000h - это библиотека.



00h DWORD Signature Bytes Сигнатура. Первые два байта "PE" 4550Н. Еще два байта обязательно должны быть равны нулю.
04h WORD CPU Type Данное поле указывает на процессор, который следует предпочесть при запуске программы. Вот возможное значение этого поля:
06h WORD Num of Objects Поле указывает на число реальных входов в Object Table (см. таб. ниже).
08h DWORD Time/Date Stamp Дата и время, которые устанавливаются при компоновке программы.
0Ch DWORD Pointer to COFF table Дополнительный указатель, определяющий местонахождение отладочной COFF-таблицы в файлах. Это поле используется только в OBJ-файлах и РЕ-файлах, содержащих отладочную COFF-информацию.
10h DWORD COFF table size Количество символов в COFF-таблице.
14h WORD NT Header Size Размер заголовка РЕ-файла, начиная с поля Magic - таким образом, общий размер заголовка РЕ-файла составляет NT Header Size + 18h.
16h WORD Flags Указывает на предназначение программы. Значение флагов:
18h WORD Magic Слово-сигнатура, определяющее состояние отображенного файла. Определены следующие значения:
107Н - отображение ПЗУ. 10BH - нормально исполняемое отображение.
1Ah BYTE Link Major Старший номер версии использовавшегося при создании модуля компоновщика. Десятичный вид.
1Bh BYTE Link Minor Младший номер версии использовавшегося при создании модуля компоновщика. Десятичный вид.
1Ch DWORD Size of Code Размер именно программного кода в файле. KERNEL использует это значение для фактического отведения памяти под загружаемую программу. Установка этого значения слишком маленьким приведет к выдаче сообщения о нехватке памяти. Обычно большинство модулей имеют только одну программную секцию .text.
20h DWORD Size of Init Data Размер секции инициализированных данных, очевидно, не используется в Windows 95, но используется в Windows NT. Назначение аналогично приведенному выше.
24h DWORD Size of UnInit Data Размер секции неинициализированных данных. Неинициализированные данные обычно содержатся в секции .bss. Данная секция не занимает на диске никакого места, но при загрузке модуля в память загрузчик отводит под нее память.
28h DWORD Entry point RVA Адрес относительно Image Base, no которому передается управление при запуске программы или адрес инициализации/завершения библиотеки.
2Ch DWORD Base of Code Адрес секции относительно базового адреса (40000Н), содержащей программный код. Этот адрес обычно равен 1000Н для компоновщика Microsoft и 10000H для компоновщика Borland.
30h DWORD Base of Data Адрес относительно базового (40000H), с которого начинаются секции данных файла. Секции данных обычно идут последними в памяти, после заголовка РЕ и программных секций.
34h DWORD Image Base При создании компоновщик помещает сюда адрес, куда будет отображен исполняемый файл в памяти. Если загрузчик отобразит файл именно по этому адресу, то дополнительной настройки не потребуется.
38h DWORD Object align Выравнивание программных секций. После отображения в память каждая секция будет обязательно начинаться с виртуального адреса, кратного данной величине.
1 - подсистема не требуется (NATIVE). 2 - запускается в подсистеме Windows GUI. 3 - запускается в подсистеме Windows character (терминальное или консольное приложение). 5 - запускается в подсистеме OS/2. 7 - запускается в подсистеме Posix.
3Ch DWORD File align В случае РЕ- файла исходные данные, которые входят в состав каждой секции, будут обязательно начинаться с адреса, кратного данной величине. Значение по умолчанию составляет 200Н.
40h WORD OS Major Старший номер версии операционной системы, необходимый для запуска программы.
42h WORD OS Minor Младший номер версии операционной системы.
44h WORD USER Major Пользовательский номер версии, задается пользователем при линковке программы. Старшая часть.
46h WORD USER Minor Пользовательский номер версии, младшая часть.
48h WORD SubSys Major Старший номер версии подсистемы.
4Ah WORD SubSys Minor Младший номер версии подсистемы. Типичное значение версии 4.0, что означает Windows 95.
4Ch DWORD Reserved Зарезервировано.
50h DWORD Image Size Представляет общий размер всех частей отображения, находящихся под контролем загрузчика. Эта величина равна размеру области памяти, начиная с базового адреса отображения и заканчивая адресом конца последней секции. Адрес конца секции выровнен на ближайшую верхнюю границу секции.
54h DWORD Header Size Общий размер всех заголовков: DOS Stub + РЕ Header + Object Table
58h DWORD File CheckSum Контрольная сумма всего файла. Как и в операционной системе MS DOS, ее никто не контролирует, а программа редактирования связей устанавливает ее в 0. Предполагалось ее рассчитывать как инверсию суммы всех байтов файла.
5Ch WORD Subsystem Операционная подсистема, необходимая для запуска данного файла. Вот значения этого поля:
5Eh WORD DLL Flags Указывает на специальные потребности при загрузке, начиная с операционной системы NT 3.5. Устарел и не используется.
60h DWORD Stack Reserve Size Память, требуемая для стека приложения. Память резервируется, но выделяется только Stack Commit Size байтов. Следующая страница является охранной. Когда приложение достигает этой страницы, то она становится доступной, а следующая страница - охранной, и так до достижения нижней границы, после чего Windows 95 убивает программу.
64h DWORD Stack Commit Size Объем памяти, отводимой для стека немедленно после загрузки.
68h DWORD Heap Reserve Size Максимально возможный размер локальной кучи.
6Ch DWORD Heap Comit Size Отводимый размер кучи при загрузке.
70h DWORD Loader Flags Начиная с Windows NT 3.5 объявлено неиспользуемым, назначение неясно, но в целом связано с поддержкой отладки.
74h DWORD Num of RVA and Sizes Указывает размер массива VA/Size, который следует ниже, данное поле зарезервирована под будущие расширения формата. В данный момент его значение всегда равно 10h.
78h DWORD Export Table RVA Относительный адрес (относительно базового адреса) таблицы экспорта.
7Ch DWORD Export Data Size Размер таблицы экспорта.
80h DWORD Import Table RVA Относительный адрес (относительно базового адреса) таблицы импорта.
84h DWORD Import Data Size Размер таблицы импорта.
88h DWORD Resource Table RVA Относительный адрес (относительно базового адреса) таблицы ресурсов.
8Ch DWORD Resource Data Size Размер таблицы ресурсов.
90h DWORD Exception Table RVA Относительный адрес таблицы исключений.
94h DWORD Exception Data Size Размер таблицы исключений.
98h DWORD Security Table RVA Адрес таблицы безопасности. По-видимому, не используется.
9Ch DWORD Security Data Size Размер таблицы безопасности.
A0h DWORD Fix Up's Table RVA Относительный адрес таблицы настроек.
A4h DWORD Fix Up's Data Size Размер таблицы настроек.
A8h DWORD Debug Table RVA Относительный адрес таблицы отладочной информации.
ACh DWORD Debug Data Size Размер таблицы отладочной информации.
B0h DWORD Image Description RVA Относительный адрес строки описания модуля.
B4h DWORD Description Data Size Размер строки описания модуля.
B8h DWORD Machine Specific RVA Адрес таблицы значений, специфичных для микропроцессора.
BCh DWORD Machine Data Size Размер таблицы значений, специфичных для микропроцессора.
C0h DWORD TLS RVA

Указатель на локальную область данных цепочек. C4h DWORD TLS Data Size Размер области данных цепочек. C8h DWORD Load Config RVA Предназначение неизвестно. CCh DWORD Load Config Data Size Предназначение неизвестно. D0h 08h Reserved Зарезервировано. D8h DWORD IAT RVA Используется в NT. В Windows 95, судя по всему, нет. DCh DWORD IAT Data Size Размер описанного поля. E0h 08h Reserved Зарезервировано. E8h 08h Reserved Зарезервировано. F0h 08h Reserved Зарезервировано. Между заголовком РЕ и данными для секций расположена таблица секций. Вот элемент этой таблицы.
Элемент таблицы секций содержит полную базу данных об одной секции.

Смещение
Длина поля
Название поля
Описание поля
.text - исполняемый код общего назначения.

CODE - исполняемый код, помещаемый компоновщиками фирмы BORLAND.

.icode - переходники (jump'ы), помещаемые сюда старой версией TLINK32.

.data - инициализированные данные, помещаются компоновщиком фирмы Microsoft.

DATA - инициализированные данные, помещаемые сюда компоновщиком TLINK32.

.bss - неинициализированные глобальные и статические переменные.

.CRT - еще одна секция для хранения инициализированных данных.

.rsrc - секция для хранения ресурсов.

.idata - секция импорта.

.edata - секция экспорта.

.reloc - секция настроек. Данная информация может понадобиться загрузчику, если он не сможет загрузить модуль по базовому адресу.

.tls - данные для запуска цепочек.

.rdata - данная секция в основном содержит отладочную информацию.

.debug$s и .debug$t - данные секции есть только в COFF-объектных файлах. Они содержат информацию о символах Code View и их типах.

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

00h 08h Object Name Имя объекта, остаток заполнен нулями. Если имя объекта имеет длину 8 символов, то заключительного 0 нет. Вот несколько возможных имен:
08h DWORD Virtual Size Виртуальный размер секции - именно столько памяти будет отведено под секцию. Если Virtual Size превышает Physical Size, то разница заполняется нулями, так определяются секции неинициализированных данных (Physical Size = 0).
0Ch DWORD Section RVA Размещение секции в памяти, ее виртуальный адрес относительно Image Base. Позиция каждой секции выровнена на границу Object align (степень 2 от 512 до 256М включительно, по умолчанию 64К), секции упакованы вплотную друг к другу, впрочем, можно это не соблюдать. Для объектных файлов поле не имеет смысла.
10h DWORD Physical Size Размер секции (ее инициализированной части) в файле кратно полю File align в заголовке РЕ Header, должно быть меньше или равно Virtual Size. Для объектных файлов это поле содержит точный размер секции, сгенерированный компилятором или ассемблером. Другими словами, для объектных файлов оно эквивалентно полю Virtual Size.
14h DWORD Physical Offset Физическое смещение относительно начала ЕХЕ-файла, выровнено на границу File align поля заголовка РЕ Header. Смещение используется загрузчиком для поиска.
18h DWORD Pointer to Linenumber Файловое смещение таблицы номеров строк. Используется для объектных файлов.
1Ch WORD Number of Relocations Количество перемещений в таблице поправок. Используется только для объектных файлов.
1Eh WORD Number of Linenumbers Количество номеров строк в таблице номеров строк для данной секции. Используется для объектных файлов.
20h 08h Reserved Зарезервировано для объектных файлов.
28h DWORD Object Flags Битовые флаги секции:

00000004h - используется для кода с 16-битными смещениями.

00000020h - секция кода.

00000040h - секция инициализированных данных.

00000080h - секция неинициализированных данных.

00000200h - комментарии или любой другой тип информации.

00000400h - оверлейная секция.

00000800h - не будет являться частью образа программы.

00001000h - общие данные.

00500000h - выравнивание по умолчанию, если не указано иное.

02000000h - может быть выгружен из памяти.

04000000h - не кэшируется.

08000000h - не подвергается страничному преобразованию.

10000000h - разделяемый.

20000000h - выполнимый.

40000000h - можно читать.

80000000h - можно писать.
Страницы образов секций. Здесь мы изучим некоторые секции.

Оптимизация кода



6. Оптимизация кода.

Выше мы пытались восстановить исходный текст программы по ассемблерному коду. Насколько это удалось, можно выяснить, сравнив получившийся текст с исходным. Следует заметить, что мы ошиблись: в исходном тексте стоит цикл while, а в нашем do. Однако, если откомпилировать получившуюся программу, она будет работать, так же как и исходная. Причина отличия двух текстов программ (весьма простых, кстати) заключается в том, что транслятор, кроме всего прочего, производит еще оптимизацию кода. Результат оптимизации - принципиальная невозможность восстановить исходный текст по исполняемому коду. Можно, однако, получить программный текст, правильно описывающий алгоритм исполнения. Можно просто понять то, что делает данный код, чем мы в данной главе и занимаемся.

Рассмотрим небольшую и весьма тривиальную программу на Си.

void main () { int i; char a[10]; char b[11]; for(i=0; i<10; i++) { a[i]='a'; *(b+i)='a'; printf("%c %c\n",a[i],b[i]); } ExitProcess(0); }



Оптимизация условных переходов



Оптимизация условных переходов.

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

... CMP EAX, 100 JB L1 Фрагмент 1 JMP L2 L1: Фрагмент 2 L2:

Мы знаем, что содержимое EAX чаще всего оказывается меньше 100. Следовательно, фрагмент можно заменить на следующий.

... CMP EAX,100 JNB L1 Фрагмент 2 JMP L2 L1: Фрагмент 1 L2:



Оптимизация вызовов процедур



Оптимизация вызовов процедур.

Рассмотрим следующий фрагмент.

P1 PROC ... ... ... CALL P2 RET P1 ENDP ... ... ... P2 PROC ... ... ... RET P2 ENDP

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

Р1 PROC ... ... ... JMP P2 Р1 ENDP ... ... ... P2 PROC ... ... ... RET P2 ENDP

Код становится и быстрее, и короче, вот только разобраться в нем становится сложнее. На этом мы оставляем вопрос оптимизации. Всех интересующихся могу отослать к книге [14].



Основной целью отладчика Turbo Debugger является отладка программы



III

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

Вообще процесс отладки можно разделить на четыре этапа:

Обнаружение ошибки. Обнаружение ошибок в программе связано с тестированием программы и ее эксплуатацией.

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

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

Ошибка в порядке следования операндов. Например, MOV EAX,EBX вместо MOV EBX,EAX. При использовании рекурсивных алгоритмов или слишком большой вложенности вызовов процедур может быть исчерпан стек. При вызове процедур может быть испорчено содержимое того или иного регистра. Неосвобождение стека при выходе из процедуры. Неправильное использование условных переходов - JA вместо JNA и т.п. Часто при организации циклических алгоритмов программисты ошибаются относительно последних значений переменных. Неправильная установка флага направления. Ошибка при определении границ переменных и массивов. Эти ошибки часто приводят к тому, что портится содержимое и других переменных. Неправильное преобразование из одного типа операнда к другому. Например, после загрузки MOV AL,BL используется EAX и забывается об обнулении старших байтов регистра EAX.

Исправление ошибки. Если найденная ошибка проста и очевидна для Вас, то исправить ее не составит большого труда. Кстати, как Вы, наверное, уже поняли, Turbo Debugger не позволяет исправлять исполняемые модули. Но случаются и ситуации, когда очевидно, что данный участок программы (или процедура) выдает ошибочное значение, а на то, чтобы найти ошибку, у Вас не хватает времени - участок достаточно сложен. Иногда неправильное значение выходной информации возникает, лишь при редком сочетании входных параметров. В этом случае может быть применен простой прием: между указанным участком и остальной частью программы вставляется несколько строк, проверяющих выходную информацию и исправляющих ее, если нужно. Такой прием часто оказывается незаменим при доработке чужой программы очень большого объема, когда понять логику чужого алгоритма (да еще содержащего ошибку) бывает просто невозможно.



Отладчик фирмы Borland



I

Отладчик фирмы 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-приложений.

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



Отладчик SoftIce



II

Отладчик SoftIce (версия 4.05) или просто Ice рассчитан для работы в Windows 9x и Windows NT. 55. Отладчик состоит из собственно отладчика (в английском варианте это "kernel-mode debugger", что можно перевести как "отладчик на уровне ядра"), кроме этого, в пакет SoftIce входит еще символьный загрузчик для загрузки в отладчик исполняемых модулей. Загрузчик позволяет прочитать отладочную информацию для продуктов фирмы Microsoft и Borland.

Итак, что дает отладка при помощи SoftIce?

символьная и обычная отладка 32-битных приложений; отладка драйверов для Windows NT и для Windows 9x, отладка 16-битных приложений для MS DOS и Windows, отладка внутренних программ операционной системы; установка обычных точек останова на команду, стоящую по определенному адресу; установка точек останова на операции чтения/записи в память, чтения/записи в порты ввода-вывода; установка точек останова на сообщения Windows; установка условных точек останова, т.е. точек останова, срабатывающих при выполнении определенного условия; получение внутренней информации операционной системы; возможность использования отладчика на удаленной машине и др.

Отладчик SoftIce имеет разные исполнения для Windows 9x и для Windows NT. В первом случае он представляет собой VXD-драйвер и запускается из autoexec.bat (программа WINICE.EXE). В Windows NT он представляет собой драйвер уровня ядра - NTICE.SYS.



Отладочная информация ( debug$S debug$T)



Отладочная информация (.debug$S, .debug$T)

Здесь помещается структура отладочного каталога, создаваемого любыми компоновщиками. Другая отладочная информация зависит от транслятора. Структуру COFF-отладочной информации можно посмотреть в книге [2].



Отладочный каталог



Отладочный каталог

Смещение

Длина поля

Название поля

Описание поля

0000h - UNKNOWN/BORLAND;

0001h - COFF таблица символов;

0002h - Code View таблица символов;

0003h - FPO таблица символов;

0004h - MISC;

0005h - EXCEPTION;

0006h - FIXUP.



Переменные и константы



Переменные и константы.

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

Для примера я взял простую консольную программу, написанную на Borland C++. В текстовом варианте программа занимает полтора десятка строк, тогда как ЕХЕ-файл имеет размер более 50 Кб. Впрочем, размер исполняемых файлов давно уже никого не удивляет. Интересно другое: корректно справился с задачей, т.е. корректно выявил точку входа - метку _main, только один дизассемблер - IDA PRO. Т.е., конечно, реально работающий участок программы дизассемблировали все, но выявить, как происходит переход на участок, смог только упомянутый мной дизассемблер. Приятно также и то, что аккуратно была распознана функция _printf. На Рисунок 4.5.1 показан фрагмент дизассемблированной программы, соответствующей основной процедуре main. С другой стороны, в данном случае нет никаких видимых возможностей быстрого поиска данного фрагмента в отладчике. Осюда наглядно можно понять полезность совместного использования отладчика и дизассемблера.

CODE 00401108 _main proc near ; DATA XREF: DATA:0040B044 CODE 00401108 CODE 00401108 argc = dword ptr 8 CODE 00401108 argv = dword ptr 0Ch CODE 00401108 envp = dword ptr 10h CODE 00401108 CODE 00401108 push ebp CODE 00401109 mov ebp, esp CODE 0040110B push ebx CODE 0040110C mov edx, offset unk_40D42C CODE 00401111 xor eax, eax CODE 00401113 CODE 00401113 loc_401113: ; CODE XREF: _main+22 CODE 00401113 mov ecx, 1Fh CODE 00401118 sub ecx, eax CODE 0040111A mov ebx, ds:off_40B074 CODE 00401120 mov cl, [ebx+ecx] CODE 00401123 mov [edx+eax], cl CODE 00401126 inc eax CODE 00401127 cmp eax, 21h CODE 0040112A jl short loc_401113 CODE 0040112C mov byte ptr [edx+20h], 0 CODE 00401130 push edx ; char CODE 00401131 push offset aS ;_va_args CODE 00401136 call _printf CODE 0040113B add esp, 8 CODE 0040113E pop ebx CODE 0040113F pop ebp CODE 00401140 retn CODE 00401140 _main endp



Первое информационное окно отладчика



Рисунок 4.4.6. Первое информационное окно отладчика.


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



Поговорим теперь об отладке программ



II

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



Поиск нужного места в программе



Поиск нужного места в программе.

Часто требуется найти в дизассемблированном коде место, соответствующее месту исполняемой программы. Наиболее эффективно это можно сделать следующим образом. Загружаем в отладчик данный модуль. Запускаем его, доходим до нужного места и нажимаем кнопку "Terminate". В результате подсвеченная строка в дизассемблированном коде окажется как раз в нужном месте. Нужно только иметь в виду, что некоторые программы делают изменения, которые потом продолжают действовать. К таковым относятся, в частности, горячие клавиши.



Поиск процедуры окна



Поиск процедуры окна.

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

Для примера я взял программу FILES.EXE. Эта поисковая программа очень удобна для поиска информации в локальной сети. Выберем, например, одно из диалоговых окон данной программы и поставим цель найти функцию этого диалогового окна. Сделаем это двумя способами: с помощью отладочных средств программы W32Dasm и с помощью отладчика SoftIce. На Рисунок 4.6.6. показаны несколько открытых диалоговых окон программы. Последнее диалоговое окно с заголовком "Название сетевого компьютера" и будет объектом нашего внимания.



Пример динамического драйвера



Рисунок 4.7.5. Пример динамического драйвера.

Прокомментируем программу на Рисунок 4.7.5.

Как я уже говорил, при загрузке драйвера на него приходит сообщение w32_DeviceIoControl, при этом на структуру указывает регистр ESI. При этом поле dwIoControlCode будет содержать число DIOC_Open, в действительности равное просто 0. Поле dwIoControlCode находится по смещению ESI+12. Убедившись, что там содержится 0, мы возвращаем управление, обнулив предварительно EAX (это необходимо). При вызове драйвера из программы мы используем номер 3. Убедившись, что в поле dwIoControlCode содержится 3, мы таким образом должны сделать то, что ждет от нас программа. Собственно задача драйвера - вывести сообщение со строкой, полученной от программы. Известен адрес строки и ее длина. Чтобы продемонстрировать некоторые функции VXD-сервиса, мы еще раз определяем длину строки и копируем ее в буфер, подготовленный в теле драйвера. Наконец вывод сообщения и возвращение управления с обнулением содержимого EAX.

Сделаем теперь некоторые пояснения к структуре Client_Reg_Struc.

Client_Reg_Struc STRUC Client_EDI DD ? Client_ESI DD ? Client_EBP DD ? Client_res0 DD ? Client_EBX DD ? Client_EDX DD ? Client_ECX DD ? Client_EAX DD ? Client_Error DD ? Client_EIP DD ? Client_CS DW ? Client_res1 DW ? Client_EFlags DD ? Client_ESP DD ? Client_SS DW ? Client_res2 DW ? Client_ES DW ? Client_res3 DW ? Client_DS DW ? Client_res4 DW ? Client_FS DW ? Client_res5 DW ? Client_GS DW ? Client_res6 DW ? Client_Alt_EIP DD ? Client_Alt_CS DW ? Client_res7 DW ? Client_Alt_EFlags DD ? Client_Alt_ESP DD ? Client_Alt_SS DW ? Client_res8 DW ? Client_Alt_ES DW ? Client_res9 DW ? Client_Alt_DS DW ? Client_res10 DW ? Client_Alt_FS DW ? Client_res11 DW ? Client_Alt_GS DW ? Client_res12 DW ? Client_Reg_Struc ENDS



Пример дизассемблирования программы с помощью самого мощного дизассемблера IDA PRO (под Windows)



Рисунок 4.2.4. Пример дизассемблирования программы с помощью самого мощного дизассемблера IDA PRO (под Windows).


Рассмотрим некоторые возможности этого дизассемблера.

1. Переименование процедур и меток в программе. При дизассемблировании IDA PRO дает, разумеется, свои названия процедурам и меткам. Вы можете ввести свои названия, тем самым сделав программу более понятной. Все изменения, сделанные в тексте, сохраняются в специальной базе и могут быть восстановлены при повторном запуске.

2. Распознавание библиотечных и API-функций (см. Рисунок 4.2.4). Дизассемблер не просто распознает эти функции, но и комментирует параметры этих функций.

3. При помощи контекстного меню или двойного щелчка Вы можете перейти по команде JMP или команде CALL в указанное место программы и так продолжать осуществлять переходы любое количество раз. Возвратиться на любое количество шагов можно, используя кнопку "стрелка" на панели инструментов.

4. При помощи Shift+Ins, Ins, а так же пунктов меню "EDIT" в любом месте программы можно записать комментарий. Комментарий, как и введенные названия меток, запоминается в базе данных программы. Но это еще не самое приятное. В комментарии может присутствовать адрес строки программы или имя метки. Если сделать двойной щелчок по адресу или метке, то мы как раз и очутимся на этом месте.

5. Сворачивание и разворачивание процедур. При помощи клавиши "-" на дополнительной клавиатуре можно свернуть процедуру, а при помощи "+" развернуть процедуру. Представление процедур в свернутом виде позволяет представить программу в более компактном и более понятном виде.

6. IDA PRO весьма аккуратно распознает не только код, но и данные. На Рисунок 4.2.5 показана дизассемблированная часть нашей программы, содержащей данные.

7. Создание и выполнение командных файлов. Язык командных файлов очень близок к языку Си. У меня нет намерения рассказывать о языке, который использует IDA PRO, приведу только один такой командный файл, содержащийся в пакете IDA PRO.



Пример командного файла IDA PRO



Рисунок 4.2.6. Пример командного файла IDA PRO.

Прокомментируем программу на Рисунок 4.2.6. Как легко догадаться, организация цикла и условные конструкции имеют в точности тот же синтаксис, что и в языке Си. Главное здесь - разобрать смысл используемых библиотечных функций. Легко видеть, что функция Message просто выводит строку в окно сообщений, которое находится под основным окном. Функция ChooseFunction вызывает окно, которое вызывается также из меню: "Jump to Function". Функция GetFunctionFlags возвращает информацию об указанной функции. Наконец функция NextFunction осуществляет переход на следующую функцию. Она возвращает адрес функции. Аргументом же ее является адрес функции, от которой осуществляется переход на следующую функцию. Оставляю Вам изучение командного языка, поддерживаемого IDA PRO, который представлен в помощи программы.

Теоретически можно написать любую сколь угодно сложную программу по анализу дизассемблированного кода.

8. Программа IDA PRO осуществляет дизассемблирование модулей самых различных форматов: .OBJ, .EXE, .DLL, VXD, .ZIP, .NLM и др.

9. Функциональность IDA PRO может быть значительно усилена посредством подключаемых модулей - plugin'ов. Подключаемые модули пишутся на языке C++ и имеют структуру РЕ-модулей. Подключение модулей осуществляется через горячие клавиши или через пункты меню Edit\Plugins. Подключаемые модули находятся в специальном каталоге Plugins, где находится и файл конфигурации, где указаны эти модули.

10. Еще одна приятная особенность дизассемблера - он создает ассемблерный файл, с которым затем можно работать уже в текстовом режиме.



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



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




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



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

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



Пример задания двух локальных массивов Взят из отладчика IDA PRO



Рисунок 4.5.3. Пример задания двух локальных массивов. Взят из отладчика IDA PRO.

Взгляните внимательно на Рисунок 4.5.3. Как видите, отладчик нам все прописал. Две переменные var_54 и var_28 являются, несомненно, массивами типа DWORD. Причем если на первый отводится 28h байт, т.е. 40 байт или 10 элементов массива, то на второй 54h-28h=2CH=44 или 11 элементов массива. И всего, следовательно, под локальные переменные зарезервировано 84 байта. А что означает команда add esp,0FFFFFFACH ? Но нас не обманешь! 0-0FFFFFFACH = 54h, что в десятичном исчислении и есть 84. В связи с массивами хотелось бы упомянуть, что в Си изначально практиковалось два способа доступа к элементам массива: посредством индексов и посредством указателей. Другими словами, можно было написать: a[i]=10 и *(a+i)=10. Причем вторая запись оказывалась более эффективной (см. по этому поводу книгу [1], гл. 15.). Разумеется, делать это можно и сейчас, но обе записи теперь в Borland C++ и Microsoft C++ 6.0 приводят к совершенно одинаковым ассемблерным эквивалентам. Что весьма отрадно - значит, компиляторы действительно развиваются. Кстати, весьма поучительным было бы сравнение ассемблерного кода, производимого разными компиляторами. Мы не будем этим заниматься, замечу только, что мое весьма поверхностное сравнение компиляторов Borland C++ 5.0 и Visual C++ 6.0 привело к выводу, что средства, разработанные фирмой Microsoft, несколько более эффективно оптимизируют транслируемый код.

Но вернемся опять к фрагменту на Рисунок 4.5.3. Посмотрим, как выглядит начало функции main в дизассемблере W32Dasm.

:00401108 55 push ebp :00401109 8ВЕС mov ebp, esp :0040110B 83C4AC add esp, FFFFFFAC :0040110E 53 push ebx :0040110F 33DB xor ebx, ebx



Программа на Си++ использующая объекты



Рисунок 4.5.7. Программа на Си++, использующая объекты.

Ниже на Рисунок 4.5.8. представлен дизассемблированный код процедуры main.

CODE 00401122 _main proc near ; DATA XREF: DATA:0040C044 CODE 00401122 CODE 00401122 var_28 = dword ptr -28h CODE 00401122 var_18 = word ptr -18h CODE 00401122 dest = dword ptr -4 CODE 00401122 argc = dword ptr 8 CODE 00401122 argv = dword ptr 0Ch CODE 00401122 envp = dword ptr 10h CODE 00401122 CODE 00401122 push ebp CODE 00401123 mov ebp, esp CODE 00401125 add esp, 0FFFFFFD8h CODE 00401128 push ebx CODE 00401129 mov eax, offset stru_40C084 CODE 0040112E call @_InitExceptBlockLDTC CODE 00401133 push 12Ch CODE 00401138 call unknown_libname_8 CODE 0040113D pop ecx CODE 0040113E mov [ebp+dest], eax CODE 00401141 test eax, eax CODE 00401143 jz short loc_40117D CODE 00401145 mov [ebp+var_18 ], 14h CODE 0040114B push offset aPrivet ; src CODE 00401150 push [ebp+dest] ; dest CODE 00401153 call _strcpy CODE 00401158 add esp, 8 CODE 0040115B push offset aPrivet_0 ; src CODE 00401160 mov edx, [ebp+dest] CODE 00401163 add edx, 0C8h CODE 00401169 push edx ; dest CODE 0040116A call _strcpy CODE 0040116F add esp, 8 CODE 00401172 mov [ebp+var_18], 8 CODE 00401178 mov ebx, [ebp+dest] CODE 0040117B jmp short loc_401180 CODE 0040117D ; ------------------------------------ CODE 0040117D CODE 0040117D loc_40117D: ; CODE XREF: _main+21 CODE 0040117D mov ebx, [ebp+dest] CODE 00401180 CODE 00401180 loc_401180: ; CODE XREF: _main+59 CODE 00401180 push ebx ; dest CODE 00401181 call sub_401108 CODE 00401186 pop ecx CODE 00401187 push ebx ; char CODE 00401188 push offset aS ; _va_args CODE 0040118D call _printf CODE 00401192 add esp, 8 CODE 00401195 push ebx ; block CODE 00401196 call @$bdele$qpv ; operator delete(void *) CODE 0040119B pop ecx CODE 0040119C mov eax, [ebp+var_28] CODE 0040119F mov large fs:0, eax CODE 004011A5 xor eax, eax CODE 004011A7 pop ebx CODE 004011A8 mov esp, ebp CODE 004011AA pop ebp CODE 004011AB retn CODE 004011AB _main endp



Программа расположенная на Рисунок в окне отладчика



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


#include <windows.h> #include <stdio.h> void sort(DWORD a[], DWORD ); void main() { DWORD a[10]; DWORD j,p,k,r; randomize(); for(j=0; j<10; j++) a[j]=random(10) ; // сортировка for(k=9; k>l; k--) { r=0; for(j=0; j<k; j++) { if(a[j]>a[j+1]){ p=a[j+1] ; a[j+1]=a[j] ; a[j]=p; r=1; }; } if(r==0) break; } for(j=0; j<10; j++) printf("%lu\n",a[j]); ExitProcess(0); }



Программа WDasm



I

Программа W32Dasm (Windows Disassembler) представляет собой симбиоз довольно мощного дизассемблера и отладчика. Версия 8.93 программы, наиболее распространенная в настоящее время, может работать не только с РЕ-модулями, но и DOS-, NE-, -LE- модулями. Я намерен довольно полно описать работу с этой программой.



Программа загружающая использующая и выгружающая динамический драйвер



Рисунок 4.7.4. Программа, загружающая, использующая и выгружающая динамический драйвер.

Прокомментируем программу на Рисунок 4.7.4. Главное здесь - разобрать работу функции DeviceIoControl. Вот параметры этой функции:

1-й параметр, дескриптор драйвера, полученный через функцию CreateFile. 2-й параметр, номер необходимой Вам операции. 3-й параметр, адрес данных для драйвера. 4-й параметр, длина данных. 5-й параметр, буфер, куда драйвер поместит свои данные. 6-й параметр, длина буфера. 7-й параметр, адрес переменной, куда будет занесено количество байтов, помещенных в буфер драйвером. 8-й параметр, адрес OVERLAPPED-структуры.

Как видите, при вызове мы передаем указатель на строку MES1.

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

DIOCParams STRUC Internal1 DD ? VMHandle DD ? Internal2 DD ? dwIoControlCode DD ? lpvInBuffer DD ? cbInBuffer DD ? lpvOutBuffer DD ? cbOutBuffer DD ? lpcbBytesReturned DD ? lpoOverlapped DD ? hDevice DD ? tagProcess DD ? DIOCParams ENDS

Опишем поле структуры:

Internal1 - указатель на структуру Client_Reg_Struc, определяющую регистры вызывающего приложения (см. Рисунок 4.7.6. и пояснения к нему).

VMHandle - дескриптор виртуальной машины.

Internal2 - указатель на блок DDB.

dwIoControlCode - номер необходимой операции.

lpvInBuffer - указатель на буфер, содержащий информацию вызывающей программы.

cbInBuffer - количество байт, пересылаемых в буфере.




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

cbOutBuffer - количество байт в буфере.

lpcbBytesReturned - количество возвращаемых байт.

lpoOverlapped - указатель на OverLapped-структуру.

hDevice - дескриптор драйвера, возвращаемый функцией CreateFile.

tagProcess - признак процесса.

.386P include vmm.inc include vcond.inc include vwin32.inc include shell.inc

DECLARE_VIRTUAL_DEVICE MSG,1,0, MSG_Control,\ UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch MSG ; будем обрабатывать сообщение w32_Device!oControl ; процедурой PROC1 Control_Dispatch w32_DeviceIoControl, PROC1 End_control_dispatch MSG

; сегмент данных VxD_PAGEABLE_DATA_SEG CAP1 DB "Окно сообщения",0 MES1 DB 50 DUP (0) VxD_PAGEABLE_DATA_ENDS

; сегмент кода VxD_PAGEABLE_CODE_SEG BeginProc PROC1 CMP DWORD PTR [ESI]+12,DIOC_Open JNE L1 XOR EAX,EAX JMP _EXIT L1: CMP DWORD PTR [ESI]+12,3 JNZ _EXIT ; длина строки MOV EDI,DWORD PTR [ESI]+16 VMMCall _lstrlen, <EDI> ; копировать в буфер INC EAX ; длина VMMCall _lstrcpyn,<OFFSET MES1,EDI,EAX> ; вызвать функцию SHELL_Message MOV ECX,OFFSET MES1 ; DWORD PTR [ESI]+14 MOV EDI,OFFSET CAP1 MOV EAX,MB_OK + MB_ICONEXCLAMATION VMMCall Get_Sys_VM_Handle ; адрес CaliBack функции, в данном случае NULL XOR ESI,ESI ; ссылка на данные для CallBack-функции XOR EDX,EDX VxDCall SHELL_Message XOR EAX,EAX _EXIT: RET EndProc PROC1 VxD_PAGEABLE_CODE_ENDS

end


Работа с динамическими библиотеками



Работа с динамическими библиотеками.

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



Ресурсы



Ресурсы.

В начале дизассемблированного текста также описаны и ресурсы, точнее два основных ресурса-меню и диалоговое окно. Со списком этих ресурсов можно работать и в специальных окнах, получаемых с помощью пунктов меню программы "Refs\Menu References" и "Refs\Dialog references". Строковые ресурсы можно увидеть в уже упомянутом окне просмотра перечня строковых ссылок (см. Рисунок 4.4.4). Остальные ресурсы данной версии программы, к сожалению, не выделяются.



Секция экспорта ( edata) Общая структура



Секция экспорта (.edata). Общая структура

00h DWORD Debug Flags Флаги, по-видимому, не используются и устанавливаются в нулевое значение.
04h DWORD Time/Date Stamp Дата и время создания отладочной информации.
08h WORD Major Version Старший номер версии отладочной информации.
0Ah WORD Minor Version Младший номер версии отладочной информации.
0Ch DWORD Debug Type Тип информации для отладчика. Вот эти типы:
10h DWORD Data Size Размер в байтах данных для отладки без размера заголовка.
14h DWORD Data RVA Относительный адрес расположения отладочных данных в памяти.
18h DWORD Data Seek Смещение к отладочным данным в файле.

1. Таблица экспорта

1Таблица собственно экспорта   Export Directory Table
2   Адресная таблица   Export Address Table
3   Таблица указателей на имена   Export Name Table Pointers
4   Таблица номеров   Export Ordinal Table
5   Таблица самих имен   Export Name Table

Смещение

Длина поля

Название поля

Описание поля

2. Таблица адресов экспорта. Эта структура данных содержит адреса экспортируемых функций (их точки входа) в формате DWORD (по 4 байта на элемент). Для доступа к данным используется номер функции с коррекцией на базу номеров (Ordinal Base).

3. Таблица указателей на имена. Данная структура содержит указатели на имена экспортируемых функций, указатели отсортированы в лексическом порядке для обеспечения возможности бинарного поиска. Каждый указатель занимает 4 байта. Имена функций обычно лежат в секции экспорта.




4. Таблица номеров. Данная структура совместно с Name Table Pointers формирует два параллельных массива, разделенных для облегчения к ним доступа индексированием на родные для процессора данные (слова, двойные слова, но не сложные структуры). Данный массив содержит номера экспорта, которые в общем случае являются индексами в Address Table экспорта (за вычетом базы Ordinal Base). Элементами данного массива являются слова (2 байта).

5. Таблица имен экспорта. Эта таблица содержит необязательные (по мнению Microsoft) имена экспортируемых функций. Данный массив используется совместно с Name Table Pointers и Ordinal Table для обеспечения связывания загрузчиком импорта/экспорта по имени. Механизм описывался выше. Каждый элемент являет собой ASCIIZ строку с именем экспортируемой функции. Никто не говорит, что они должны в файле идти друг за другом последовательно, хотя так построено большинство файлов. Надо отметить, что имена экспорта чувствительны к регистру. Отметим особенность загрузчика - при связывании, если адрес функции находится в секции экспорта, на самом деле по указанному адресу лежит строка, переадресующая к другой библиотеке, экспортирующей данную функцию (с указанием библиотеки и самой функции). Это называется - передача экспорта.


Секция ресурсов ( rdata)



Секция ресурсов (.rdata)

Ресурсы представляют собой многоуровневое двоичное дерево. Их структура позволяет содержать до 2^31 уровней, однако реально используется только 3: самый верхний есть Type, затем Nam и затем Language (тип, имя, язык). Перемещения по иерархии каталогов ресурсов похожи на перемещения по каталогам жесткого диска. Типичное представление ресурсного участка в файлах:

00h DWORD Flags Зарезервировано, должно быть равно нулю.
04h DWORD Time/Date Stamp Время и дата создания экспортных данных.
08h WORD Major Version Старший номер версии таблицы экспорта. Не используется.
0Ah DWORD Minor Version Младший номер версии таблицы экспорта, также не используется.
0Ch DWORD Name RVA Относительный адрес строки, указывающей на имя нашей библиотеки.
10h DWORD Ordinal Base Начальный номер экспорта, для функций, экспортируемых данным модулем.
14h DWORD Num of Functions Количество функций экспортируемых данным модулем, является числом элементов массива Address Table (см.ниже).
18h DWORD Num of Name Pointers Число указателей на имена, обычно равно числу функций, но это не так, если у нас есть функции, экспортируемые только по номеру.
1Ch DWORD Address Table RVA Указатель на таблицу относительных адресов экспортируемых функций.
20h DWORD Name Pointers RVA Указатель на таблицу указателей на имена экспортируемых функций данного модуля.
24h DWORD Ordinal Table RVA Указатель на таблицу номеров экспорта, данный массив по индексам параллелен Name Pointers, элементами являются слова.



Скорость или объем



Скорость или объем.

Это весьма важный вопрос, когда дело касается микропроцессора.

Например, команда MOV EAX,EBX выполняется быстрее команды XCHG EAX,EBX, но длиннее ее. Зная такую особенность, можно либо сокращать объем, либо увеличивать скорость программы. Особенно часто используется замена такой операции, как MUL, другими командами, в частности SHL. Это может значительно увеличить скорость выполнения программы и увеличить ее объем. Интересно, что арифметические действия можно производить и с помощью команды LEA (см. Приложение 2), и современные трансляторы уже взяли это на вооружение. Так что команда MUL не так часто может встретиться в оттранслированном коде, как об этом можно подумать, исходя из текста программы. Вообще, свойство команд надо исследовать, и иногда узнаешь довольно интересные вещи. Например, нахождение остатка от деления числа без знака на 4 производится следующим образом: AND EAX,0003h - не правда ли, оригинально?



Содержимое файла VXD MAP



III

Приступим теперь к написанию простого, но работающего статического виртуального драйвера. Такой драйвер представлен на Рисунок 4.7.3. Ниже мы обсудем этот драйвер, а также построение статических виртуальных драйверов.

.386P

include vmm.inc include shell.inc include vcond.inc

; заполнить структуру DDB DECLARE_VIRTUAL_DEVICE VXD2,1,0, VXD2_Control, \ UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

; объявить принимаемые сообщения и процедуры, ; которые их обрабатывают Begin_control_dispatch VXD2 Control_Dispatch Create_VM, OnVMCreate Control_Dispatch VM_Terminate2, OnVMClose End_control_dispatch VXD2

; сегмент для хранения сообщений VxD_PAGEABLE_DATA_SEG MsgTitle db "Сообщение драйвера VXD",0 VMCreated db "Создается виртуальная машина",0 VMDestroyed db "Уничтожается виртуальная машина",0 VMFocus db "Смена фокуса виртуальной машины" VxD_PAGEABLE_DATA_ENDS

; сегмент, содержащий код VxD_PAGEABLE_CODE_SEG

; процедура - реакция на создание виртуальной машины BeginProc OnVMCreate ; здесь можно вставить код - реакцию на ; открытие виртуальной машины

MOV ECX,OFFSET VMCreated CALL MES RET EndProc OnVMCreate

; процедура - реакция на закрытие виртуальной машины BeginProc OnVMClose ; здесь можно вставить код - реакцию на ; закрытие виртуальной машины

MOV ECX,OFFSET VMDestroyed CALL MES RET EndProc OnVMClose

; процедура, выводящая сообщение MES PROC ; получить дескриптор системной виртуальной машины VMMCall Get_sys_vm_handle ; дескриптор возвращается в EBX ; в EAX - флаг сообщения MOV EAX,MB_OK ; адрес заголовка сообщения MOV EDI, OFFSET MsgTitle ; адрес CallBack функции, в данном случае NULL XOR ESI,ESI ; ссылка на данные для CallBack-функции XOR EDX,EDX ; сервисная функция VXD - окно сообщения VxDCall SHELL_Message MES ENDP VxD_PAGEABLE_CODE_ENDS

end

Рисунок 4.7.3. Пример простого статического виртуального драйвера.

Как мы уже указывали, статический драйвер загружается при загрузке системы и остается в памяти до завершения работы операционной системы. Удобнее всего загрузить драйвер, указав строку device=имя_драйвера в секции [386enh] файла SYSTEM.INI. Можно также использовать системный реестр: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\key\StaticVxD=pathname.




Первое, однако, более удобно, т.к. в случае ошибки в VXD- драйвере можно исключить его запуск, отредактировав файл SYSTEM.INI в MS DOS.

При установке виртуальных драйверов VMM посылает драйверам следующие три сообщения в порядке списка.

SysCriticalInit - посылается при переключении в защищенный режим, но до разрешения прерываний.

Device_Init - посылается после разрешения прерываний. Именно это сообщение чаще всего используется виртуальными драйверами для проведения начальной инициализации.

Init_Complete - последнее сообщение, посылаемое виртуальным драйверам при загрузке системы.

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

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

System_Exit2 - сообщение посылается перед выгрузкой системы. Микропроцессор находится еще в защищенном режиме.

Sys_Critical_Exit2 - следующее сообщение перед выгрузкой системы.

Device_Reboot_Notify2 - сообщают виртуальным драйверам, что система "собирается" выгружаться. Прерывания еще доступны.

Crit_Reboot_Notify2 - аналогичное предыдущему сообщению, но прерывания недоступны.

Обратимся теперь к программе на Рисунок 4.7.3. Этот драйвер выводит сообщение при активизации виртуальной машины (например, создание консоли или просто запуск DOS-приложения) и при ее деактивизации. В драйвере мы использовали две сервисные функции: получить дескриптор системной виртуальной машины и вывести сообщение. Рассмотрим эти функции:

Get_sys_vm_handle - получить дескриптор системной виртуальной машины. Причем дескриптор возвращается в регистре EBX.

SHELL_Message — вывести сообщение. Параметры хранятся в регистрах:

EBX — дескриптор виртуальной машины. EAX - флаг сообщения, например MB_OK. ECX - 32-битный адрес строки-сообщения. EDI - 32-битный адрес строки-заголовка. ESI - адрес функции - реакции на действие пользователя. Если функции нет, тогда 0. EDX - адрес данных, которые будут посылаться функции.

И еще, при выходе драйвер должен очищать флаг переноса. В нашем случае очистка флага переноса обусловлена правильным выполнением функции SHELL_Message.


Сообщение об истечении времени работы программы



Рисунок 4.6.3. Сообщение об истечении времени работы программы.


Таким образом, следует решить две проблемы:

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

Рисунок 4.6.2 являет собой явный "прокол" авторов программы. Дело в том, что окно и все его содержимое можно спрятать в ресурсы. Но когда на том же окне появляется новая запись - это уже программный код. Итак, запускаем W32Dasm и считываем туда программу All Screen. Запускаем окно SDR (String Data Reference), ищем строку Shareware Delay, дважды щелкаем по ней и, закрыв его, оказываемся в нужном месте программы. Вот этот фрагмент (Рисунок 4.6.4.).

* Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004420BC(C) | :00442123 33D2 xor edx, edx :00442125 8B83B0010000 mov eax, dword ptr [ebx+000001B0] :0044212B E8541DFDFF call 00413E84 :00442130 33D2 xor edx, edx :00442132 8B83B4010000 mov eax, dword ptr [ebx+000001B4] :00442138 E8471DFDFF call 00413E84 :0044213D 33D2 xor edx, edx :0044213F 8B83B8010000 mov eax, dword ptr [ebx+000001B8] :00442145 E83A1DFDFF call 00413E84 :0044214A BA50000000 mov edx, 00000050 :0044214F 8B83BC010000 mov eax, dword ptr [ebx+000001BC] :00442155 E8D618FDFF call 00413A30

* Possible StringData Ref from Code Obj ->"Shareware Delay"

:0044215A BAA8214400 mov edx, 004421A8 :0044215F 8B83BC010000 mov eax, dword ptr [ebx+000001BC] :00442165 E8EE1DFDFF call 00413F58 :0044216A 33D2 xor edx, edx :0044216C 8B83C0010000 mov eax, dword ptr [ebx+000001C0] :00442172 E80D1DFDFF call 00413E84 :00442177 33D2 xor edx, edx :00442179 8B83C4010000 mov eax, dword ptr [ebx+000001C4] :0044217F E8001DFDFF call 00413E84 :00442184 33D2 xor edx, edx :00442186 8B83C8010000 mov eax, dword ptr [ebx+000001C8] :0044218C E8F31CFDFF call 00413E84 :00442191 8B83CC010000 mov eax, dword ptr [ebx+000001CC] :00442197 E8E8D4FFFF call 0043F684 :0044219C 5B pop ebx :0044219D C3 ret



Структура ЕХЕпрограммы для MS DOS



Рисунок 4.1.1. Структура ЕХЕ-программы для MS DOS.

Более подробно разбор заголовка DOS-программы можно найти в [1]. Добавлю только, что сразу за таблицей перемещения начинается исполняемая часть модуля. Таблица же перемещения используется для того, чтобы при загрузке настроить адреса. Но это лишь в том случае, если в программе используются адреса сегментов. В противном случае таблица перемещения не содержит элементов, и исполняемый код начинается сразу за форматированной частью заголовка. Перейдем теперь к общей структуре РЕ-модуля.



Структура элемента массива настроек



Структура элемента массива настроек

Каталог ресурсовResources Directory Table
Данные ресурсов   Resources Data

15...12
Type
11...0
Offset
Биты слова, Type указывает на тип настройки, a Offset на ее смещение внутри 4-килобайтной страницы.

Перечислим возможные типы поправок.



Структура содержащая значения


.

58 При написании главы были использованы материалы с сайта http://win32asm.cjb.net



Таблица директория импорта



Таблица директория импорта

0h Адрес абсолютный и никаких изменений производить не требуется.
1h Добавить старшие 16 битов "Дельты" к 16-битовому полю, находящемуся по смещению Offset. 16-битовое поле представляет старшие биты 32-битового слова.
2h Добавить младшие 16 битов "Дельты" по смещению Offset. 16-битовое поле представляет младшую половину 32-битового слова. Данная запись настройки присутствует только на RISC машине, когда Object align не является по умолчанию 64К.
3h Прибавляет 32-битовое "Дельта" к 32-битовому значению.
4h Настройка требует полного 32-битового значения. Старшие 16-бит берутся по адресу Offset, а младшие в следующем элементе TypeOffset. Все это объединяется в знаковую переменную, затем добавляется 32-битовое "Дельта" и DWORD 8000h. Старшие 16 бит получившегося значения сохраняются по адресу Offset в 16-битовом поле.
5h ?



Таблица настроек адресов



Таблица настроек адресов

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

Формирование данных базовых поправок выглядит следующим образом. Поправки упаковываются сериями смежных кусков различной длины. Каждый кусок описывает поправки для одной четырехбайтовой страницы отображения и начинается со следующей структуры:

Каталог импортаImport Directory Table
Таблица ссылок на имена   LookUp Table
Таблица имен   Hint-Name Table
Таблица адресов импорта   Import Address Table

Смещение

Длина поля

Название поля

Описание поля

Для наложения настройки необходимо вычислить 32-битную разницу ("Дельта") между желаемой базой загрузки и действительной. Если образ программы загружен в требуемое место, то эта разница равна нулю и никакой настройки не требуется. Каждый блок настроек должен начинаться на DWORD-границе, для выравнивания блока можно пользоваться нулями. При настройке необходимую позицию в блоке вычисляют как сумму относительного адреса страницы и базового адреса загруженной программы.



Таблица разделов потоков



Таблица разделов потоков

00h DWORD Page RVA Относительный адрес страницы применения.
04h DWORD Block Size Размер блока настроек (с заголовком). Эта величина используется для вычисления количества настроек.
08h WORD TypeOffset Record Массив записей настроек, их переменное количество.

Смещение

Длина поля

Название поля

Описание поля



Текст программы можно увидеть на экране



Текст программы можно увидеть на экране,

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



Точки останова



Точки останова.

В дизассемблированном тексте можно установить точки останова. Для этого следует перейти к нужной строке и воспользоваться клавишей F2 или использовать левую кнопку мыши при нажатой клавише Ctrl. Установка точки останова в окне дизассемблера тут же отражается в информационном окне и в управляющем окне - у отмеченной команды появляется префикс BP*. Удалить точку останова можно тем же способом, что и при установке. Точку останова можно сделать также неактивной. Для этого нужно обратиться к информационному окну и списку точек останова. Выбрав нужный адрес, щелкните по нему правой кнопкой. При этом "звездочка" у данной точки останова исчезнет, а строка в окне дизассемблера из желтой станет зеленой.



Условные конструкции



1. Условные конструкции.

Неполная условная конструкция.

if (простое условие) { ... ... ... }

если условие простое, то оно, разумеется, заменяется следующей возможной последовательностью

CMP EAX,1 JNZ L1 ... ... ... L1:

Полная условная конструкция.

if (простое условие) { ... ... ... } else { ... ... ... }

CMP EAX,1 JNZ L1 ... ... ... JMP L2 L1: ... ... ... L2:

Вложенные условные конструкции.

Здесь все достаточно очевидно.

CMP EAX,1 JNZ L1 CMP EBX,2 JNZ L1 ... ... ... L1:

Что, конечно, равносильно одному составному условию, связанному союзом "И". Союз "ИЛИ", как известно, заменяется проверкой условий в блоке "ELSE".



Вход в таблицу ресурсов (Resource Entry Item)



Вход в таблицу ресурсов (Resource Entry Item)

00h DWORD Start Data Block VA Виртуальный адрес начала блока данных цепочки.
04h DWORD End Data Block VA Виртуальный адрес конца блока данных цепочки.
08h DWORD Index VA Виртуальный адрес индексной переменной, используемой для доступа к локальному блоку данных цепочки.
0Ch DWORD CallBack Table VA Виртуальный адрес таблицы обратных вызовов. Локальные обратные вызовы - массив виртуальных адресов функций, которые будут вызваны загрузчиком после создания цепочки (нити) и после ее завершения. Последний вход имеет нулевое значение и указывает на конец таблицы.

Смещение

Длина поля

Название поля

Описание поля

Каждый пункт данных (Resource Entry Item) имеет следующий формат:

00h DWORD Name RVA or Res ID Поле содержит либо идентификатор ресурса, либо указатель на его имя в таблице имен ресурсов.
04h DWORD Data Entry RVA or SubDirectory RVA Указывает либо на данные, либо на еще одну таблицу входов ресурсов. Старший бит поля, сброшенный в ноль, говорит, что поле указывает на данные.

Смещение

Длина поля

Название поля

Описание поля



Вид загрузчика SoftIce (LOADER EXE) Окно настройки запуска модуля в отладчике Softice



Рисунок 4.4.11. Окно настройки запуска модуля в отладчике Softice.




Внешний вид программы HIEW EXE


Программа на Рисунок 4.2.3 проста и корректна. Представьте теперь, что при отладке Вы случайно изменили одну команду: вместо JE поставили JNE. В результате поспе трансляции программа перестала работать. Можно исправить ее, не прибегая к ассемблерному тексту? Конечно. Для этого в начале ее следует дизассемблировать, найти ошибку, а потом воспользоваться программой HIEW.EXE. Вообще говоря, можно ограничиться только программой HIEW, так как она вполне корректно дизассемблирует. Однако мы нарочно проведем исправление в два этапа.

Дизассемблируем модуль при помощи программы DUMPBIN.EXE. Вот дизассемблированный текст программы (Рисунок 4.2.4).

Dump of file cons1.exe

File Type: EXECUTABLE IMAGE

00401000: 6A F5 push 0F5h 00401002: E8 2B 00 00 00 call 00401032 00401007: 83 F8 FF cmp eax,0FFFFFFFFh 0040100A: 75 1E jne 0040102A 0040100C: A3 16 30 40 00 mov [00403016],eax 00401011: 6A 00 push 0 00401013: 68 12 30 40 00 push 403012h 00401018: 6A 11 push 11h 0040101A: 68 00 30 40 00 push 403000h 0040101F: FF 35 16 30 40 00 push dword ptr ds:[00403016h] 00401025: E8 0E 00 00 00 call 00401038 0040102A: 6A 00 push 0 0040102C: E8 0D 00 00 00 call 0040103E 00401031: CC int 3 00401032: FF 25 08 20 40 00 jmp dword ptr ds:[00402008h] 00401038: FF 25 00 20 40 00 jmp dword ptr ds:[00402000h] 0040103E: FF 25 04 20 40 00 jmp dword ptr ds:[00402004h]



Внешний вид программы WDasm



Рисунок 4.4.2. Опции отлпдчика


Для начала работы с исполняемым модулем достаточно выбрать нужный файл в меню "Disassembler\0pen File...". После этого программа производит анализ модуля и выдает дизассемблированный текст, а также весьма полную информацию о секциях модуля54. W32Dasm весьма корректно распознает API-функции и комментирует их (см. Рисунок 4.4.3).

После работы с модулем можно создать проект работы при помощи пункта "Disassembler\Save Disassembler...". По умолчанию проект сохраняется в подкаталог "wpjfiles", который расположен в рабочем каталоге W32Dasm и состоит из двух файлов: с расширением "alf" - дизассемблированный текст, с расширением "wpj" - собственно сам проект. При повторном запуске можно открывать уже не модуль, а проект с помощью пункта "Project\0pen...".

* Possible Reference to String Resource ID=00001: "! >>1I5=85" | :00401013 6A01 push 00000001 :00401015 FF3518304000 push dword ptr [00403018]

* Reference To: USER32.LoadStringA, Ord:01A8h | :0040101B E8AE000000 Call 004010CE



Вот как выглядит ассемблерный код



Рисунок 4.5.5.

Вот как выглядит ассемблерный код, полученный с помощью транслятора Borland C++ 5.0.

.text 00401108 _main proc near ; DATA XREF: .data:0040A0B8 .text 00401108 .text 00401108 var_18 = byte ptr -18h .text 00401108 var_C = byte ptr -0Ch .text 00401108 argc = dword ptr 8 .text 00401108 argv = dword ptr 0Ch .text 00401108 envp = dword ptr 10h .text 00401108 .text 00401108 push ebp .text 00401109 mov ebp, esp .text 0040110B add esp, 0FFFFFFE8h .text 0040110E push ebx .text 0040110F xor ebx, ebx .text 00401111 .text 00401111 loc_401111: ; CODE XREF: _main+30 .text 00401111 mov [ebp+ebx+var_C], 61h .text 00401116 mov [ebp+ebx+var_18], 61h .text 0040111B movsx eax, [ebp+ebx+var_18] .text 00401120 push eax .text 00401121 movsx edx, [ebp+ebx+var_C] .text 00401126 push edx ; char .text 00401127 push offset aCC ; _va_args .text 0040112C call _printf .text 00401131 add esp, 0Ch .text 00401134 inc ebx .text 00401135 cmp ebx, 0Ah .text 00401138 jl short loc_401111 .text 0040113A push 0 ; uExitCode .text 0040113C call ExitProcess .text 00401141 pop ebx .text 00401142 mov esp, ebp .text 00401144 pop ebp .text 00401145 retn .text 00401145 _main endp



Второе управляющее окно отладчика



Рисунок 4.4.7. Второе управляющее окно отладчика.


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



VxDдрайвер



I

Для программирования VxD-драйверов нам понадобятся файлы VMM.INC, SHELL.INC, VCOND.INC и др., которые можно найти в пакете DDK (Device Development Kit), свободно распространяемом фирмой Microsoft.

При загрузке Windows программа WIN.COM загружает драйвер VMM32.VXD, называемый Менеджером Виртуальной Машины (Virtual Machine Manager), который инициализирует другие VxD-драйверы. Затем VMM переключает процессор в защищенный режим и инициализирует системную виртуальную машину. Кроме уже сказанного, VMM обеспечивает сервис для других драйверов VXD. При запуске DOS-приложения для него выделяется виртуальная машина, так что приложению "кажется", что оно работает с настоящей машиной. При запуске обычных 32-битных приложений они работают в системной виртуальной машине. Приложения, работающие в разных виртуальных машинах, не подозревают о существовании других виртуальных машин. Одним из главных назначений виртуальных драйверов является обеспечение бесконфликтного коллективного доступа к физической аппаратуре для всех одновременно работающих виртуальных машин. Еще одна задача, которую приходится решать виртуальным драйверам, - это осуществление взаимодействия системной виртуальной машиной с другими виртуальными машинами.

Следует отметить, что в Windows существуют и так называемые обычные драйверы, имеющие расширение DRV и структуру DLL-библиотеки, которые экспортируют API-функции для работы с некоторыми внешними устройствами (например, видеоадаптером). Эти драйверы получают доступ к внешним устройствам не напрямую, а посредством VxD-драйверов. Поскольку драйверы VxD работают в нулевом кольце защиты, они имеют доступ к любому участку памяти и напрямую, посредством портов ввод-вывода, обращаются к внешним устройствам.

Все виртуальные драйверы делятся на два класса - статические и динамические. Статические драйверы загружаются во время загрузки системы и остаются в памяти до выхода из системы. Статические драйверы существовали еще в Windows 3.x. Динамические драйверы могут загружаться и выгружаться системой по мере необходимости. Они в основном используются для обслуживания устройств "Plug and Play" и загружаются менеджером конфигурации. Загрузить динамический виртуальный драйвер можно и из обычного приложения при помощи обычных функций работы с файлами.




Имеется три механизма взаимодействия виртуальных драйверов друг с другом.

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

Формат виртуальных драйверов называется LE-форматом, сокращенно от "linear executable". Данный формат поддерживает наличие как 16-битного, так и 32-битного кода. Это актуально для статических виртуальных драйверов, которые часть работы (инициализация) выполняют в реальном (незащищенном) режиме. В Windows NT драйверы грузятся в защищенном режиме, по этой причине данный формат в этой операционной системе не используется.

Код и данные в файле LE-формата располагаются в сегментах. Ниже мы опишем возможные классы сегментов.

LCODE - код или данные, заключенные в этом коде, не могут сбрасываться системой на диск (paging).

PCODE - код может временно помещаться на диск.

PDATE - аналогично предыдущему, но здесь хранятся данные.

ICODE - здесь располагается код инициализации, после инициализации сегмент удаляется из памяти.

DBOCODE - используется при запуске драйвера "под отладчиком".

SCODE - статические код и данные. Всегда остается в памяти, даже если драйвер выгружается.

RCODE — содержит 16-битный код для инициализации в реальном режиме.

16ICODE - 16-битный код инициализации в защищенном режиме.

MCODE - содержит строки сообщений.

Перечисленные классы сегментов не задаются непосредственно в тексте программы. Сегменты и классы объявляются в DEF-файле. Файл vmm.inc содержит огромное количество макросов, и нам не избежать пользоваться ими. Это позволит материал, который я хочу изложить, вместить в одну главу.


Выполнение команды с переходом через процедуру



Выполнение команды с переходом через процедуру.

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



Выполнение с задержкой



Выполнение с задержкой.

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

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

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



Выполнить единичную команду



Выполнить единичную команду.

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



Выполнить процедуру



Выполнить процедуру.

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



Выполнить программу до строки где находится курсор



Выполнить программу до строки, где находится курсор.

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



Вызов импортируемой функции



Рисунок 4.1.4. Вызов импортируемой функции.


Схема вызова импортируемых функций из РЕ-модуля изображена на Рисунок 4.1.4, который с некоторыми изменениями взят из книги [2]. Смысл данного рисунка заключается в следующем. При компоновке все вызовы API-функций преобразуются к вызову типа CALL Адрес1. При этом адрес, также как и вызов, находится в секции кода (.text). По адресу же стоит команда JMP DWORD PTR [Адрес2]. [Адрес2] находится в секции .idata (импорта) и содержит двойное слово — адрес функции в динамической библиотеке. Современные компиляторы содержат директивы, позволяющие вместо двух вызовов (CALL и JMP) генерировать один CALL [Адрес2].



Загрузка программ для отладки



Загрузка программ для отладки.

Загрузить модуль для отладки можно двумя способами. С помощью пункта "Debug\Load Process" загружается для отладки уже дизассемблированный модуль. Пункт же "Debug\Attach to an Active Process" позволяет "подсоединяться" и отлаживать процесс, находящийся в памяти. После загрузки отладчика на экране появляются два окна. Первое окно информационное (Рисунок 4.4.6), в документации оно называется "нижним левым окном отладчика". Второе окно управляющее (Рисунок 4.4.7), называемое в документации "нижним правым окном отладчика".

+++++++++++++++++++ IMPORTED FUNCTIONS ++++++++++++++++++ Number of Imported Modules = 7 (decimal)

Import Module 001: ADVAPI32.dll Import Module 002: KERNEL32.dll Import Module 003: MPR.dll Import Module 004: COMCTL32.dll Import Module 005: GDI32.dll Import Module 006: SHELL32.dll Import Module 007: USER32.dll

+++++++++++++++++++ IMPORT MODULE DETAILS +++++++++++++++

Import Module 001: ADVAPI32.dll Addr:000D9660 hint(0000) Name: RegCloseKey Addr:000D966B hint(0000) Name: RegOpenKeyExA Addr:000D967B hint(0000) Name: KegQueryValueExA Addr:000D9692 hint(0000) Name: RegSetValueBxA

Import Module 002: KERNEL32.dll



Загрузка программы для отладки



Загрузка программы для отладки.

Для загрузки приложения в отладчик в пакете SoftIce имеется специальная программа LOADER32.EXE. Она используется для загрузки 32-битных приложений. Для загрузки 16-битных приложений используются утилиты, расположенные в подкаталоге Util16.

Итак, вернемся к программе LOADER32.EXE. Вид загрузчика показан на Рисунок 4.4.10. Последовательность действий для загрузки программы в отладчик следующая: открыть модуль, транслировать модуль - преобразование отладочной информации в NMS-файл, загрузить модуль. Если загрузчик разобрался с отладочной информацией, то в окне отладчика появится программный текст, в противном случае Вам придется работать с обычным дизассемблированным текстом. После загрузки модуля, вы можете настроить запуск модуля в отладчике, воспользовавшись пунктом меню Module\Setting (Рисунок 4.4.11). Настройка достаточно очевидна, поэтому мы не будем разъяснять смысл пунктов данного окна.



Запуск отлаживаемого приложения



Запуск отлаживаемого приложения.

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









00h DWORD Data RVA Указатель на реально расположенные данные относительно Image Base.
04h DWORD Size Размер ресурсных данных.
08h DWORD CodePage Кодовая страница.
0Ch DWORD Reserved He используется и устанавливается в ноль.