Минимальное Windows-приложение на ассемблере — Архив WASM.RU

Все статьи

Минимальное Windows-приложение на ассемблере — Архив WASM.RU

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

  1. когда начинающий программист дочитывает до конца первую главу учебника и до него доходит, что большинство известных ему программ умеют нечто большее, чем тупо приветствовать мир. Пытливый ум новичка подсказывает ему, что если встречаются программы, умеющие больше этого, то, значит, где-то есть и программы, умеющие меньше
  2. когда начинающий программист робко, опасаясь быть проигнорированным, замодерированным или посланным, забрасывает вопрос в какую-нибудь конференцию. Последующие события, как правило, приводят его в шок, надолго отбивая охоту приступать к прочтению второй главы. Со всех концов бескрайней саванны мгновенно слетаются и сбегаются бесчисленные гуру, возникает потасовка, из облака пыли доносятся рык, ржание, визги и предсмертные хрипы, разлетаются перья и клоки шерсти. Великие считают и пересчитывают строчки в исходниках, потом буквочки в них же, а потом и байтики в екзешниках. Вопли стихают только после того, когда кто-нибудь предлагает подсчитывать биты, установленные в 1, и все вдруг понимают, почему двери в психушках открываются исключительно вовнутрь. Вывалив языки и тяжко дыша, братия расползается по своим кельям зализывать раны и готовиться к грядущим битвам. Посередине вытоптанной поляны, одинокий, жалкий и забытый, остывает трупик несчастного новичка
  3. когда новичок, отчаявшись познать истину самостоятельно или испить ее из ладоней великих, записывается на компьютерные курсы, и, с трудом досидев на первом занятии до сакраментального "У кого есть вопросы?", тянет руку и получает в ответ краткое изложение всей программы обучения. Затем, сверившись со списком группы и перезвонив в кассу, преподаватель также дает обещание, что к концу курса студент сам сможет элементарно ответить на этот простейший вопрос.

Между тем вопрос о минимальной программе - совсем не простейший, и представляет отнюдь не академический интерес. Минимальная программа, очевидно, решает две задачи: (1)стартует и (2)завершается в конкретной рабочей среде. И то, и другое она должна делать корректно, с тем, чтобы:

  • стартовав, получить нормальный доступ к ресурсам рабочей среды
  • завершившись, оставить рабочую среду работоспособной
    Здесь, пожалуй, уместно напомнить про ограниченность тематики сайта: прикладное программирование на masm под windows. Поэтому дальше речь пойдет не о программах вообще, и не о каких-нибудь VxD, а о приложениях, то бишь applications, непосредственно взаимодействующих с операционной системой.

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

В C/C++, например, запуск и завершение приложения win32 поддерживаются скрытой от прикладного программиста функцией _WinMainCRTStartup, содержащейся в runtime-библиотеке. Именно ее вызывает операционная система при запуске приложения, а уж она, кое-чего поделав, вызывает ту самую WinMain, с которой начинается всякое приложение, базирующееся на win32. (Конечно, строго говоря, запуск приложения посредством вызова _WinMainCRTStartup - это свойство не языка, а операционной среды.) От программиста же требуется, чтобы WinMain была правильно оформлена (включая четыре входных параметра), и завершалась оператором return с кодом выхода типа int.
В языках программирования более высокого уровня для программиста все еще проще, хотя там уже вряд ли можно говорить о том, что приложение-де "непосредственно взаимодействует с операционной системой". Java, еще более высокий, чем C/С++, уже не использует фиксированное название стартовой функции, да и завершение программы в нем - это просто закрывающая фигурная скобка.

Однако, вернемся к ассемблеру. Вот обещанный текст минимального приложения для win32:

.386
.model flat,stdcall
ExitProcess PROTO :DWORD
.code
WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show
 ;...
 invoke ExitProcess,0
WinMain ENDP
end

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

