Print This Post MASM32: Начало

Суббота, 21. Август 2010
Раздел: Assembler, Windows, Для новичков, автор:

Решил начать цикл статей по ассемблеру, и, в частности, по MASM32. Пригодятся эти мануалы с примерами тем, кто хочет поднять свои навыки программирования и развить умение программировать на ассемблере. Пакет MASM32 - это не просто голый ассемблер. В нём есть огромное множество облегчающих разработку софта вещей - пользовательские макросы, встроенные функции и макросы, дебаггер и прочее, и обо всём этом я буду рассказывать. Конечно, читать такие статьи будет гораздо легче тем, кто уже умеет программировать на каком-нибудь языке. Если вы программируете на каком-нибудь говне вроде Visual Basic, или фанатеете от перетаскивания компонентов и кнопочек на формы в дельфи или Borland C++ - не расстраивайтесь, я расскажу, как можно перетаскивать кнопочки и в ассемблере. Разумеется, никаких стандартных облегчающих жизнь компонентов здесь не будет, но это побудит разобраться с WinAPI - огромной кладезью полезных функций, которые способны делать всё, начиная от чтения данных из сокета и заканчивая отображением окон.

Собственно, эту статью я начну с примера простого GUI-приложения на MASM. Конечно, проектировать дизайн окна мы будем визуально (я же обещал). Для этого сначала следует скачать визуальный редактор ресурсов ResEd. Запускаем его и видим интерфейс:

Создаем новый файл ресурсов (File - New Project и вводим имя). В правой верхней панели появляется значок папки и имя файла. Кликаем по ней правой кнопкой и нажимаем "Add Dialog". Теперь мы можем визуально спроектировать интерфейс окна и изменить его настройки. Я создал простое окно TEST_DIALOG с двумя кнопками TEST_BTN и EXIT_BTN:

Если вы успели обрадоваться - не спешите: здесь нельзя программировать, можно только делать дизайн интерфейса. Теперь необходимо добавить в наш файл ресурсов еще пару вещей. Первое - это include-файл с определениями всех констант, который будет необходим компилятору ресурсов MASM32. Как и раньше, нажимаем правой кнопкой мыши по значку папки, выбираем "Include file", "Add" и вводим путь. У меня это C:\masm32\include\RESOURCE.H. У вас может быть и другой, зависит от папки установки masm32 (как? вы еще не установили его?).

Теперь еще одна вещь. Пусть наше окно и кнопки выглядят в современном XP-стиле. Для этого необходимо добавить к файлу ресурсов XP Manifest. Добавляется он аналогично предыдущим пунктам (Add XP Manifest).

Теперь сохраняем файл ресурсов. Он должен выглядеть примерно таким образом:

#define MANIFEST 24
#define TEST_DIALOG 1000
#define TEST_BTN 1001
#define EXIT_BTN 1002
#define IDR_XPMANIFEST1 1
 
#include "C:/masm32/include/RESOURCE.H"
 
TEST_DIALOG DIALOGEX 6,6,134,51
CAPTION "Test Dialog"
FONT 8,"MS Sans Serif",0,0,0
STYLE WS_VISIBLE|WS_CAPTION|WS_SYSMENU
BEGIN
  CONTROL "Тест",TEST_BTN,"Button",WS_CHILD|WS_VISIBLE|WS_TABSTOP,6,18,54,13
  CONTROL "Выход",EXIT_BTN,"Button",WS_CHILD|WS_VISIBLE|WS_TABSTOP,72,18,54,13
END
 
IDR_XPMANIFEST1 MANIFEST "xpmanifest.xml"

Осталось написать программу. Я в качестве редактора предпочитаю обычный Блокнот Windows. Сначала я приведу полный листинг, а потом прокомментирую его построчно:

.386
.model flat, stdcall
option casemap :none
 
include \masm32\include\windows.inc
include \masm32\macros\macros.asm
uselib kernel32, user32, masm32, comctl32
 
WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
 
TEST_DIALOG = 1000
TEST_BTN = 1001
EXIT_BTN = 1002
 
.data?
  hInstance dd ?
  hWnd dd ?
  icce INITCOMMONCONTROLSEX <>
 
.code
  start:
    mov icce.dwSize, SIZEOF INITCOMMONCONTROLSEX
    mov icce.dwICC, ICC_DATE_CLASSES or \
                    ICC_INTERNET_CLASSES or \
                    ICC_PAGESCROLLER_CLASS or \
                    ICC_COOL_CLASSES
 
    invoke InitCommonControlsEx, offset icce
 
    invoke GetModuleHandle, NULL
    mov hInstance, eax
 
    invoke DialogBoxParam, hInstance, TEST_DIALOG, 0, offset WndProc, 0
 
    invoke ExitProcess,eax
 
