Решил начать цикл статей по ассемблеру, и, в частности, по 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).
Теперь сохраняем файл ресурсов. Он должен выглядеть примерно таким образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#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. Сначала я приведу полный листинг, а потом прокомментирую его построчно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
.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 будет эти сообщения получать, а мы будем в ней обрабатывать часть сообщений. которые нужны нам.
Далее я распишу код с комментариями:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
;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 |
Ну и последнее:
Здесь мы устанавливаем точку входа на метку start, т.е. с нее начнется выполнение программы.
Ну что же, сохраним код как new.asm, закинем в одну папку new.rc, new.asm и xpmanifest.xml и скомпилируем всё следующим bat-файлом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@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.io/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!», и представлю Вашему вниманию.
И последнее. Если после прочтения вы будете находиться в состоянии, подобном этому — не расстраивайтесь, у вас ещё все впереди!
Многие считают, что Assembler – уже устаревший и нигде не используемый язык, однако в основном это молодые люди, которые не занимаются профессионально системным программированием. Разработка ПО, конечно, хорошо, но в отличие от высокоуровневых языков программирования, Ассемблер научит глубоко понимать работу компьютера, оптимизировать работку с аппаратными ресурсами, а также программировать любую технику, тем самым развиваясь в направлении машинного обучения. Для понимания этого древнего ЯП, для начала стоит попрактиковаться с простыми программами, которые лучше всего объясняют функционал Ассемблера.
IDE для Assembler
Первый вопрос: в какой среде разработки программировать на Ассемблере? Ответ однозначный – MASM32. Это стандартная программа, которую используют для данного ЯП. Скачать её можно на официальном сайте masm32.com в виде архива, который нужно будет распаковать и после запустить инсталлятор install.exe. Как альтернативу можно использовать FASM, однако для него код будет значительно отличаться.
Перед работой главное не забыть дописать в системную переменную PATH строчку:
С:\masm32\bin
Программа «Hello world» на ассемблере
Считается, что это базовая программа в программировании, которую начинающие при знакомстве с языком пишут в первую очередь. Возможно, такой подход не совсем верен, но так или иначе позволяет сразу же увидеть наглядный результат:
.386 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib .data msg_title db "Title", 0 msg_message db "Hello world", 0 .code start: invoke MessageBox, 0, addr msg_message, addr msg_title, MB_OK invoke ExitProcess, 0 end start
Для начала запускаем редактор qeditor.exe в папке с установленной MASM32, и в нём пишем код программы. После сохраняем его в виде файла с расширением «.asm», и билдим программу с помощью пункта меню «Project» → «Build all». Если в коде нет ошибок, программа успешно скомпилируется, и на выходе мы получим готовый exe-файл, который покажет окно Windows с надписью «Hello world».
Сложение двух чисел на assembler
В этом случае мы смотрим, равна ли сумма чисел нулю, или же нет. Если да, то на экране появляется соответствующее сообщение об этом, и, если же нет – появляется иное уведомление.
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: mov eax, 123 mov ebx, -90 add eax, ebx test eax, eax jz zero invoke MessageBox, 0, chr$("В eax не 0!"), chr$("Info"), 0 jmp lexit zero: invoke MessageBox, 0, chr$("В eax 0!"), chr$("Info"), 0 lexit: invoke ExitProcess, 0 end start
Здесь мы используем так называемые метки и специальные команды с их использованием (jz, jmp, test). Разберём подробнее:
- test – используется для логического сравнения переменных (операндов) в виде байтов, слов, или двойных слов. Для сравнения команда использует логическое умножение, и смотрит на биты: если они равны 1, то и бит результата будет равен 1, в противном случае – 0. Если мы получили 0, ставятся флаги совместно с ZF (zero flag), которые будут равны 1. Далее результаты анализируются на основе ZF.
- jnz – в случае, если флаг ZF нигде не был поставлен, производится переход по данной метке. Зачастую эта команда применяется, если в программе есть операции сравнения, которые как-либо влияют на результат ZF. К таким как раз и относятся test и cmp.
- jz – если флаг ZF всё же был установлен, выполняется переход по метке.
- jmp – независимо от того, есть ZF, или же нет, производится переход по метке.
Программа суммы чисел на ассемблере
Примитивная программа, которая показывает процесс суммирования двух переменных:
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data msg_title db "Title", 0 A DB 1h B DB 2h buffer db 128 dup(?) format db "%d",0 .code start: MOV AL, A ADD AL, B invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start
В Ассемблере для того, чтобы вычислить сумму, потребуется провести немало действий, потому как язык программирования работает напрямую с системной памятью. Здесь мы по большей частью манипулируем ресурсами, и самостоятельно указываем, сколько выделить под переменную, в каком виде воспринимать числа, и куда их девать.
Получение значения из командной строки на ассемблере
Одно из важных основных действий в программировании – это получить данные из консоли для их дальнейшей обработки. В данном случае мы их получаем из командной строки и выводим в окне Windows:
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: call GetCommandLine ; результат будет помещен в eax push 0 push chr$("Command Line") push eax ; текст для вывода берем из eax push 0 call MessageBox push 0 call ExitProcess end start
Также можно воспользоваться альтернативным методом:
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: call GetCommandLine ; результат будет помещен в eax invoke GetCommandLine invoke MessageBox, 0, eax, chr$("Command Line"), 0 invoke ExitProcess, 0 push 0 call ExitProcess end start
Здесь используется invoke – специальный макрос, с помощью которого упрощается код программы. Во время компиляции макрос-команды преобразовываются в команды Ассемблера. Так или иначе, мы пользуемся стеком – примитивным способом хранения данных, но в тоже время очень удобным. По соглашению stdcall, во всех WinAPI-функциях переменные передаются через стек, только в обратном порядке, и помещаются в соответствующий регистр eax.
Циклы в ассемблере
Вариант использования:
.data msg_title db "Title", 0 A DB 1h buffer db 128 dup(?) format db "%d",0 .code start: mov AL, A .REPEAT inc AL .UNTIL AL==7 invoke wsprintf, addr buffer, addr format, AL invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start
.data msg_title db "Title", 0 buffer db 128 dup(?) format db "%d",0 .code start: mov eax, 1 mov edx, 1 .WHILE edx==1 inc eax .IF eax==7 .BREAK .ENDIF .ENDW invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0
Для создания цикла используется команда repeat. Далее с помощью inc увеличивается значение переменной на 1, независимо от того, находится она в оперативной памяти, или же в самом процессоре. Для того, чтобы прервать работу цикла, используется директива «.BREAK». Она может как останавливать цикл, так и продолжать его действие после «паузы». Также можно прервать выполнение кода программы и проверить условие repeat и while с помощью директивы «.CONTINUE».
Сумма элементов массива на assembler
Здесь мы суммируем значения переменных в массиве, используя цикл «for»:
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data msg_title db "Title", 0 A DB 1h x dd 0,1,2,3,4,5,6,7,8,9,10,11 n dd 12 buffer db 128 dup(?) format db "%d",0 .code start: mov eax, 0 mov ecx, n mov ebx, 0 L: add eax, x[ebx] add ebx, type x dec ecx cmp ecx, 0 jne L invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start
Команда dec, как и inc, меняет значение операнда на единицу, только в противоположную сторону, на -1. А вот cmp сравнивает переменные методом вычитания: отнимает одно значение из второго, и, в зависимости от результата ставит соответствующие флаги.
С помощью команды jne выполняется переход по метке, основываясь на результате сравнения переменных. Если он отрицательный – происходит переход, а если операнды не равняются друг другу, переход не осуществляется.
Ассемблер интересен своим представлением переменных, что позволяет делать с ними что угодно. Специалист, который разобрался во всех тонкостях данного языка программирования, владеет действительно ценными знаниями, которые имеют множество путей использования. Одна задачка может решаться самыми разными способами, поэтому путь будет тернист, но не менее увлекательным.
Post Views:
60 290
alex-rudenkiy 5 / 5 / 0 Регистрация: 02.01.2013 Сообщений: 438 |
||||||||
1 |
||||||||
02.04.2015, 18:47. Показов 8548. Ответов 7 Метки нет (Все метки)
Люди помогите разобраться с WINDOWS.INC. Я когда добавлял этот inc, вылазила такая ошибка
Потом я добавил такую строчку «option casemap:none» в надежде исправить ошибку, но и потом тоже вылезла ошибка.
Что делать ?
0 |
NoNaMe Модератор 1541 / 626 / 127 Регистрация: 10.06.2009 Сообщений: 2,436 |
||||
02.04.2015, 22:42 |
2 |
|||
Вызовы каким образом осуществляются?
Как вариант cdecl
0 |
Shvonder 46 / 35 / 24 Регистрация: 16.03.2015 Сообщений: 179 |
||||
03.04.2015, 04:01 |
3 |
|||
( если путь каталога отличный от «\masm32\», изменить )
0 |
alex-rudenkiy 5 / 5 / 0 Регистрация: 02.01.2013 Сообщений: 438 |
||||
03.04.2015, 14:26 [ТС] |
4 |
|||
0 |
Модератор 1541 / 626 / 127 Регистрация: 10.06.2009 Сообщений: 2,436 |
|
04.04.2015, 02:34 |
5 |
D:\masm\masm32\bin\Link.exe /LIBPATH:»D:\masm\masm32\lib» /SUBSYSTEM:WINDOWS /MERGE:.data=.text /ALIGN:16 /OPT:NOREF «%1.obj»
0 |
5 / 5 / 0 Регистрация: 02.01.2013 Сообщений: 438 |
|
04.04.2015, 13:25 [ТС] |
6 |
WINDOWS.INC(7938) : error A2179: structure improperly initialized
0 |
NoNaMe Модератор 1541 / 626 / 127 Регистрация: 10.06.2009 Сообщений: 2,436 |
||||||||||||||||
04.04.2015, 18:49 |
7 |
|||||||||||||||
Сообщение было отмечено alex-rudenkiy как решение Решение Спойлеры….
Кликните здесь для просмотра всего текста
1 |
5 / 5 / 0 Регистрация: 02.01.2013 Сообщений: 438 |
|
05.04.2015, 11:33 [ТС] |
8 |
Спасибо огромное, заработало
0 |
Оглавление
Исходники
Программы
Справочник
Разминка. Первая программа
Итак, приступим. Нам понадобится сам MASM32, который легко найти в Интернете.
Для своих проектов создадим папку, но, желательно не внутри MASM32. Назовем ее, но так,
чтобы в названии были только английские буквы — компилятор, гад, по-русски не понимает и
при компилляции будут трудности. Если имя не придумывается, то назовем просто ASM. Получилось? Тогда начнем. Немножко теории. В ассемблере
любая программа содержит сегмент данных — он отмечается символом .data, сегмент констант
— .const и сегмент кода — .code. В сегмент данных заносятся переменные, которые будут
доступны из любой точки программы (глобальные переменные). То же самое относится и к
сегменту констант. Тогда в общем виде структура программы имеет вид:
.data — глобальные переменные
.const — константы (по ходу дела не меняются)
.code — исполняемый код прогаммы
start: — его начало и наконец,
end start — его окончание.
MASM32 использует плоскую модель памяти — никаких сегментов памяти нет,
она, в принципе, доступна вся. Об этом — в самом начале программы мы должны
сообщить компиллятору (который сделает их нашего текста исполняемый .ехе файл):
.386 ; процессор
.model flat, stdcall ; 32 разрядная модель памяти
option casemap :none ; различает строчные
и заглавные буквы
Накопленный человечеством опыт собран в библиотеках. Опыт программистов
также собран в библиотеках — динамических и статических. Нам не нужно изобретать
велосипед заново — воспользуемся
тем, что уже есть (по крайней мере, на первых порах).
Windows содержит огромное количество функций в библиотеках динамической компоновки (DLL — dynamic link library),
эти функции часто называют API(Application Programming Interface).
Подключим к нашему проекту библиотеки. Это делается помощью include и includelib так:
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
Если мы впишем в текст программы эти строки, то к нашему проекту будет подключена
библиотека user32. Одной библиотекой, как правило, не обойтись, чаще всего стандартный набор
выглядит так:
; Подключаемые файлы.
include \masm32\include\windows.inc
include \masm32\include\masm32.inc
include \masm32\include\gdi32.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\Comctl32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\shell32.inc
include \MASM32\include\oleaut32.inc
; Подключаемые библиотеки.
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\Comctl32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\shell32.lib
includelib \masm32\lib\oleaut32.lib
Все понятно? Если не все (или все не) — ничего страшного, потом разберемся.
Едем дальше. Наша первая программа:
———————————————Линия отреза———————————————
.386
.model flat, stdcall
option casemap :none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
TitleMsg db «Первая программа»,0
TextMsg db «Пишем здесь чаво-нибудь, типа — это я сам сделал(а)!» ,0
.code
start:
invoke MessageBox, NULL,ADDR TextMsg,ADDR TitleMsg, MB_OK
invoke ExitProcess, NULL
end start
uild
————————————-Линия отреза——————————————————
Аккуратно копируем то, что между линиями отреза (они не должны попасть в копируемую область!),
откравыем MASM32 editor, в меню File выбираем пункт New и вставляем туда скопированное. Теперь сохраним созданный файл
в нашей папке под каким-нибудь название, и, что очень важно, с расширением .asm, ну, например, first.asm. Получилось?
Если нет — то повторим все еще раз (или не раз, пока не получится). Теперь — компиляция, т.е. создание .ехе-файла. В меню
редактора выбираем пункт Project и в нем — пункт Build All. Выдохнули и нажали. Если все сделано правильно, через
какое-то время на экране появится что-то вроде:
Если картинка похожа, то .ехе-файл создан. Открываем нашу папку и находим его. Если папка была сначала
пустой, то после компиляции там три файла — один first.asm — это наш исходник, другой, похожий на использованную
туалетную бумагу — first.obj, и наконец, наш файл first.exe. Щелкаем по нему мышкой. Работает? Если не работает —
ищем, что сделано неправильно, а если работает — то еще похожий пример:
———————————————Линия отреза———————————————
.386
.model flat, stdcall
option casemap :none
include \masm32\include\windows.inc
include \masm32\include\masm32.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
TextMsg1 db «Здесь — текст, типа — это я сам сделал(а)!» ,0
TextMsg2 db» (чтобы показывать друзьям и знакомым )»,0
TextMsg3 db «Copyright © А здесь — свою ФИО., «,13,10,»(полностью, чтобы они поверили)»,13,10,»ну и, конечно, -All Right Reserved»,0
.code
start:
invoke AboutBox,NULL,NULL,NULL,ADDR TextMsg1,ADDR TextMsg2,ADDR TextMsg3
invoke ExitProcess, NULL
end start
end start
————————————-Линия отреза——————————————————
Повторим все то, что мы сделали раньше. Запускается? Для тех, у кого рука дрожит и текст
не копируется, исходники — пример 1 и пример 2
Ну вот, разминка закончена. Тот, кто понял, что MASM32 он уже полностью освоил, может
расслабиться, а для остальных — продолжение на следующей странице…
В начало
Дальше
MASM32 и OpenGL
Запуск
.EXE-программы
может быть осуществлён с помощью двойного
щелчка мышью.
Если
вы создали программу, которая ничего
не выводит на экран, то за её работой
можно наблюдать при помощи
программы-отладчика, например, OllyDbg.
Отладчики позволяют наблюдать за
изменением содержимого регистров и
флагов. Подробнее работа в отладчике
OllyDbg будет описана ниже.
2.4. Инструментальный пакет masm32
В
п.2.3 отмечено, что для создания программ
на ассемблере в Windows,
необходим текстовый редактор и компилятор.
Реальные программы Win32 используют также
внешние функции, стандартные константы
и переменные, ресурсы и много другое.
Всё это требует дополнительных файлов,
которые есть в инструментальном пакете
MASM32. Важно понять, что MASM32 не компилятор,
а сборник для программирования под
Win32, в который входит 32-битный компилятор
MASM.
Инструментальный
пакет MASM32
предназначен для создания приложений
Windows
на языке ассемблера и содержит все
необходимые инструменты, к тому же он
распространяется бесплатно.
Основные сведения и порядок работы в пакете masm32:
1.
Для создания исходных текстов программ
рекомендуется использовать текстовый
процессор пакета MASM32
под названием QEDITOR
(от Quick
Editor,
быстрый редактор):
После
набора сохранить текст программы
командой File
– Save,
указать папку BIN
и расширение .ASM,
например MYPROG.ASM.
2.
Командой Project
– Build
All
создать объектный и исполнимый файлы:
Если
исходный текст программы набран без
ошибок, то в папке, где он хранился,
увидим два новых файла: MYPROG.OBJ
и MYPROG.EXE.
3.
Когда объектный и исполнимый файлы
созданы, запустите программу на
выполнение. Для этого можно дважды
щелкнуть мышью по названию исполнимого
файла (в папке BIN)
или запустить программу через редактор
QEDITOR
командой Project
– Run
Program.
2.5. Примеры
Пример
0.
«Скелет»
стандартной программы
.386
.model flat, stdcall
option casemap :none
;подключение
необходимых
библиотек
include
\MASM32\INCLUDE\windows.inc
include
\MASM32\INCLUDE\masm32.inc
include
\MASM32\INCLUDE\gdi32.inc
include
\MASM32\INCLUDE\user32.inc
include
\MASM32\INCLUDE\kernel32.inc
includelib
\MASM32\LIB\masm32.lib
includelib
\MASM32\LIB\gdi32.lib
includelib
\MASM32\LIB\user32.lib
includelib
\MASM32\LIB\kernel32.lib
;раздел, где
объявляются все константы
.const
;раздел, где
объявляются переменные, уже имеющие
какое-то значение
.data
;раздел, где
объявляются переменные, еще не имеющие
значения
.data?
.code
start: ;с этого слова
начинается код программы
invoke ExitProcess,0
end start ;с этого
слова заканчивается код программы
Сохраните
этот «скелет» в отдельном файле для
удобства и используйте как заготовку.
Пример
1.
Структура
программы и основные директивы
Построчно
разберём простейшую программу.
Текст
программы на ассемблере содержит кроме
инструкций процессору еще и служебную
информацию
(в виде директив),
предназначенную для программы-ассемблера.
Начнем
с простого. В первой программе не будет
вызовов API-функций,
ее цель – понять саму структуру программы
на языке ассемблера для Windows.
Поэтому программа, прибавляющая к 2
число 3, будет выглядеть следующим
образом:
.386
.model
flat, stdcall
.code
start:
mov
eax, 8
add
eax, 8
ret
end
start
В
ней инструкции процессора mov,
add,
ret
окружены
директивами. Первые три директивы
начинаются с точки.
Директива
.386
показывает,
для какого процессора предназначена
программа. В нашем случае это процессор
Intel
80386 и более поздние модели, ведь семейство
процессоров Intel
совместимо снизу вверх.
Вторая
директива .model
flat,
stdcall
показывает,
в какой среде будет работать программа.
Все программы работают под управлением
операционной системы, которая их
запускает и обеспечивает взаимодействие
с внешней средой. Директива .model задаёт
модель памяти flat (плоская или сплошная)
для нашей программы. Эта модель памяти
используется для программирования под
Windows,
т.е. директива говорит о том, что именно
для операционных систем семейства
Windows
952
предназначена программа.
Stdcall
— это «уговор» о том, кто будет
чистить параметры (функция, которую
вызвали, или сам вызывающий). Мы всегда
будем использовать вариант «функция
чистит свои параметры». Он и называется
stdcall. Однако такое объяснение не полное,
и мы вернемся к параметру stdcall при
объяснении вызова функций. К этому
моменту вы уже будете знать, что такое
стек.
Третья
директива .code
показывает,
где начинаются сами команды процессора.
Когда операционная система пытается
запустить программу, она ищет в ней
инструкцию, с которой нужно начать, и
отправляет ее процессору. Когда же
инструкции кончаются, операционная
система «подхватывает» программу и
помогает ей правильно завершиться,
чтобы освободить место другим, ведь
Windows
– многозадачная операционная система,
способная выполнять одновременно
несколько программ. Уйти из-под «опеки»
операционной системы помогает инструкция
ret.
Инструкция,
с которой начинается программа, обычно
помечается последовательностью символов
с двоеточием на конце (меткой). В нашем
случае это start:.
Там, где оканчивается последовательность
команд процессора, в программе должна
стоять директива end
<метка первой инструкции программы>,
в нашем случае это
end
start.
Эта директива, а также сама метка не
переводятся в инструкции ассемблера,
а лишь помогают получить программу,
которую способен выполнить процессор.
Без них программа-ассемблер не поймет,
с какой инструкции процессор начнет
работу.
Отладка
Программа
ничего не выводит на экран, поэтому за
для изучения её работы воспользуемся
программой-отладчиком OllyDbg.
Чтобы открыть программу в отладчике,
достаточно загрузить OllyDbg и открыть
программу как обычный документ –
File-Open.
В
верхней левой части отладчика можно
увидеть свою программу. Вверху справа
– регистры процессора.
Внизу
слева — байты памяти (OllyDbg сразу же
показывает секцию данных программы).
Внизу справа отображается содержимое
стека (работа со стеком будет описана
ниже).
Необходимо
помнить, что Ollydbg это отладчик и у него
есть свои ограничения. Рекомендуется
закрывать его каждый раз перед загрузкой
новой программы.
С
помощью клавиши F8
можно выполнить программу по шагам и
просмотреть, как меняется содержимое
регистров. Ответьте на вопрос, почему
в результате сложения 8+8 регистр EAX
стал равен 10? Состояние флагов также
меняется. Мы видим, что после выполнения
первой команды флаг Z
опустился (обратился в ноль), потому что
результат выполнения операции не равен
нулю.
Пример
2.
Использование
функций API
У
предыдущей программы не было связи с
внешним миром, она ничего не считывала
с клавиатуры и не выводила на экран. О
том, что она делала, мы могли узнать
только с помощью отладчика. Обычно
программам помогает общаться с окружающим
миром операционная система, которая
берет на себя все детали взаимодействия
с внешними устройствами. В системе
Windows,
для этого служат функции
API.
API
– это стандартные функции, на основе
которых и пишут все программы для
Windows.
Например MessageBox выдаёт сообщение на
экран, PostQuitMessage сообщит Windows, что программа
хочет закончить работу и т.д. Все они
уже готовы – остается только вызывать
их. Получается, что используя API-функции,
мы применяем элементы программирования
высокого уровня.
Находятся
API-функции
обычно в динамически загружаемых
библиотеках – файлах с расширением
.DLL.
При
вызове каждой API-функции
надо передавать параметры, т.е. аргументы,
с которыми ей предстоит работать. Раньше
это делалось так: параметры заталкивались
в стек (команда push) задом наперед –
сначала последний, потом предпоследний
и т.д., а затем вызывалась сама программа
(команда call). Например:
push addr Text2
push addr Text1
push hWnd
call MessageBox
Такая
запись допускается и сейчас, но существует
и более компактная: invoke
MessageBox, hWnd, Text1, Text2
Правда,
использование invoke требует прототипов
для каждой вызываемой программы, но
прототипы API готовы и хранятся в
соответствующих файлах включения.
Рассмотрим
программу с использованием функций
API.
Прежде чем приступить к выводу на экран,
изучим более простую процедуру
ExitProcess.
Её
вызывает каждая Windows-программа,
чтобы завершить свою работу. В ассемблере
под DOS
мы пользовались инструкцией возврата
ret.
Но ExitProcess
действует
правильнее, не только возвращая управление
операционной системе, но и освобождая
занятые программой ресурсы.
В
следующем листинге показана программа
для Windows,
которая только и делает, что правильно
завершается.
.386
.model flat, stdcall
option casemap:none
includelib
C:\MASM32\LIB\kernel32.lib
ExitProcess proto :DWORD
.code
start:
push 0
call
ExitProcess
end
start
Вызываемая
в ней процедура ExitProcess
требует
одного параметра – это код завершения,
возвращаемый операционной системе. Он
передается процедуре командой push
0.
Число 0 считается признаком удачного
завершения.
Поскольку
ExitProcess
–
«чужая» процедура, не определенная в
нашей программе, ассемблер должен знать,
где она находится, а также (для проверки
– она ли это) число и размер ее параметров.
Сведения
об адресе и параметрах процедуры хранятся
в файле библиотеки kernel32.lib,
который подключается к ассемблерному
тексту директивой includelib
C:\MASM32\LIB\kernel32.lib.
Перед
тем как создать инструкцию вызова этой
процедуры компоновщик сравнивает
сведения из библиотеки с прототипом
ExitProcess
proto
:DWORD,
и если все совпадает, создает пригодный
к исполнению файл с расширением .EXE.
Прототип процедуры очень прост и состоит
из имени, слова proto
и
параметров. В нашем случае параметр
один – это двойное слово (то есть 4 байта)
DWORD.
Если параметров несколько, они разделяются
запятой.
Пример
3.
Вывод
строки на экран
Создадим
программу, выводящую на экран фразу
“Hello,
world!”:
.386
.model
flat, stdcall
option casemap:none
ExitProcess proto :dword
GetStdHandle
proto :dword
WriteConsoleA proto :dword,
:dword, :dword, :dword, :dword
includelib
C:\MASM32\LIB\kernel32.lib
.data
stdout dd ?
msg db “Hello, world!”,
0dh, 0ah
cWritten dd ?
.code
start:
invoke GetStdHandle, -11
mov stdout, eax
invoke WriteConsoleA,
stdout, ADDR msg, sizeof msg, ADDR cWritten, 0
invoke
ExitProcess,
0
end
start
В
программе вызываются три процедуры:
GetStdHandle,
WriteConsoleA
и
ExitProcess.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #