Ассемблерное программирование в Windows

24.05.2004
Новиков Максим Глебович.

Вступление

Ассемблер позволяет создавать небольшие и эффективные программки не только в среде MS-DOS. В современной операционной системе Windows ассемблер тоже может помочь вам создать программу, занимающую гораздо меньше места на диске и в памяти, чем ее аналог, созданный, к примеру, с помощью C++ или Delphi.

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

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

В этой статье я постараюсь дать введение в программирование на ассемблере для Windows, а также указать на конкретные программы, имеющиеся в Интернете, которые будут необходимы вам для программирования. Если вы ещё незнакомы с внутренней архитектурой компьютера, то изучите вначале мою статью «ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ АССЕМБЛЕРА ПОД MS-DOS» — там я достаточно подробно и довольно доступно описал элементы компьютера, понимание работы которых необходимо для программирования на ассемблере вообще.

Глава 1. Программное обеспечение

Прежде всего вам надо установить собственно ассемблер. Существует множество разных ассемблеров, но для программирования в Windows больше подходит MASM — макроассемблер от Microsoft. Мой любимый TASM в среде Windows даёт, к сожалению, менее оптимальный код.

На момент написания статьи последней версией пакета MASM32 была версия 8.2, содержащая MASM 6.14. Её cкачаем отсюда: http://www.masm32.com. Можно еще залезть сюда http://www.win32asm.cjb.net и скачать MASM 6.15 и MASM Patch. После инсталляции MASM32 8.2 замените входящую в его комплект версию 6.14 ml.exe на 6.15, а потом пропатчите всё это дело.

В принципе, для создания программ больше ничего не надо. Но я настоятельно рекомендую вместо входящего в комплект редактора исходного текста «MASM32 Editor» скачать себе RadASM — интегрированную среду разработки. Прежде всего эти программы различаются тем, что во встроенном в RadASM редакторе есть настраиваемая и очень гибкая подсветка синтаксиса. Плюс к этому RadASM организовывает все ваши файлы в единый проект. Также для компиляции и сборки исполнимого файла вам нет надобности прописывать командные строки компилятора и линковщика — в RadASM уже всё прописано — он сам вызывает эти программы с нужными опциями. Кроме этих удобных дополнений в программе предусмотрено много других дополнительных функций.

RadASM качаем отсюда: http://radasm.cherrytree.at.

Настройка RadASM весьма проста, кроме того, он снабжен толковым хелпом. Единственное, что я там сделал — это с помощью блокнота в разделе [Paths] файла «masm.ini» изменил путь к каталогу MASM32 (он у меня на диске «D:» стоит, а не на «C:», как было жестко там прописано), а в разделе [MenuHelp] файла «RadASM.ini» изменил меню помощи, прописав туда все файлы помощи из MASM32. Еще я изменил расцветку редактора через меню настройки. Поставил глубокий синий для фонов всех окон, и немного упростил подсветку синтаксиса, чтобы не так пёстро выглядела. Если вы хотите воспользоваться моей расцветкой, впишите следующий текст БЕЗ пробелов и переносов (в одну строку) в раздел [Color] файла «RadASM.ini»:

3=Samovar,4194304,12632256,8421504,16777215,5395026,12644544,6052956,12632256,8421504,
4194304,4194304,12632256,4194304,12632256,4194304,12632256,8421376,8421504,65280,65535,
16777215,12632256,12632256,16744448,12615935,12615808,11184640,16777215,16777215,
33488896,16711808,285212671,16777215,255,8421376,33023

а затем в RadASM через меню «Option → Colors & KeyWords» загрузите палитру «Samovar».

И последнее — через меню «Option → Code Editor Font» я поставил моноширинный шрифт «Lucida Console». На мой взгляд, это наиболее удачный из общеизвестных шрифт, как нельзя кстати подходящий для программирования на ассемблере — он читабелен и все его символы имеют одинаковую ширину, что незаменимо при выравнивании команд не табуляцией, а пробелами. Он же применен мной и в приведенных ниже исходниках, так что при копировании вы не столкнетесь с проблемами форматирования.

Глава 2. Первая программа

Запустим RadASM и организуем новый проект «File → New Project», выберем ассемблер — «masm», тип проекта — «win32 App», имя проекта — «hello», описание проекта — «вывод окна приветствия» (описание можно и не вводить). Выберем папку, в которой будут располагаться файлы проекта и нажмем «Далее». В окне выбора шаблона оставим по умолчанию «none», в создании папок и файлов тоже все оставим без изменений и нажмем «Далее». В открывшемся окне сделаем небольшое дополнение к строчке «Compile RC». После «RC.EXE» добавим опцию /l419 (строчная буква «L»). Она включит поддержку русского языка в ресурсах. Нажимаем «Готово». Проект организован, и его файлы отображаются справа в браузере проекта. Откроем основной файл проекта. Впишем туда следующий текст (можно его скопировать прям отсюда):

includelib \masm32\lib\user32.lib     ;Подключим библиотеки
includelib \masm32\lib\kernel32.lib
extrn __imp__MessageBoxA@16:dword     ;Сообщим компилятору, что такие функции
extrn __imp__ExitProcess@4:dword      ;существуют во внешних файлах 
                                      ;(в библиотеках)
               
.386                                  ;Пишем код в командах процессора 80386
.model flat                           ;Плоская модель памяти
               
.data                                 ;Сегмент данных 
               