WndProc proc hWin :DWORD, uMsg :DWORD, wParam :DWORD, lParam :DWORD
  switch uMsg
    case WM_INITDIALOG
      invoke SendMessage, hWin, WM_SETICON, 1, FUNC(LoadIcon, NULL, IDI_ASTERISK)
 
    case WM_COMMAND
      switch wParam
        case TEST_BTN
          invoke MessageBox, hWin, chr$("Hello, world!"), chr$("Test"), 0
        case EXIT_BTN
          jmp exit_program
      endsw
    case WM_CLOSE
      exit_program:
      invoke EndDialog, hWin, 0
 
    endsw
 
  xor eax,eax
ret
WndProc ENDP
 
end start

Итак, начнем:

.386
.model flat, stdcall
option casemap :none

Эти директивы говорят о том, что мы пишем код под 386 архитектуру процессора (это так и будет всегда), вторая говорит о том, что модель памяти мы используем плоскую и вызовы функций по стандарту stdcall. Этот стандарт подразумевает, что аргументы функциям передаются через стек в обратном порядке, и функция сама должна удалять их оттуда. Кроме того, функции сохраняют регистры ebx, edi и esi и возвращают значение в регистре eax. Если вы сейчас ничерта не поняли - не расстраивайтесь, это всё прекрасно разъяснено в гугле - и про регистры, и про стек. Если вы занимаетесь программированием, то понять это не составит труда.

include \masm32\include\windows.inc
include \masm32\macros\macros.asm
uselib kernel32, user32, masm32, comctl32

Здесь мы подключаем необходимые библиотеки. kernel32 содержит функцию ExitProcess, user32 - всякие GUI-функции, comctl32 - функции работы с common controls, masm32 - библиотека встроенных функций masm32, я не знаю, зачем я ее здесь подключил, потому что она все равно в этом простом проекте не используется. Ну, лишнего объема, как в дельфи, это не добавит, если функции из библиотеки не используются. Я расскажу о ней в будущем. uselib - это макрос masm32, который всё необходимое позволяет одной строкой подключить. Только представьте, эти три строки эквивалентны следующему коду:

include \masm32\include\windows.inc
include \masm32\macros\macros.asm
 
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\masm32.inc
include \masm32\include\comctl32.inc
 
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\comctl32.lib

Как узнать, из какой библиотеки функция? Смотреть msdn.

Идем дальше...

WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

Эта строка представляет собой прототип процедуры. Если вы программируете на C или C++, то знаете, что это такое, если нет - я поясню. Функцию нельзя вызывать до того, как она будет объявлена, поэтому в начало файла часто пишутся прототипы функций, расположенных в других файлах или ниже места первого вызова функции.

TEST_DIALOG = 1000
TEST_BTN = 1001
EXIT_BTN = 1002

Этими строками мы просто объявили некоторые значения из нашего файла ресурсов new.rc. Можно было бы этого и не делать, но с ними программа будет более читаемой.

.data?
  hInstance dd ?
  icce INITCOMMONCONTROLSEX <>

В этом куске кода у нас объявляются глобальные переменные в секции неинициализированных данных. Что это такое? Это просто переменные, не имеющие начального значения. Они не занимают места в получающемся после компиляции exe-файле. В hInstance мы будем хранить указатель на модуль нашей программы (зачем - позже поясню), а в icce - структуру INITCOMMONCONTROLSEX (также объясню позже). dd - он же DWORD - тип данных "двойное слово". В C++ такой тип имеют int, long и все указатели, но C и C++ являются более типизированными языками, а в ассемблере всё сводится к двойным словам (4 байта).

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

.data
  vasya dd 0
  some_string db "hello, world", 0 ;строковая переменная, состоящая из db - байтов (он же BYTE)

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

.code
  start:
    mov icce.dwSize, SIZEOF INITCOMMONCONTROLSEX
    mov icce.dwICC, ICC_DATE_CLASSES or \
                    ICC_INTERNET_CLASSES or \
                    ICC_PAGESCROLLER_CLASS or \
                    ICC_COOL_CLASSES
 
    invoke InitCommonControlsEx, offset icce
 
    invoke GetModuleHandle, NULL
    mov hInstance, eax
 
    invoke DialogBoxParam, hInstance, TEST_DIALOG, 0, offset WndProc, 0
 
    invoke ExitProcess, eax