Что здесь что и зачем?

  • .386 - директива ассемблера, определяющая набор команд процессора, который может быть использован в программе. Для приложений win32 необходимо использовать эту директиву или выше, в зависимости от того, собираетесь ли вы использовать возможности, предоставляемые процессорами последующих поколений
  • .model flat,stdcall - сегментная директива ассемблера, определяющая сегментную модель приложения как плоскую, использующую соглашения о вызове процедур, принятые в win32. Именно такая сегментная модель должна всегда использоваться при написании приложений для win32 ExitProcess PROTO :DWORD - прототип функции API, выполняющей завершение приложения. (Поскольку сервис этой функции предоставляется dll-библиотекой kernel32.dll, то при сборке приложения хорошо бы не забыть подключить библиотеку импорта kernel32.lib)
  • .code - сегментная директива ассемблера, определяющая начало сегмента кода
  • WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show - начало тела стартовой процедуры. Следует обратить внимание на наличие директивы видимости (visibility) PUBLIC, которая позволит сборщику сделать процедуру доступной для операционной системы, дабы та смогла передать управление приложению. Параметры процедуры обсудим чуть ниже, правда, новость будет не очень приятной.
  • ;... - здесь должна быть всякая прочая мелочь, которую вы хотели бы заставить делать ваше приложение
  • invoke ExitProcess,0 - собственно вызов функции завершения приложения. Здесь код выхода 0, но он, естественно, может быть любым в пределах 32-разрядного целого. В полноценных приложениях нормой считается определение кода выхода в цикле обработки сообщений главного окна.
  • WinMain ENDP - всякое тело должно иметь конец. Это он и есть. Правда, в силу наличия предыдущей строки, он в данной программе не достижим, но это и не требуется: здесь мы просто исполняем необходимую формальность. Кстати, настоящим ассемблерщикам должно быть приятно, что, опустив ненужную в связи с этим команду ret, мы сэкономили аж 4 байта кода!
  • end - конец модуля.

Замечание 1. По поводу необходимости применения функции ExitProcess. Именно с ее помощью, а не посредством команды ret, как могли бы ожидать знатоки C/C++, должно завершаться приложение win32, написанное на ассемблере.

Если залезть внутрь runtime-библиотеки C/C++, то мы увидим, что именно так завершает работу приложения уже упоминавшаяся функция _WinMainCRTStartup. Но поскольку подключать runtime-библиотеку C/C++ к ассемблерной программе как-то нелогично (хотя и вполне возможно), мы должны вызвать ExitProcess "вручную". Только эта функция корректно завершает работу приложения, в частности, оповещая использовавшиеся приложением библиотеки DLL о необходимости декрементировать их счетчики занятости и, при обнулении последних, выгрузиться из памяти.

Замечание 2. А вот и обещанная плохая новость про параметры стартовой процедуры. Дело в том, что установкой их значений занимается - кто бы вы думали? - опять же _WinMainCRTStartup! И следовательно, в ассемблерных программах, где ее нет, при входе в WinMain параметры содержат:

  • hinst - не дескриптор текущего экземпляра приложения, а количество акций Microsoft у Билла Гейтса
  • prev_hinst - не 0, а прогнозируемое значение NASDAQ после выхода на мировой рынок российской ОС BrokenWindows
  • command_line - не указатель на командную строку, а традиционный народный российский указатель направления
  • cmd_show - не предлагаемый начальный вид окна, а то, что обещал показать мировому сообществу Никита Сергеевич Хрущов (и даже, помнится, начал раздеваться)

Так что, воленс-ноленс, придется получать эту ценную информацию самостоятельно. Как говорится, клик хере.

Замечание 3. И все-таки приведенный текст приложения - не самый минимальный. Можно сделать его еще меньше, для чего следует убрать все параметры функции WinMain. Поскольку приложение собирается без runtime-библиотеки C/C++, это вполне допустимо, так как больше не с чем выполнять ее связывание. В результате объем исполняемого файла останется тем же, а вот объем исполняемого кода в нем (образа приложения) сократится аж до 7 байт. Про это стоит почитать еще.

Замечание 4. В этой статье рассматривается минимизация размера кода программы, но отнюдь не exe-файла, ее содержащего. Как известно, исполняемые файлы Windows обычно имеют так называемый PE-формат, и манипуляции с этим форматом дают некоторые возможности для сокращения размера файла. Например, можно вместо стандартной stub-программы использовать более компактную, собственной разработки.

2002-2013 (c) wasm.ru