hello_title db "Окно приветствия",0   ;Строка байт содержит коды символов 
                                      ;заголовка окна
hello_message db "Привет, Windows!",0 ;Строка байт содержит коды символов 
                                      ;содержимого окна

.code                          ;Сегмент кода
               
_start:                        ;Пометим начало программы
    push 0                     ;Загрузим 4-ый параметр нулём (нет иконки в окне)
    push offset hello_title    ;Загрузим 3-ой параметр (название окна)
    push offset hello_message  ;Загрузим 2-ий параметр (текст в окне)
    push 0                     ;Загрузим 1-ый параметр нулём(идентификатор предка)
    call __imp__MessageBoxA@16 ;Вызовем функцию вывода окна
    push 0                     ;Загрузим параметр нулём
    call __imp__ExitProcess@4  ;Выполним функцию завершения программы
end _start                     ;Завершим программу.

Теперь нажимаем «Make → Go». После компиляции проекта запустится наша программа и на экране появится её окошко с надписью «Привет, Windows». Если компилятор в нижнем окне сообщает об ошибках, прочитайте внимательно об их причинах. Но скорее всего вы допустили ошибку в исходном коде, или RadASM не может найти корневую директорию MASM32, как было в моём случае. Проверьте настройки в ini-файлах RadASM'а.

Теперь разберемся, что же мы написали:

includelib \masm32\lib\user32.lib   ;Подключим библиотеки
includelib \masm32\lib\kernel32.lib ;
extrn __imp__MessageBoxA@16:dword   ;Сообщим компилятору, что такие функции
extrn __imp__ExitProcess@4:dword    ;существуют во внешних файлах (в библиотеках)

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

Последние две строчки — объявление функций. Этим мы сообщаем компилятору ассемблера, чтобы он не искал эти функции в нашем исходнике, а поверил, что они внешние, т.е. что они есть в подключенных нами (в первых двух строчках) библиотеках. Впоследствии, при выполнении программы, функции будут вызываться не из этих библиотек, служащих компилятору вспомогательными средствами, а из одноименных копий этих библиотек в каталоге «Windows/system32», имеющих расширение «dll». То есть сам код этих функций в нашу программу включён не будет — в процессе работы наша программа будет вызывать эти функции из dll-файлов каталога «Windows/system32».

Истинные имена, по которым числятся функции в Windows, имеют следующий формат: сначала идет приставка «__imp__», затем имя функции, затем «@» и количество байт, занимаемых параметрами, передаваемыми функции. По этим именам мы и будем их вызывать. Есть и другой способ вызова функций, но он даёт менее оптимальный код.

.386        ;Пишем код в командах процессора 80386
.model flat ;Плоская модель памяти

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

Вторая строчка указывает компилятору используемую нами модель памяти. В Windows мы будем работать со всей памятью как с одним непрерывным сегментом размером 4 Гигабайта, включающим в себя все виртуальные сегменты (и сегмент данных, и сегмент кода и т.п.). Здесь не будет разбивки на 64-килобайтовые сегменты, с которыми мы работали в DOS'е.

.data                                 ;Сегмент данных

hello_title db "Окно приветствия",0   ;Строка байт содержит 
                                      ;коды символов заголовка окна
hello_message db "Привет, Windows!",0 ;Строка байт содержит 
                                      ;коды символов содержимого окна

Ну тут всё понятно. Первая строка объявляет сегмент данных. Далее две строковые константы размерностью 1 байт на символ, заканчивающиеся нулевым байтом в качестве кода конца строки.

.code    ;Сегмент кода
                 
_start:  ;Пометим начало программы

Объявляем сегмент кода и метим начало программы. При завершении программы нам придется использовать эту метку, как и в программах для DOS.

push 0                     ;Загрузим 4-ый параметр нулём (нет иконки в окне)
push offset hello_title    ;Загрузим 3-ой параметр (название окна)
push offset hello_message  ;Загрузим 2-ий параметр (текст в окне)
push 0                     ;Загрузим 1-ый параметр нулём (идентификатор предка)
call __imp__MessageBoxA@16 ;Вызовем функцию вывода окна

Параметры передаются функции через стек — специальную область памяти в программе для временного сохранения данных. Первые четыре строчки — мы заполняем стек четырьмя параметрами, в числе которых — адреса (а точнее смещение относительно адреса текущей команды) строковых констант. Стек можно сравнить с магазином автомата Калашникова — у него есть только один выход, он же вход. Патрон, заряженный последним, выстрелит первым. С другого конца магазина или из середины патрон достать нельзя. Также и данные в стеке. Для «заряжания» стека используется команда «push», для вытаскивания данных из стека — команда «pop» (именно этой командой функция вытаскивает данные). Чтобы функция получила параметры в правильном порядке, «заряжать» их надо, начиная с последнего.

После засовывания в стек всех требующихся функции параметров мы осуществляем её вызов. Если мы разделим число 16 из имени функции на количество параметров, то получим размерность параметров (4 байта). Вызванная нами функция осуществляет вывод на экран окна сообщения с переданным ей текстом.

push 0                    ;Загрузим параметр нулём
call __imp__ExitProcess@4 ;Выполним функцию завершения программы

Аналогично вызываем другую функцию, которая завершает программу.

end _start                ;Завершим программу.

Объявим компилятору, что программа закончена.

[Вернуться в начало]

Глава 3 [Оставить отзыв в гостевой]
Hosted by uCoz