Что же происходит здесь? Здесь мы уже объявляем секцию исполняемого кода и метку start, которую потом объявим точкой входа.
Мы инициализируем объявленную ранее структуру icce и вызываем функцию InitCommonControlsEx. Инструкция mov загружает данные в регистр или ячейку памяти. Представьте, что мы пишем

    icce.dwSize = SIZEOF(INITCOMMONCONTROLSEX);
    icce.dwICC = ICC_DATE_CLASSES |
                    ICC_INTERNET_CLASSES |
                    ICC_PAGESCROLLER_CLASS |
                    ICC_COOL_CLASSES;
 
    InitCommonControlsEx(&icce);

... и всё станет понятнее. Встроенный макрос invoke используется для вызовы любых функций, у которых есть прототип (а прототипы всех WinAPI прописаны в заголовочных файлах MASM32, которые мы подключили в самом начале программы). Sizeof возвращает размер структуры в байтах, offset позволяет получить смещение в памяти какого-либо байта. Есть еще addr, позволяющая получить смещение какого-то байта, размещенного в памяти по заранее неизвестному адресу (например, для локальных переменных в процедурах).

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

    hInstance = GetModuleHandle(NULL);

Все stdcall-функции возвращают значение в регистре eax, как я уже говорил, а GetModuleHandle как раз stdcall WinAPI. Ах да, у вас, вероятно, есть вопросы по этим функциям, если вы впервые слышите про WinAPI? Ну так вбейте название непонятной функции в гугл, и получите ссылку на msdn с подробнейшим описанием.

И, как вы уже могли догадаться, мы создаем диалоговое окно функцией DialogBoxParam с указанием идентификатора диалога из файла ресурса (TEST_DIALOG = 1000). Эта функция начинает цикл сообщений windows с использованием функции WndProc, на которую мы передали указатель. Это типизированная функция, далее я опишу ее, но пока что - пара слов о цикле сообщений. Каждое окно в Windows получает множество сообщений от системы или других приложений, от других окон или от своего же в непрерывном цикле. Процедура WndProc будет эти сообщения получать, а мы будем в ней обрабатывать часть сообщений. которые нужны нам.

Далее я распишу код с комментариями:

;WndProc - это процедура, которая принимает 4 параметра
;hWin - хендл окна, которому передается сообщение
;uMsg - тип сообщения
;wParam и lParam - по сути, дополнительные данные,
;различные для каждого сообщения
WndProc proc hWin :DWORD, uMsg :DWORD, wParam :DWORD, lParam :DWORD
  ;switch - такой же, как во всех языках. Это макрос MASM32, удобно, не так ли?
  switch uMsg
    ;WM_INITDIALOG отсылается диалогу 1 раз - когда форма загружается
    case WM_INITDIALOG
      ;Давайте установим диалогу иконку "Инфо"
      ;Для этого мы пошлем ему самому сообщение
      ;WM_SETICON с указателем на иконку
      ;которую загрузим функцией LoadIcon
      invoke SendMessage, hWin, WM_SETICON, 1, FUNC(LoadIcon, NULL, IDI_ASTERISK)
      ;FUNC - еще один удобный макрос MASM32
      ;он вызывает функцию и возвращает ее возвращаемое значение :)
 
 
      ;теперь - к обработке нажатий на кнопки
      ;за это ответственно сообщение WM_COMMAND
    case WM_COMMAND
      ;идентификатор кнопки будет в wParam
      ;не верите - вбейте в гугл "WM_COMMAND"
      switch wParam
        ;если нажали на TEST_BTN
        case TEST_BTN
          ;выведем сообщение Hello, World
          invoke MessageBox, hWin, chr$("Hello, world!"), chr$("Test"), 0
          ;здесь chr$("строка") - еще один удобный макрос MASM32
          ;он создает строку в инициализированной секции данных
          ;и возвращает указатель на нее
          ;это эквивалентно записи:
          ;.data
          ;some_name db "Hello, world!",0
          ;...
          ;invoke MessageBox, hWin, offset some_name, ...
 
 
          ;Если нажали Выход
        case EXIT_BTN
          jmp exit_program ;переходим на выход
          ;ДА! Ассемблер - это язык, где никто не будет
          ;ругаться за использование в программе GOTO!
      endsw
 
    ;WM_CLOSE посылается окну при нажатии на крестик или при Alt+F4
    case WM_CLOSE
      exit_program:
      invoke EndDialog, hWin, 0 ;закрываем диалог
    endsw
 
  xor eax,eax ;всегда возвращаем 0
