assembler. Учебник для вузов_Юров В.И_2003 -637с (862834), страница 84
Текст из файла (страница 84)
Теоретически, на его основе впоследствии можно сформировать функционально эквивалентный исполняемый модуль.Более того, если задаться такой целью, то не составит большого труда переделатьего для обработки транслятором TASM 5.0.Но не все так просто. Готов расстроить читателя, но мы изложили очень упрощенный подход к разработке Windows-приложения на ассемблере.
Полученныйкод можно рассматривать лишь как схему (шаблон) такого приложения. Реальновсе несколько сложнее. Прежде чем мы объясним имеющиеся проблемы, проведем еще один эксперимент — дизассемблируем исполняемый модуль программыprg!6_l.cpp. Причем сделать это нужно тем дизассемблером, который «понимает»интерфейс Win32 API. В данной книге для этой цели был использован дизассемблер IDA. Дизассемблированный с его помощью файл можно сохранить как листинг (.1st) и как исходный текст ассемблера (.asm). Это говорит о том, что IDAтакже пытается по загрузочному модулю построить файл, пригодный для повторной трансляции.
В начале дизассемблированного им текста даже приводятся рекомендации по использованию соответствующих параметров транслятора TASM.В контексте нашего изложения наибольший интерес представляет файл листинга (.1st), формируемый IDA. Анализ его содержимого позволяет изнутри посмотреть на результаты процесса формирования исполняемого модуля транслятором и редактором связей. Файл листинга в своей левой части содержит колонкус адресными смещениями команд.
Все метки и символические имена в дизассемблированном тексте формируются с учетом этих смещений, поэтому данный файлудобно использовать для анализа.Файл листинга IDA имеет большой размер, поэтому привести весь его текств книге невозможно, да это и не нужно. Для нас интерес представляют лишь отдельные фрагменты этого файла (листинг 16.3). Полностью Дизассемблированныйкод программы ргд!б_1.срр находится среди фалов, прилагаемых к книге.Листинг 16.3. Фрагменты дизассемблированного кода каркасногоWindows-приложения (дизассемблер IDA)Этот файл сгенерирован интерактивным дизассемблером(ЮА)Copyright (с) 1997 by DataRescue sprl, <ida@datarescue.com>Каркасное Windows-приложение на C/C++37500401000 ;этот файл должен компилироваться следующей командной строкой: TASM/ml/m500401000р38600401000model flat00401000_WinMain@16 proc near;CODE004010000040100100401003004010060040100700401008pushmovsubpushpushpushebpebp, espesp, 50hebxesiedi00401037004010390040103F004010420040104700401049pushcallmovpushpushcall0ds: LoadlconA[ebp+var 38] , eax7F00h0ds : LoadCursorA0040107200401075004010760040107Cleapushcallmovzxeax, [ebp+var_50]eaxds:RegisterClassExAeax, ax0040108E0040108E00401090004010930040109400401096004010980040109D004010A2004010A7004010AC004010B1004010B6004010BB004010BD004010CE00401004004010D7004010D8004010DE004010DE004010DE004010E0004010E2004010E4004010E7004010E8004010EE004010F0004010F6004010F9004010FA0040110000401103004011040040110AXREF: start+146_ploc_40108E::;CODEXREF: WinMain@16+81 jpush0eax, [ebp+arg_0]movpusheaxpush0push0push80000000Пpush80000000hpush80000000Пpush80000000hpush0CF0000hpushoffset unk 404034pushoffset unk 404020push0caUds:CreateWindowExAcallmovpushcallds : ShowWi ndoweax, [ebp+var_4]eaxds:llpdateWindowloc_4010DE :; CODE XREF: WinMain@16+10Apush0push0push0eax, [ebp+var_20]leapusheaxds :GetMessageAcalltesteax, eaxloc_40110Fjzeax, [ebp+var_20]leapusheaxds:TranslateMessagecalleax, [ebp+var_20]leapusheaxds :DispatchMessageAcallloc_4010DEjmpjпродолжение •376Глава 16.
Создание Windows-приложений на ассемблереЛистинг 16.3 (продолжение)00401117 loc_401117:;CODE00401117popedi00401118popesi00401119popebx0040111Aleave0040111Bretn10h0040111B_WinMain@16 endpXREF:_WinMain@16+89_j;оконная процедура;CODE XREF: . text:00401163_]00401132 loc_401132:push00040113200401134callds:PostQuitMessagejmploc_40116E0040113A0040114E0040114Fpushcalleaxds:DefWindowProcA0040117500401176004011770040117800401179poppoppopleaveretnediesiebx10h0040118000401180 public start00401180 start proc near004011A6004011A6call ds:GetVersion ;получить текущую версию Windows;и информацию о текущей операционной платформе004012050040120В00401210callmovcallds:GetCommandLineAds:dword_4050B0, eax_crtGetEnvironmentStringsA004012A0callds:GetStartupInfoA004012BF004012C5004012C6004012CB004012CCcallpushcallpushcalldsrGetModuleHandleAeax_WinMain@16eax_exit0040130Epopesi0040130Fpopebx00401310movesp, ebp00401312popebp00401313retn00401313 start endp;sp = -88h0040131300401380_exit proc near ;CODE00401380 ; start+14C_p00401380XREF: start+A9_p00401389callsub_4013C0;doexit00401391_exitendp004013C0 ;п о д п р о г р а м м а doexit004013C0 ; Атрибуты: Статическая библиотечная функцияКаркасное Windows-приложение на C/C++G04013C0 sub_4013C0 proc near; CODE004013D1004013D700401308callpushcallds:GetCur rent Processeaxds: Terminate Process0040145800401459pushcallesids: Exi tProcess377XREF:_exit+9_p00401462retn00401462 sub_4013C0 endp0040610C ; Импорт из KERNEL32.dll0040610C0040610Cextrn GetModuleFileNameA:dword0040610C;_NMSG_WRITE+77_r; DATA XREF:_setargv+12_r00406188 Импорт из USER32.dll00406188extrn PostQuitMessage:dword ;DATA XREF: .text:00401134_r0040618Cextrn DispatchMessageA:dword;DATA XREF:_WinMain@16+104_r004061B4end startТекст листинга 16.3 дает богатую пищу для размышлений.
Его обсуждение нужно начать с последней строки. По правилам ассемблера, в последней директивепрограммы END должна стоять метка первой исполняемой команды. В нашем случае это метка start. Если теперь посмотреть на место в тексте, куда она ссылается,то мы увидим, что в данной строке содержится директива PROC, обозначающая начало процедуры с именем start. Можно сделать вывод, что исполнение программыначинается именно с этого места. Обратите внимание на то, что в теле процедурыstart содержатся многочисленные вызовы функций.В листинге 16.3 показаны только вызовы функций API, но если вы посмотритеполный вариант листинга 16.3, находящийся среди файлов к книге, то увидите,что в нем содержатся вызовы множества других функций. Названия этих функций начинаются символом подчеркивания (_), в отличие от названий функцийAPI, которым предшествует префикс переопределения сегмента, например:ds:GetVersion.
Задержимся немного и посмотрим внимательно на листинг 16.1, в частности, нас интересуют функции, к которым идет обращение. Видно, что в исходном тексте Windows-приложения, написанном на C/C++, нет и намека на какиелибо функции, за исключением функций API. Более того, вызов многих из функцийAPI, которые присутствуют в дизассемблированном тексте, отсутствует в листинге 16.1.
Отсюда следует естественный вывод о том, что их вставил компиляторC/C++ и, очевидно, они предназначены для инициализации среды, в которой будет функционировать программа. А так ли они необходимы? Забегая вперед, скажем, что нет. Но и отказаться от них нельзя, так как они фактически «навязываются» нам компилятором C/C++. В этом и состоит одна из главных причин того, чтоисполняемые модули на ассемблере по размеру в несколько раз меньше функционально эквивалентных модулей на языках высокого уровня.
Можно сказать, чтоприложение, написанное на ассемблере, не содержит избыточного кода. Отметьтееще один характерный момент относительно функций API. Кодовый сегмент исполняемого модуля содержит только команды дальнего перехода к этим функциям. Сами же функции находятся в отдельном файле — библиотеке DLL. В конце378Глава 16. Создание Windows-приложений на ассемблерелистинга 16.3 содержится таблица, в которой описано местонахождение всех импортируемых функций API.Проследим за действиями, которые производит код исполняемого модуля, формируемый компилятором Visual C++ для инициализации Windows-приложения.Для этого откройте файл prg!6_l.lst с дезассемблированным текстом программына C/C++, находящийся среди прилагаемых к книге файлов, и переместитесь наначало процедуры start.1.
Вызов функции API GetVersion 1 для определения текущей версии Windows и некоторой другой информации о системе. В настоящее время существует болееинформативная версия данной функции — GetVersionEx.2. Инициализация кучи. Используется как для динамического выделения памяти функциями языков C/C++, так и для организации ввода-вывода на низкомуровне.3. Вызов функции API GetCommandLineA для получения указателя на команднуюстроку, с помощью которой было вызвано данное приложение.4.
Вызов функции API GetEnvironmentStringsA для получения указателя на блокс переменными окружения.5. Инициализация глобальных переменных периода выполнения.6. Вызов функции API GetStartupInfoA для заполнения структуры, поля которойопределяют свойства основного окна приложения.7. Вызов функции API GetModuleHandleA для получения базового адреса, по которому загружен данный исполняемый модуль.8. Вызов функции WinMain. Как можно судить по способу вызова и внутреннемуназванию, _WinMain@16 не является функцией API.
Это локальная функцияданного исполняемого модуля. Территориально она расположена в начале дизассемблированного текста (см. листинг 16.3). Позже обсудим ее подробнее.Главный вывод приведенных рассуждений — текст процедуры start исполняемого модуля не соответствует исходному тексту программы на C/C++. В него добавлены локальные функции и функции Win32 API, которые вызываются в исполняемом модуле, например GetVersion, GetCommandLineA и т. д. Это и есть такназываемый стартовый код программы.