ret
WndProc ENDP

Ну и последнее:

end start

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

Ну что же, сохраним код как new.asm, закинем в одну папку new.rc, new.asm и xpmanifest.xml и скомпилируем всё следующим bat-файлом:

@echo off
cls
 
REM ну сюда впишите свои пути
SET PATH=C:\Masm32\bin
SET INCLUDE=C:\Masm32\INCLUDE
SET LIB=C:\Masm32\LIB
 
REM компилируем ресурсы
Rc.exe /v %1.rc
 
REM компилируем исходник
ML /nologo -c -coff %1.asm
if errorlevel 1 goto terminate
 
 
REM линкуем всё в exe
REM !!!!!! файл 64stub.exe можно взять отсюда:
REM http://kaimi.ru/2009/08/пакет-для-компиляции-masm32
REM и положить его в папку с батником и проектом
LINK /nologo %1.obj %1.res /SUBSYSTEM:WINDOWS /STUB:64stub.exe /FILEALIGN:512 /VERSION:4.0 /MERGE:.rdata=.text /MERGE:.data=.text /SECTION:.text,EWR /ignore:4078 /RELEASE /BASE:0x400000
 
REM ключей тут много, я описывать их не буду, вот самый примитивный вариант линкования:
rem LINK32 /nologo %1.obj  /SUBSYSTEM:WINDOWS
 
if errorLevel 1 goto terminate
 
echo OK
 
:terminate

После компиляции и линкования получаем программу размером 2.5 кб, которая еще и работает. Ну не прелесть ли?

Надеюсь, эта статья была вам полезна. Хотя о чем это я... Если вы дочитали до этого момента, то явно почерпнули для себя что-то полезное. Надеюсь, вам уже хочется писать свои GUI-программы на ассемблере с использованием MASM32, наполняя их функционалом, ну или хотя бы немного заинтересовала эта тема. В следующей статье я напишу что-нибудь более полезное, чем простой "Hello, world!", и представлю Вашему вниманию.

И последнее. Если после прочтения вы будете находиться в состоянии, подобном этому - не расстраивайтесь, у вас ещё все впереди! :)

Также рекомендую почитать

 Обсудить на форуме


Получать обновления на почту:     

Метки: , , , , , .

Комментариев: 93 к “MASM32: Начало”


  1. Игорь :

    Разобрадся и в стеке и в организации памяти а dx ни захотел почом с bat-файлом или на ошибку указать. Компиляция завершаетсф неудачей

    [Ответить]


  2. Игорь :

    Какой путь прописываем сюда в переменную патч - SET PATH=C:\Masm32\bin -?

    [Ответить]


  3. Игорь :

    Какой путь мы должны прописать в переменную patch- SET PATH=C:\Masm32\bin-?И я так и делаю вообщето

    [Ответить]

    dx:

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

    [Ответить]

    Игорь:

    Да я все понял я так и делал все. как ты написал от а до я!!Ну попробую еще!!!

    [Ответить]

    Игорь:

    А что мы задаем после знака роцента в батнике %1 - ?

    [Ответить]

    dx:

    Ты совсем отупел и не можешь инструкцию прочитать? Батник не надо редактировать, берешь и сохраняешь КАК В СТАТЬЕ ДАН.
    А потом читаешь мой комментарий чуть повыше и делаешь по пунктам, не задумываясь.

    Игорь:

    Просто покажи как выглядет батник с уже заданными параметрами а то при запуски из нужной директории выдается ошибка - не удалось открыть new.asm и new.rc,а xpmanifast я экспортирую из самого Resed,-а.Я все так и делал. как ты щас распиал от а до я и именно в этой последовательности три недели.

    Приведи батник со значениями, которые стаят после параметра %1.asm %1.rc

    Я задаю параметр %1new.asm и %1new.rc, что указываешь ты-?

    dx:

    А вот не подскажу, ты мне надоел, даже текст для полных идиотов выполнить не можешь

    Kaimi:

    Ну раз просишь показать, то вот: http://vimeo.com/41038645


  4. Игорь :

    Все так и делаю три недели. так же точ в точ, как у вас ос стоит и разрядность-?Какую версию xp у меня просто windows7 32-ая на xp перейти не могу, система битая(пиратская) слететь может,может из за этого-?Но ведь и в 7-ке есть старые библиотеки xp

    [Ответить]

    dx:

    Я на семерке x64 все собирал и собираю. Просто у кого-то руки совершенно криво растут из жопы.

    [Ответить]

    Игорь:

    ЗАРАБОТАЛ!!!!СПАСИБО!!!!Я ТАК ВСЕ И ДЕЛАЛ, ПРОСТО СТАБ64 НУЖНО БЫЛО УСТАНОВИТЬ!!ПАШЕТ!А ПРЕПОД НЕ СМОГ ХА!

    [Ответить]

    dx:

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

    Игорь:

    Аха я проанализировал уйму ошибок и знаешь я изначально все делал верно, просто stub64 exe был не рабочий,пото посмотрел цикл вшаих статей и понял. что он у вас слетает, а так я во всем разобрался и в механизмах работы ядра и в процессе компиляции. и понял что ошибка вызвана строкой в котороей передоваемый параметрк указывает, компилятору какие файлы мы компилируем,компилятор был не рабочий в рпезультате возникала ошибка и фалй ну далось открыть,так как не удавалось запустить пакет компиляции!!Что уделал я тебя, я нашел ошибку. которую ты проморгал и ты щас взбесился!!ИЗНАЧАЛЬНО ВСЕ ДЕЛАЛ ВЕРНО НО НЕ РАБОТАЛ STUB64 EXE!А хамить ты мастак!Но все равно спасибо за то. что поделился идеей

    dx:

    Каково это - сдавать диплом в 14 лет?


  5. Игорь :

    СПАСИБО ВАМ ЕСЛИ ЧЕ НЕ ДУЙТЕСЬ!!!

    [Ответить]


  6. Игорь :

    ТОЧНЕЕ ПЕРЕУСТАНОВИТЬ СТАБ64!!!ЧТО Я И ЗДЕЛАЛ СКАЧАЛ ПОВТОРНО УДАЛИЛИ СТАРЫЙ И УСТАНОВИЛ НОВЫЙ !!!

    [Ответить]


  7. Игорь :

    И КАКОРЕ ВИДЕО?!!ГДЕ-?!Я ЕЩЕ 2 НЕДЕЛИ НАЗАД РАЗОБРЛАСЯ,КАК ОТКОМПИЛИРОВАТЬ ИСХОДНИК И ВАМ СЮДА СКИДЫВАЛ А ВЫ ВСЕ ВАЛИЛИ НА МЕНЯ А ФАТАЛЬНАЯ ОШИКБА В СТРОКЕ КОДА, ГДЕ ПАЕКТУ КОМПИЛЯЦИИ ПЕРЕДАЕТСЯ ЗАДАННЫЙ ПАРАМЕТР УКАЗЫВАЮЩИЙ КАКИ ФАЛЫЙ НУЖНО ОТКОПИЛИРОВАТЬ В EXE,УКАЗЫВАЛА ПРЯМО НА ТО, ЧТО НЕ УДАЛОСЬ ЗАПУСТИТЬ STUB64 EXE И НЕ ВЫ С ВАШИМ ОПЫТОМ, НИ НА ФОРУМЕ ПРОГРАММИСТОВ НИ ПРЕПОДОВАТЕЛЬ В ЭТОМ НЕ РАЗОБРАЛИСЬ А СТУДЕНТ , КОТОРЫЙ НАЧАЛ ВСЕ С 0 РАЗОБРАЛСЯ АБСОЛЮТНО ВО ВСЕ, ОКАЗАВШИСЬ НА ПОТОЛОК ВАС ВЫШЕ!

    [Ответить]

    Kaimi:

    КОНЕЧНО ВОТ ЭТО ВИДЕО!!??111---+++=== Студент? Вы себе льстите, как максимум - школьник, который не в состоянии написать пару-тройку предложений, не совершая десяток ошибок в каждом. Также стоит отметить феерическое содержание вопросов, которые Вы изволили задавать на форуме программистов, да и заявление "разобрался и в механизмах работы ядра и в процессе компиляции" вызывает истерический смех, не более.
    Если Вы такой эксперт и во всем так круто разбираетесь, то зачем было задавать все эти идиотские вопросы, которые вообще не должны возникать у адекватного человека?

    [Ответить]

    Игорь:

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

    [Ответить]

    dx:

    Диплом с нуля, с нулевыми знаниями по теме? Это прекрасно!
    Интересно, чем же ты занят был несколько недель? Не мог почитать хэлпы к программам ml, link и компилятору ресурсов? И собрать все без батника? Надо было на протяжении 6 страниц трахать мозг людям на форуме, даже не пытаясь понять, что они советуют, ну и нам заодно.


Оставьте ваш комментарий