Макросы в masm32: Руководство по проектированию макросов в MASM32
Содержание
Использование макросов в MASM на примере создания окна / Хабр
В далеком 2001-ом году я проводил много времени за изучением ассемблера под Win32. Тогда после долгих мучений с написанием одного и того же кода по сотне раз я взялся написать для себя небольшую библиотеку макросов. В итоге удалось достаточно серьезно облегчить себе судьбу и уменьшить необходимость повторять огромные полотенца кода, при необходимости написать простейшую программу с одним окном.
Недавно наткнулся на те проекты и решил выложить некоторые из них, может кому пригодится…
Состав проекта
Итак приступим. Проект, прикрепленный внизу, имеет следующую структуру.
\Macros | Каталог с макросами, используемыми в приложении | |
Macros.Inc | Здесь находятся основные макросы, которые нужны при написании любой Win32 программы. Здесь макросы для выделения памяти, облегчения включения файлов, макросы для определения данных и проч. | |
Window.Mac | Макросы, которые облегчают создание окон | |
Status.Mac | Макросы для создания и использования статус строк | |
Menu.Mac | Макросы для создания и использования меню | |
Quake.Bmp | Изображение, которое будет загружено и отображено в окне программы | |
Scull.Ico | Иконка изображения (просто черепок) | |
Rsrc.rc | Файл определения ресурсов | |
Window.Asm | Основной файл программы | |
Window.Exe | Откомпилированная программа | |
WndExample.Asm | В данном файле находится исходный код для обработки сообщений идущих к окну «Example» нашей программы |
При запуске Window.Exe отображаемое окно будет выглядеть следующим образом:
Простейшая программа, без окна
include macros\macros. inc
@Start
@Uses kernel32
.code
WinMain Proc
invoke ExitProcess, 0
WinMain Endp
End WinMain
Первой строкой здесь включение основных макросов, далее идет макрос Start, который создает начало программы и подставляет информацию о модели памяти, об используемом процессоре и проч. Далее идет макрос Uses он включает необходимую библиотеку в программу. В данном случае мы будем использовать kernel32.dll так как именно она содержит в себе используемую нами функцию завершения процесса ExitProcess.
Далее следует блок кода указанный с помощью .code, в котором находится основная процедура программы. По факту сама процедура может называться как угодно и название WinMain я дал ей просто от балды. Главное, чтобы в конце файла была строка End {Имя_функции_точки_входа}
Эта программа не несет никакой функциональной нагрузки, поэтому после запуска она ничего не будет делать — просто завершит свою работу. Теперь исходный код программы, приведенной в архиве:
include macros\macros. inc
IDC_MAINSTATUS Equ 1
IDC_MENUEXIT Equ 10
@Start
@Uses gdi32, user32, comctl32, kernel32
.xlist
include macros\Menu.mac
include macros\Window.mac
include macros\Status.mac
.list
.data?
hIcon Dd ?
hBrush Dd ?
hCursor Dd ?
hImage Dd ?
hInstance Dd ?
@DefineMenu Menu
@DefineStatus Example
@DefineWindow Example
.code
; Main program cycle
WinMain Proc
mov hInstance, @Result(GetModuleHandle, NULL)
mov hIcon, @Result(LoadIcon, hInstance, 100)
mov hCursor, @Result(LoadCursor,NULL,IDC_ARROW)
mov hBrush, @Result(GetSysColorBrush, COLOR_APPWORKSPACE)
@CreateWindow Example, hInstance, NULL,'Example_wnd', \
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_VISIBLE, \
WS_EX_APPWINDOW, 'Example', \
hIcon, hBrush, hCursor, NULL
@SetWndSize Example, 700, 600
@MoveWnd Example, 100, 100
@CreateMenu Menu
@AppendMenu Menu, 'Exit', IDC_MENUEXIT
@AttachMenu Example, Menu
@CreateStatus Example, Example, IDC_MAINSTATUS
@SetStatusParts Example, 2,300,-1,0,0,0,0,0,0,0,0
@SetStatusText Example, 'Example program window. ..', 0, 0
@SetStatusText Example, 'The CHEMI$T Copyright(C)2001', 0, 1
@ProcessMsgs Example, FALSE
@DestroyMenu Menu
@DestroyWindow Example
invoke ExitProcess, 0
WinMain Endp
End WinMain
Теперь объясню поэтапно, что происходит в этом исходнике. Первое, включаются макросы реализации меню, статус-строки и оконного функционала. Они обрамлены спец командами макроассемблера .xlist (отключение листинга) и .list(включение листинга) это было сделано только для того, чтобы в случае выдачи листинга макроассемблером, не было кода из этих файлов /ибо только лишние полотенца кода/ Далее идет описание блока неинициализированных данных .data?, переменные в данном блоке не инициализируются, система просто выделяет память не обнуляя ее. Такие переменные без инициализации использовать чревато, ибо в памяти находится может что угодно. Здесь же выделяется место под переменные, которые в первых строках метода WinMain принимаю значения загруженных ресурсов и инстанса самого приложения.
Макросы @DefineMenu, @DefineStatus и @DefineWindow производят первоначальную инициализацию переменных, в которых будут хранится параметры объектов /меню, статус строки и окна соответственно/
И уже после всех инициализаций идет самое интересное.
Четыре первых строки
mov hInstance, @Result(GetModuleHandle, NULL)
mov hIcon, @Result(LoadIcon, hInstance, 100)
mov hCursor, @Result(LoadCursor,NULL,IDC_ARROW)
mov hBrush, @Result(GetSysColorBrush, COLOR_APPWORKSPACE)
Инициализируют переменные /инстанс приложения, иконка, курсор, кисть для отрисовки окна/. Здесь используется приятный макрос
Result
. Этот макрос выполняет указанный вызов API с переданными параметрами и возвращает содержимое регистра EAX, который служит для возврата результатов функции. Если бы не было данного макроса, то каждая строка разбивалась на подобный код :
invoke GetModuleHandle, NULL
mov hInstance, eax
Макросы для создания и работы окна должны вызываться последовательно, @CreateWindow — создает окно, @SetWndSize — выставляет размер окна, @MoveWnd перемещает окно в нужные координаты на экране, @ProcessMsgs отрабатывает основной цикл обработки сообщений, идущих к вашему окну, @DestroyWindow — удаляет окно. Когда вы создаете окно, вам необходимо создать файл с обработчиками событий данного окна. В приведенном проекте это файл WndExample.Asm. Данное имя задано с тем, что файл обработчик событий включается автоматически по маске Wnd.Asm
Макросы для создания меню и для создания статус-строки я особо не доделывал тогда, сделал только до необходимого мне функционала.
Макросы работы с меню:
@CreateMenu {Имя_меню}
Создание меню с нужным именем
@AppendMenu {Имя_меню}, {Заголовок_пункта_меню}, {Код_сообщения}
Добавление пункта меню с нужным заголовком. По нажатии на данный пункт меню в очередь сообщений попадет код сообщений.
@AttachMenu {Имя_окна}, {Имя_меню}
Добавление меню к указанному окну.
Макросы для работы со статус строкой /Необходим ComCtl32/
@CreateStatus {Имя_статус_строки}, {Имя_окна}, {ID_статус-строки}
Создание статус строки у указанного окна
@SetStatusParts {Имя_статус_строки}, {Кол-во_частей}, {Ширина_части}, {}, {}, {}, {} /До десяти частей, последняя указывается размер=-1, т.е. растянуть/
Разделение на несколько частей, данный макрос можно было бы доработать, но как то видимо я этого тогда не сделал
@SetStatusText {Имя_статус_строки}, {Текст}, {Стиль /уже не помню для чего/}, {Часть_статус_строки}
Выставление статуса в нужную часть статус строки
Файл с обработчиками событий
В данном файле указывается исходный текст основной процедуры окна, в которой регистрируются пользовательские обработчики и в которой также должны быть зарегистрированы обработчики событий меню. Каждый обработчик события окна выглядит так:
@WndHandlerStart {Имя_окна}, {Имя_обработчика}
mov eax, TRUE
@WndHandlerEnd {Имя_окна}, {Имя_обработчика}
Основная процедура в приведенном проекте находится в конце файла и выглядит так:
@WndProcedureBegin Example, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT
; Menu handlers
@WndMenuHandler IDC_MENUEXIT, Exit
; Sample user handler
@WndUserHandler Example, WM_SIZING
@WndProcedureEnd Example
Здесь назначается обработчик пункта меню, которому назначен Код_сообщения IDC_MENUEXIT обработчик с именем Exit. А также регистрируется пользовательский обработчик сообщения WM_SIZING. Пользовательский обработчик событий должен иметь имя сообщения, которое он обрабатывает. Все события, которые заранее прописаны в окне можно посмотреть в файле Window.Mac, в макросе @WndProcedureBegin. Список этих событий таков: Close, Paint, Help, Activate, Deactivate, SysCommand, Show, Hide, Create, Destroy, KeyDown, KeyUp, Resize, DblClick, MouseUp, MouseDown, WheelDown, WheelUp. Примеры данных обработчиков включены в исходник проекта и вы можете понажимать F1 в окне и покрутить колесо мыши.
В принципе все, что связано с этими событиями можно посмотреть в MSDN и исходниках, в этом нет ничего сложного и в данном описании я не буду включать.
Компиляция программы
Для компиляции необходим пакет masm32 (можно найти здесь) После установки желательно внести путь до каталога masm32\bin в переменную окружения Path и отредактировать файл masm32/bin/build.bat исправив вызов компилятора ml и линкера, чтобы добавить пути библиотек и включаемых файлов и не приходилось постоянно данные пути прописывать в коде.
Так в вызов ML.Exe нужно добавить еще один параметр /IF:\masm32\include — вместо F:\masm32 вам нужно указать путь, куда установился пакет masm32 у вас. А в два вызова линкера Link.exe нужно добавить путь к библиотекам с помощью параметра /LIBPATH:F:\masm32\lib. Опять же путь замените на тот, который соответствует вашему.
Далее, в каталоге с проектом даем две команды: bres (bres.bat производит компиляцию файла ресурсов rsrc.rc в текущем каталоге) и следом за ним build window (build.bat — производит компиляцию и линковку проекта).
Проекты переложил на GitHub
вызов macros в MASM — CodeRoad
У меня неприятности. MASM macros, похоже, вообще не ведет себя как C macros. C имеет препроцессор, который обрабатывает текст как последовательность токенов, и вы можете поместить вызов макроса где угодно. Кажется, не так в MASM. Я не могу найти подходящий документ для этого, но вы не можете вызвать макрос внутри директивы, верно? За исключением тех случаев, когда он является частью pars в другом вызове макроса? Или когда он появляется внутри макроса def? Разве это единственные исключения? Где spec/doc?
OK, я пытаюсь сделать вот что:
external macro fnames:VARARG
extern stdin:qword,
for fname, <fnames>
fname&_ptr: qword,
endm
endm
external puts, fgets, printf, srand, rand
И я получаю:
синтаксическая ошибка: для
Похоже, он уже интерпретирует директиву extern и находит внутри макрос for, который не разрешен => синтаксическая ошибка.
Что, черт возьми, я могу сделать? Вы можете разделить директиву на несколько строк, если закончите каждую строку запятой, не так ли? Но если он все равно не интерпретирует макросы, то тут уж ничего не поделаешь.
Я обнаружил, что могу это сделать.:
external macro fnames:VARARG
extern stdin:qword
for fname, <fnames>
extern fname&_ptr: qword
endm
endm
external puts, fgets, printf, srand, rand
Но умножение директивы-это не всегда вариант. Возьмем директиву proc f.ex. Я не могу использовать заголовок proc для одного и того же proc несколько раз.
Есть ли разумное решение этой дилеммы? Можно ли использовать MASM macros для производства одной линии продукции? Я не знаю, как. Спешите на помощь, все вы там ботаники! 🙂 Cheerioh
assembly
macros
masm
Поделиться
Источник
Henrik
20 января 2017 в 22:31
2 ответа
- Вызов функции C из masm 64
У меня есть проблема с моим кодом assembly (64 bit masm в Visual 2013 на win8 64). Когда я вызываю функцию C (printf), она выбрасывает исключение из ntdll.dll. Что я делаю не так? Как я могу читать и записывать данные с консоли в 64 bit masm? Где я могу найти хороший учебник для masm 64 bit? extrn…
- Нет консольного вывода из программы, собранной с MASM на win32
Я запускаю некоторые примеры MASM32 (из www.masm32.com) и замечаю, что вывод консоли пуст в моем окне командной строки (программа компилируется, связывается и запускается, но не выводится. .486 ; create 32 bit code .model flat, stdcall ; 32 bit memory model option casemap :none ; case sensitive…
4
Подпись директивы EXTERN
является
EXTERN [[ language-type]] name [[ (altid) ]] : type
[[, [[language-type]] name [[ (altid) ]] : type ...]]
Таким образом, вы можете создать однострочный файл с директивой CATSTR
, как это:
external MACRO fnames:VARARG
txt textequ <stdin: qword>
for fname, <fnames>
txt CATSTR txt, <, &fname&_ptr: qword>
endm
extern txt
endm
external puts, fgets, printf, srand, rand
Его вывод находится в одной строке:
extern stdin: qword, puts_ptr: qword, fgets_ptr: qword, printf_ptr: qword, srand_ptr: qword, rand_ptr: qword
Кроме того, если вы хотите использовать выходные данные MACRO
в качестве входных данных для другого MACRO
, параметра или выражения, вы можете использовать директиву EXITM
.
Поделиться
zx485
21 января 2017 в 00:31
1
Вот что я придумал. Очень похоже на вышесказанное:
functions macro fnames:VARARG
local list
list textequ <>
for fname, <fnames>
list catstr list, <,fname&_ptr: qword>
endm
list substr list, 2
exitm list
endm
extern stdin:qword, functions(puts, fgets, printf, srand, rand)
Это работает! Таким образом, действительно можно вызывать макрофункции из внутренних директив. Это должны быть макропроцедуры FUNCTIONS, а не макропроцедуры.
Поделиться
Henrik
21 января 2017 в 00:55
Похожие вопросы:
MASM-Макропеременная?
Это мой первый вопрос здесь, и я надеюсь, что вы сможете мне помочь! В настоящее время я работаю над эмулятором GameBoy и хочу написать его в MASM, для обработки инструкций CPU я хочу создать массив…
Инициализация больших чисел с константами в MASM
Я пытаюсь написать некоторые macros в MASM для обработки больших чисел (то есть чисел со значениями, большими, чем могут быть сохранены в регистрах). Однако я столкнулся с проблемой создания…
Разница между директивами MASM EQU и TEXTEQU
Я пытаюсь понять разницу между директивами MASM EQU и TEXTEQU . До сих пор все, что мне удалось собрать, — это то, что синтаксис немного отличается и что EQU macros не может быть переопределен, в то…
Вызов функции C из masm 64
У меня есть проблема с моим кодом assembly (64 bit masm в Visual 2013 на win8 64). Когда я вызываю функцию C (printf), она выбрасывает исключение из ntdll.dll. Что я делаю не так? Как я могу читать…
Нет консольного вывода из программы, собранной с MASM на win32
Я запускаю некоторые примеры MASM32 (из www.masm32.com) и замечаю, что вывод консоли пуст в моем окне командной строки (программа компилируется, связывается и запускается, но не выводится. .486 ;…
Как удалить неиспользуемые данные .CONST в MASM?
Я использую macros в MASM для генерации около 2000 функций, для каждой из которых я определяю строку, но я использую только около ~30 из них в любой данной программе. (Нет никакого способа…
Преобразует язык assembly macros на понятном языке assembly
В MASM можно ли преобразовать макрокоманды в соответствующие assembly языковые инструкции? Я хочу преобразовать макроинструкции MASM в простые инструкции языка assembly, чтобы увидеть, как на самом…
Команды MASM — проблемы в DOSBox
Я новичок на этом форуме и хотел бы получить некоторую помощь в программировании MASM. В настоящее время я изучаю MASM у своего преподавателя курса, и он сказал нам использовать MASM 6.15 (легко…
Вызов библиотеки, выполненной в MASM — C или C++
я пытался позвонить в библиотеку в c году, которая была сделана в masm. Мне удалось сделать файл .lib из assembly MASM. Но я понятия не имею, как назвать его на языке C в качестве библиотеки. Вот…
MASM — различия между процедурами и macros
Мне было интересно, в чем разница между macros и procedures в MASM году ? Есть ли разница с тем, что будет делать программа или как ассемблер будет собирать мой код, если я вызову macro вместо…
Ввод-вывод, более сложные программы и чтение листингов
В этом разделе мы познакомимся с макросами ввода-вывода, разберем несколько
более сложных примеров и посмотрим на листинги.
Структура ассемблерной программы
Посмотрим еще раз на программу из предыдущего раздела:
include console.inc
.code
Start: mov ecx, 5
again: outstrln 'Hello World'
dec ecx
jnz again
exit
end Start
Разберем структуру исходного текста этой программы:
Строка
include console.inc
подключает макросы, которые мы будем
использовать в этом курсе. О макросах мы поговорим чуть ниже.Строка
.code
открывает секцию кода.Следующие четыре строки содержат собственно код нашей программы. Отдельно
стоит упомянуть меткуStart
, которая соответствует точке входа в нашу
программу — мы можем разместить ее и не в начале кода.
outstrln
— это вызов макроса для вывода строки на стандартный поток
вывода. Этот макрос определен в файлеconsole.inc
.exit
— это также макрос изconsole.inc
, который осуществляет
системный вызов, завершающий работу программы.Наконец, директива
end Start
сообщает ассемблеру, что исходный текст
программы закончился, и одновременно информирует его, какую метку (Start
)
нужно использовать в качестве точки входа.
Макросы
Для написания простейших программ на языке ассемблера нам потребуется не так
много возможностей языка. Умения работать с командами пересылки данных,
арифметическими вычислениями и переходами вполне достаточно. Помимо этого,
потребуется представление о структуре ассемблерной программы и директивах
резервирования памяти.
Кроме этого, не хватает только возможностей ввода-вывода. Поскольку для
организации ввода-вывода (а также, например, для завершения работы программы)
требуется обращаться к операционной системе, мы не можем (пока) сделать этого
самостоятельно — нам потребуется материал, который в этом курсе
разбирается позднее.
В качестве врéменной меры мы будем пользоваться ограниченным набором
макросов, определенных в файле console.inc
. Этот файл разработан для нашего
курса, найти его можно в каталоге include
внутри каталога MASM (например,
C:\masm32\include
).
Макросы будут подробно разбираться далее в конце этого курса, и в дальнейшем
мы познакомимся с тем, как устроен console.inc
. На текущем этапе нам нужно
знать о них следующее: макросы раскрываются в некоторый код языка ассемблера
(возможно, в 2-3 машинные команды, а возможно, и в десятки и даже сотни).
Макросы ввода-вывода
Кратко рассмотрим минимальный набор макросов, которыми будем пользоваться в
ближайшее время:
inchar
,outchar
— ввод и вывод символов; единственный
операнд — код символа, который может быть регистром или памятью
размерности 8, а в случае вывода еще и непосредственным операндом.inint
,outint
,outword
— ввод и вывод целых чисел;outword
выводит число как беззнаковое,outint
— как знаковое. Размерность
операнда может быть 8, 16 или 32.outstr "text"
можно использовать для вывода текста.newline
выводит символы возврата каретки и перевода строки.flush
очищает буфер ввода.Наконец, макрос
exit
завершает работу программы.
Ниже приведена шпаргалка по макросам:
outchar op1 ; r/m/i 8 вывод символа
inchar op1 ; r/m 8 ввод символа
outint op1 ; r/m/i 8/16/32 вывод целого с/з
outword op1 ; r/m/i 8/16/32 вывод целого б/з
inint op1 ; r/m 8/16/32 ввод целого
outstr "text" ; вывод строки
newline ; вывод символов CR LF
flush ; очистка буфера ввода
exit ; завершение работы программы
Пример программы: числа Фибоначчи
Попробуем написать простую, но законченную программу. Наша программа (назовем
ее fib
) будет запрашивать у пользователя номер n и вычислять n-е число
Фибоначчи.
Наша программа не будет использовать никаких переменных в памяти, обходясь
одними регистрами.
Запросим у пользователя число n и поместим его в регистр EDX:
outstr "enter n: "
inint edx ; F_n to calculate
Проинициализируем рабочие регистры. Текущее число Фиббоначчи Fk
будем хранить на EAX, предыдущее Fk — на EBX. Само текущее k будем держать в ECX:
mov eax, 1 ; F_2=F_k
mov ebx, eax ; F_1
mov ecx, 2 ; k=2
Если нас просят вычислить F1 или F2, ответ (единица) у
нас уже готов, прыгнем вперед на метку result
(команда jbe
, jump if below
or equal, переходит на указанную метку, если беззнаковое сравнение на меньше
или равно было истинным, т. е. EDX ≤ 2):
cmp edx, 2 ; F_1 or F_2? we already have the answer
jbe result
В основном цикле вычисления мы будем складывать два предыдущих члена
последовательности до тех пор, пока не доберемся до искомого n-го члена.32″
fin: exit
end Start
Поместим текст программы в файл fib.asm
в рабочем каталоге, после чего
соберем и запустим ее при помощи mkr
:
C:\work>mkr fib.asm
Assembling: fib.asm
enter n: 10
F_10 = 55
Генерация листинга
Чтобы узнать, как выглядит машинный код, в который была оттранслирована наша
программа, мы можем запросить построение файла листинга, который будет
содержать, в числе различной дополнительной информации, адреса команд и
машинный код.
Для генерации листинга используется аргумент /Fl
(регистр символов важен). Получим листинг программы fib
:
C:\work>ml /c /coff /Fl fib.asm
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997. All rights reserved.
Assembling: fib.asm
Файл листинга будет называться fib.lst
. Откроем его в текстовом редакторе.
Листинг состоит из двух страниц — собственно листинга и таблиц макросов, процедур, структур и т. д. Остановимся подробнее на листинге:
include console.inc
C .NOLIST
C .LIST
C
C
C ;include masm32.inc
C
C includelib masm32.lib
C includelib user32.lib
C includelib kernel32.lib
C includelib msvcrt.lib
C includelib io_proc.lib
C
C
00000000 .code
00000000 Start:
outstr "enter n: "
inint edx ; F_n to calculate
0000003C B8 00000001 mov eax, 1 ; F_2=F_k
00000041 8B D8 mov ebx, eax ; F_1
00000043 B9 00000002 mov ecx, 2 ; k=2
00000048 83 FA 02 cmp edx, 2 ; F_1 or F_2? we already have the answer
0000004B 76 0E jbe result
0000004D 93 nx: xchg ebx, eax ; ebx <- F_k
0000004E 03 C3 add eax, ebx ; eax <- F_k+1
00000050 0F 82 0000014D jc overflow
00000056 41 inc ecx ; k = k + 1
00000057 3B CA cmp ecx, edx
00000059 72 F2 jb nx
0000005B result: outstr "F_"
outword edx
outstr " = "
outword eax
newline
000001A1 EB 33 jmp fin
000001A3 overflow:
outstr "The result exceeds 2^32"
000001D6 fin: exit
end Start
Листинг состоит из трех колонок: первая содержит адреса (точнее, смещения в секции), вторая — сгенерированный машинный код и третья — соответствующие строчки исходного кода программы.
Примечание. Обратите внимание, что вызовам макросов никакого машинного
кода в листинге не соответствует. Это происходит потому, что в console.inc
генерация листинга для макросов подавлена. Ее можно снова включить директивой
.LISTMACRO
.
По листингу сразу видно, что команда mov
формата «регистр — константа»
занимает 5 байт (1 байт на код операции и обозначение формата и
регистра-приемника, плюс 4 байта на сам непосредственный операнд), а команда
формата «регистр — регистр» — только 2 байта.
Адреса в первой колонке «прыгают» от 0
к 3C
потому, что макросы outstr
и inint
раскрываются в некоторое количество машинных команд, которые
попросту не отражены в листинге.
Если обратить внимание на команду jbe result
, то видно, что переход указан
относительно, т. е. «вперед на 8 байт». Если прибавить 8 к адресу
следующей команды, 4Dh
, то получится 55h
— адрес метки result
.
Листинг для секции данных
Если в нашей программе будет присутствовать секция данных, то для нее также будет сгенерирован листинг, по которому будет видно содержимое памяти, которое будет отведено под каждую из наших переменных.
Например, пусть секция данных выглядит вот так:
.data
B db 15
W dw 15
D dd 15
B2 db -2
D2 dd -2
Pr db "ABCD"
X db 'A'
X2 db 41h
Тогда соответствующий фрагмент листинга будет таким:
00000019 .data
00000019 0F B db 15
0000001A 000F W dw 15
0000001C 0000000F D dd 15
00000020 FE B2 db -2
00000021 FFFFFFFE D2 dd -2
00000025 41 42 43 44 Pr db "ABCD"
00000029 41 X db 'A'
0000002A 41 X2 db 41h
Из этого примера хорошо видно, к чему приводит указание одинаковых начальных
значений для переменных разных размерностей, а также то, что символы
представляются их ASCII-кодами.
Справочник по ассемблеру макросов (Майкрософт)
-
- Чтение занимает 2 мин
В этой статье
Ассемблер Microsoft Macro Assembler (MASM) имеет целый ряд преимуществ по сравнению со встроенным кодом на языке ассемблера. MASM поддерживает язык макросов с такими возможностями, как циклическая обработка, арифметические операции и обработка текстовых строк. Кроме того, MASM обеспечивает более эффективный контроль над аппаратным обеспечением благодаря поддержке наборов инструкций для процессоров 386, 486 и Pentium. Кроме того, применение MASM позволяет сократить затраты времени и ресурсов памяти.
в этом разделе
ML и ML64-параметр командной строки
Описание параметров командной строки ML.exe и ML64.exe.
Сообщения об ошибках машинного обучения
Описание неустранимых и других ошибок и предупреждений, связанных с ML.exe.
Справочник по директивам
Ссылки на статьи, посвященные использованию директив в MASM.
Справочник по символам
Ссылки на статьи, посвященные использованию символов в MASM.
Справочник по операторам
Ссылки на статьи, посвященные использованию операторов в MASM.
Руководства по программированию для производителей процессоров
Ссылки на веб-сайты, которые могут содержать полезную информацию по программированию для процессоров, производимых, продаваемых или поддерживаемых не корпорацией Майкрософт.
MASM для x64 (ml64.exe)
Сведения о способах создания выходных файлов для процессоров с архитектурой x64.
Грамматика MASM BNF
Формальное описание BNF для MASM для x64.
C++ в Visual Studio
Ссылки на различные разделы документации по Visual Studio и Visual C++.
См. также раздел
Встроенные функции компилятора
x86Intrinsics
Встроенные функции x64 (amd64)
assembly — вызывая макросы в MASM
Я нахожусь в неприятности. Макросы MASM, похоже, не ведут себя как макросы C вообще. В C есть препроцессор, который обрабатывает текст как последовательность токенов, и вы можете вызывать вызов макросов где угодно. В MASM это не так. Я не могу найти подходящий документ для этого, но вы не можете вызвать макрос внутри директивы, верно? За исключением случаев, когда это входит в парс в другом вызове макроса? Или когда он появляется внутри макроса def? Это единственные исключения? Где спецификация / документ?
Хорошо, что я пытаюсь сделать, это:
external macro fnames:VARARG
extern stdin:qword,
for fname, <fnames>
fname&_ptr: qword,
endm
endm
external puts, fgets, printf, srand, rand
И я получаю: синтаксическая ошибка: для
Кажется, он уже интерпретирует директиву extern и находит макрос for внутри которого не разрешено => синтаксическая ошибка.
Какого черта я могу сделать? Вы можете разделить директиву на несколько строк, если заканчиваете каждую строку запятой, верно? Но если он все равно не интерпретирует вызовы макросов, в этом нет никакой помощи.
Я обнаружил, что я могу сделать:
external macro fnames:VARARG
extern stdin:qword
for fname, <fnames>
extern fname&_ptr: qword
endm
endm
external puts, fgets, printf, srand, rand
Но умножение директивы не всегда возможно. Возьмите директиву proc f.ex. Я не могу использовать заголовок proc для одного и того же процесса несколько раз.
Есть ли разумное решение этой дилеммы? Можно ли использовать макросы MASM для вывода одной строки? Я не знаю как Спешите на помощь, все вы, кретины, там! 🙂 Cheerioh
0
Henrik
21 Янв 2017 в 01:31
2 ответа
Лучший ответ
Подпись EXTERN
директива есть
EXTERN [[ langtype]] name [[(altid)]] : type
[[, [[langtype]] name [[(altid)]] : type]]...
Таким образом, вы можете создать однострочник с помощью CATSTR
директивы вот так:
external MACRO fnames:VARARG
txt textequ <stdin: qword>
for fname, <fnames>
txt CATSTR txt, <, &fname&_ptr: qword>
endm
extern txt
endm
external puts, fgets, printf, srand, rand
Результат выводится в одну строку .
extern stdin: qword, puts_ptr: qword, fgets_ptr: qword, printf_ptr: qword, srand_ptr: qword, rand_ptr: qword
Кроме того, если вы хотите использовать выходные данные MACRO
в качестве входных данных для другого MACRO
, параметра или выражения, вы можете использовать EXITM
(https://msdn.microsoft.com/en-us/library/tb4885hc (v = vs.80) .aspx) директива.
4
zx485
21 Янв 2017 в 00:31
Вот что я придумал. Очень похоже на вышесказанное:
functions macro fnames:VARARG
local list
list textequ <>
for fname, <fnames>
list catstr list, <,fname&_ptr: qword>
endm
list substr list, 2
exitm list
endm
extern stdin:qword, functions(puts, fgets, printf, srand, rand)
Оно работает! Таким образом, действительно можно вызывать макро-функции изнутри директив. Это должны быть макрофункции, а не макропроцедуры.
1
Henrik
21 Янв 2017 в 00:55
Макросыконсольного вводаinput и inkey
Макрос
input()вызываетсяфункционально, в отличие отprint.
Практически это означает, что вызовinput()должен
быть операндом выражения, в которое он
возвращает вычисляемое им значение.
Еще одна особенность функционального
синтаксиса – перечисление параметров
приводится в скобках, а не списком после
имени макроса.
Макрос
input()получает
с клавиатуры текст, пишет его в
автоматически создаваемую строковую
переменную и возвращает адрес этой
переменной.
mov
lpstring, input()
Параметром
макроса input() может выступать
строка-подсказка для ввода. Эта строка
может включать список строк в кавычках
и байтов, задаваемых числами:
mov
lpstring, input(«Введите число «,62,» «)
Здесь
62 – это код символа «>».
Преобразование строки в число
Как
говорилось выше, консольный ввод-вывод
осуществляется исключительно как
ввод-вывод текстов. Поэтому, если вы
вводите с клавиатуры «94», то это строка
из символов «9» и «4», а не число 94,
представляемое в памяти в двоичном
виде. Поэтому, если нам нужно ввести в
программу число94, то это придется
делать в два шага: 1) Ввести строку «94»,
2) Преобразовать строку «94» в число 94.
Для этого существует много макросов вmacros.asm, мы
рассмотрим только два.
Функционально
вызываемый макрос a2ud()
читает строку, задаваемую указателем.
Алгоритм считает ее десятичной записью
числа, и преобразует ее в числовое
значение. Это число пишется в автоматически
создаваемую переменную типаDWORD(4 байта). Адрес этой переменной и есть
значение, возвращаемое этим макросом:
mov
EDX,
a2ud(lpNumString)
; в EDX
— адрес числа
Второй
макрос имеет имя h2ud()и отличается отa2ud()только тем, что рассматривает преобразуемую
в число строку как запись числа в
16-ричном формате. Всё остальное совпадает.
Еще
одна часто встречающаяся задача ввода
– получение одиночного символа нажатием
клавиши на клавиатуре. Например, чтобы
задержать окно консоли для демонстрации
результатов работы программы.
Эту
задачу рещает процедурно вызываемый
макрос inkeyс необязательным
параметром (текст в кавычках и/или байты
числами), играющим роль приглашения,
вроде «Продолжить по кнопке». Примеры
вызовов:
inkey
inkey
NULL
Inkey «Жду кнопочку.»
Просто
inkeyпокажет приглашение «по умолчанию»,
скорее всего это будет «Press a key to
continue …». Вызовinkey
NULL
ничего не напишет, буде просто
мигать курсором и ждать нажатия. Вызовinkey
«Жду кнопочку.» выведет
текст-операнд и будет ждать кнопки.
Примечание
А.Код нажатого символа должен, по
замыслу авторов макроса, возвращаться
в ЕАХ. Увы, ребята пропустили ошибочку.
Не возвращает.» с кодом 94, и следующие за ним
три ноль-символа (ноль-символ, 255-символ
и 32-символ, это пробел не имеют визуального
изображения).
Функционально
вызываемый макрос str$(число)
возвращает адрес строки, в которую
преобразуется число-аргумент. Например
Mov
EDX,
1234
print
«Вывод
десятичного представления числа «
print
str$(EDX),13,10
Два
вызова printприходится
писать потому, что указатель выводимой
строки (второй вызов) по его спецификации
должен быть только первым операндом.
Из примера понятно, чтоstr$()
формирует строку, которая показывает
число в 10-чном формате.
Второй
макрос – hex$() – формирует
16-ричное строковое представление числа.
В остальном он аналогиченstr$().
Описанных
макросов достаточно для ввода-вывода
текстовых и целочисленных данных в
учебных задачах нашего курса. Если вас
интересуют другие макросы, читайте
d:\MASM32\macros\macros.asmи d:\MASM32\help\hlhelp.chm.
Макрокоманды в ассемблере
При программировании достаточно серьезной задачи, появляются повторяющиеся участки кода. Они могут быть небольшими, а могут занимать и достаточно много места. В последнем случае эти фрагменты будут существенно затруднять чтение текста программы, снижать ее наглядность, усложнять отладку и служить неисчерпаемым источником ошибок. В языке ассемблера есть несколько средств, решающих проблему дублирования участков программного кода. К ним относятся:
- макроассемблер;
- механизм процедур;
- механизм прерываний.
Макрокоманда представляет собой строку, содержащую некоторое символическое имя — имя макрокоманды, предназначенную для того, чтобы быть замещенной одной или несколькими другими строками. Имя макрокоманды может сопровождаться параметрами. Для написания макрокоманды вначале необходимо задать ее шаблон-описание, который называют макроопределением.
Синтаксис макроопределения следующий:
ИмяМакрокоманды macro СписокФормальныхАргументов
ТелоМакроопределения
endm
Макроопределение обрабатывается транслятором следующим образом. Для того чтобы использовать описанное макроопределение в нужном месте программы, оно должно быть активизировано с помощью макрокоманды указанием следующей синтаксической конструкции:
ИмяМакрокоманды СписокФактическихАргументов
Результатом применения данной синтаксической конструкции в исходном тексте программы будет ее замещение строками из конструкции ТелоМакроопределения. Но это не простая замена. Обычно макрокоманда содержит некоторый список аргументов — СписокФактическихАргументов, которыми корректируется макроопределение. Места в теле макроопределения, которые будут замещаться фактическими аргументами из макрокоманды, обозначаются с помощью так называемых формальных аргументов. Таким образом, в результате применения макрокоманды в программе формальные аргументы в макроопределении замещаются соответствующими фактическими аргументами; в этом и заключается учет контекста. Процесс такого замещения называется макрогенерацией, а результатом этого процесса является макрорасширение.
Макрокоманды в ассемблере схожи с директивой #define в языке Си.
Существует три варианта размещения макроопределений:
- в начале исходного текста программы, до кода и данных с тем, чтобы не ухудшать читаемость программы. Этот вариант следует применять в случаях, если определяемые макрокоманды актуальны только в пределах одной этой программы;
- в отдельном файле. Этот вариант подходит при работе над несколькими программами одной проблемной области. Чтобы сделать доступными эти макроопределения в конкретной программе, необходимо в начале исходного текста этой программы записать директиву include ИмяФайла, например:
.586
.model flat, stdcall
include show.inc ;сюда вставляется текст файла show.inc - в макробиблиотеке. Универсальные макрокоманды, которые используются практически во всех программах целесообразно записать в так называемую макробиблиотеку. Сделать актуальными макрокоманды из этой библиотеки можно также с помощью директивы include. Недостаток этого и предыдущего способов в том, что в исходный текст программы включаются абсолютно все макроопределения. Для исправления ситуации можно использовать директиву purge, в качестве операндов которой через запятую перечисляются имена макрокоманд, которые не должны включаться в текст программы. К примеру:
include iomac.inc
purge outstr,exitВ данном случае в исходный текст программы перед началом трансляции MASM вместо строки include iomac.inc вставит строки из файла iomac.inc. Но вставленный текст будет отличаться от оригинала тем, что в нем будут отсутствовать макроопределения outstr и exit.
Каждый фактический аргумент представляет собой строку символов, для формирования которой применяются следующие правила:
- Строка может состоять:
— из последовательности символов без пробелов, точек, запятых, точек с запятой;
— из последовательности любых символов, заключенных в угловые скобки: <…>. В этой последовательности можно указывать как пробелы, так и точки, запятые, точки с запятыми. - Для того чтобы указать, что некоторый символ внутри строки, представляющей фактический параметр, является собственно символом, а не чем-то иным, например, некоторым разделителем или ограничивающей скобкой, применяется специальный оператор !. Этот оператор ставится непосредственно перед описанным выше символом, и его действие эквивалентно заключению данного символа в угловые скобки.
- Если требуется вычисление в строке некоторого константного выражения, то в начале этого выражения нужно поставить знак %:
%КонстантноеВыражение
Значение КонстантноеВыражение вычисляется и подставляется в текстовом виде в соответствии с текущей системой счисления.
В процессе генерации макрорасширения транслятор ассемблера ищет в тексте тела макроопределения последовательности символов, совпадающие с теми последовательностями символов, из которых состоят формальные параметры. После обнаружения такого совпадения формальный параметр из тела макроопределения замещается соответствующим фактическим параметром из макрокоманды. Этот процесс называется подстановкой аргументов. В общем случае список формальных аргументов содержит не только перечисление формальных элементов через запятую, но и некоторую дополнительную информацию. Полный синтаксис формального аргумента следующий:
ИмяФормальногоАргумента: Тип
где Тип может принимать значения:
- REQ – требуется обязательное явное задание фактического аргумента при вызове макрокоманды;
- =<ЛюбаяСтрока> — если аргумент при вызове макрокоманды не задан, то в соответствующие места в макрорасширении будет вставлено значение по умолчанию, соответствующее значению ЛюбаяСтрока. Символы, входящие в ЛюбаяСтрока, должны быть заключены в угловые скобки.
Но не всегда ассемблер может распознать в теле макроопределения формальный аргумент. Это, например, может произойти в случае, когда он является частью некоторого идентификатора. В этом случае последовательность символов формального аргумента отделяют от остального контекста с помощью специального символа &. Этот прием часто используется для задания модифицируемых идентификаторов и кодов операций. Например,
.686p
.model flat, stdcall
def_table macro t:REQ, len:=<1>
tabl_&t d&t len dup(5)
endm
.data
def_table d, 10
def_table b
.code
main proc
mov al, [tabl_b]
mov ah, [tabl_b+1]
mov ebx, [tabl_d]
ret
main endp
end main
После трансляции текста программы, содержащего строки сегмента данных, получится
def_table d, 10 ;tabl_d dd 10 dup(5)
def_table b ;tabl_b db 1 dup(5)
Заметим, что строка программы
mov ah, [tabl_b+1]
поместит в ah число отличное от 5, поскольку память для tabl_b распределена только под 1 элемент массива длиной 1 байт со значением 5.
Символ & можно применять и для распознавания формального аргумента в строке, заключенной в кавычки «».
Если тело макроопределения содержит метку или имя в директиве резервирования и инициализации данных, и в программе данная макрокоманда вызывается несколько раз, то в процессе макрогенерации возникнет ситуация, когда в программе один идентификатор будет определен несколько раз, что, естественно, будет распознано транслятором как ошибка. Для выхода из подобной ситуации применяют директиву local, которая имеет следующий синтаксис:
local CписокИдентификаторов
Эту директиву необходимо задавать непосредственно за заголовком макроопределения. Результатом работы этой директивы будет генерация в каждом экземпляре макрорасширения уникальных имен для всех идентификаторов, перечисленных в CписокИдентификаторов. Эти уникальные имена имеют вид ??хххх, где хххх — шестнадцатеричное число. Для первого идентификатора в первом экземпляре макрорасширения хххх=0000, для второго — хххх=0001 и т. д. Контроль за правильностью размещения и использования этих уникальных имен берет на себя транслятор.
Для примера использования макроопределений рассмотрим программу вывода имени в диалоговое окно.
.686P
.MODEL FLAT, STDCALL
PrintName macro Name
local STR1, STR2, METKA
jmp METKA
STR1 DB «Программа»,0
STR2 DB «Меня зовут: &Name «,0
METKA:
PUSH 0
PUSH OFFSET STR1
PUSH OFFSET STR2
PUSH 0
CALL MessageBoxA@16
endm
Init macro
EXTERN MessageBoxA@16:NEAR
endm
Init
.CODE
START:
PrintName <Лена>
PrintName <Таня>
RET
END START
Результат выполнения
При использовании макроопределений код программы становится более читаемым.
Функционально макроопределения похожи на процедуры. Сходство их в том, что и те, и другие достаточно один раз описать, а затем вызывать их многократно специальным образом. Различия макроопределений и процедур в зависимости от целевой установки можно рассматривать и как достоинства, и как недостатки:
- в отличие от процедуры, текст которой неизменен, макроопределение в процессе макрогенерации может меняться в соответствии с набором фактических параметров. При этом коррекции могут подвергаться как операнды команд, так и сами команды. Процедуры в этом отношении менее гибки;
- при каждом вызове макрокоманды ее текст в виде макрорасширения вставляется в программу. При вызове процедуры микропроцессор осуществляет передачу управления на начало процедуры, находящейся в некоторой области памяти в одном экземпляре. Код в этом случае получается более компактным, хотя быстродействие несколько снижается за счет необходимости осуществления переходов.
Назад
Назад: Язык ассемблера
Сборка
— Что такое макросы в MASM?
Из Руководства программиста MASM 6.1:
Макрос — это символическое имя, которое вы даете серии символов (
текстовый макрос) или к одному или нескольким операторам (макропроцедура или
функция).
Когда каждый раз, когда вы используете макрос в коде, например, вы пишете prnstr buf2
в какой-либо строке, он будет заменен на этапе сборки инструкциями из определения макроса, то есть так, как вы написали бы в исходном коде там три строки:
мов ах, 09ч
lea dx, buf2; аргумент msg был заменен значением buf2
int 21h
Так что хороший макрос избавит вас от набора текста и может несколько улучшить читаемость исходного кода.
Сейчас я обычно не одобряю использование макросов в сборке, особенно новичками, и я попытаюсь объяснить причины, почему в следующем тексте, но часть выше ответит на ваш вопрос, и если он не ответил на ваш вопрос, позвольте мне знать в комментариях.
Макросы несколько запутывают машинный код, который будет сгенерирован, когда вы выполняете анализ кода какого-либо источника и видите prnstr buf2
, вы должны также открыть определение макроса, чтобы проверить, имеет ли сгенерированный код в целом смысл, i .е. если код вокруг не ожидал, что, например, значение в dx
будет сохранено, а затем более поздний программист добавил prnstr
, забыв, что он действительно изменяет значение dx
. Таким образом, обзор становится более громоздким.
Эта тема продолжается также в отладчике, в отладчике вы видите дизассемблированный реальный машинный код, поэтому вы увидите все эти настоящие инструкции вместо исходного макроса, используемого в исходном коде. Из-за чего может быть немного сложнее быстро понять, где вы находитесь в (исходном) коде, какую часть вы отлаживаете и откуда пришли инструкции.Кроме того, если конкретная макро-инструкция не подходит и вам нужно «исправить» код, это может быть немного сложнее, чем изменение фактической инструкции в коде (поскольку любое изменение в макросе будет распространяться также на все другие места, где оно используется).
Кроме того, макрос сохраняет только строки исходного кода, но не окончательный машинный код, поэтому чрезмерное их использование без полного понимания эффекта может привести к получению двоичного файла намного большего размера, чем вы ожидали.
Наконец, макросы — это особенность конкретного ассемблера (если производитель ассемблера намеренно не пытается быть совместимым с другим поставщиком, например, TASM способен скомпилировать почти все исходники MASM и использовать это в промо-текстах как «аргумент в пользу продажи»), в то время как инструкции определяются ЦП, поэтому в случае, если ваша целевая платформа (ЦП + ОС) имеет много конкурирующих ассемблеров, источники без макросов могут быть более совместимы с разными ассемблерами (хотя часто есть также крошечные биты несовместимости синтаксиса, что делает задачу «портирования» требовать ручного редактирования источника в любом случае).
Конкретный prnstr
кажется мне разумным, хотя, если бы я создавал какой-нибудь пример для новичков, я бы все равно писал эти инструкции каждый раз в коде — он используется 5 раз, 5 + 5 строк против 5×3 = 15 строк = только Исходный код на 5 строк короче, IMO того не стоит, так как «написание кода» обычно занимает около 10-20% времени разработки и экономию на записи, что также не экономит чтение / просмотр кода и исправление ошибок + обслуживание (эти два часто бывают множественными. раз больше времени разработки, чем написание), в конечном итоге только усугубляют ситуацию.
Распространенный образец (неправильного) использования макросов новичками заключается в том, что они принимают их за процедуры. Для процедур процессор x86 имеет встроенную поддержку в виде таких инструкций, как call
и ret
, поэтому в случае более длинных фрагментов кода вы можете использовать их вместо макроса, они упрощают чтение кода, как и каждый x86. asm-кодер должен их знать, и они видны при разборке в отладчике точно так же, как был написан исходный код.
Теперь макросы, конечно же, являются важной частью того, что сделало MASM отличным инструментом для программистов, поэтому у них, безусловно, есть свои преимущества:
производительность по сравнению с вызовом процедуры… пара команд вызов
+ вызов
требует крошечного времени на выполнение, в то время как макрос вводит инструкции непосредственно в исходный поток. Если мы говорим о каком-то интенсивном коде внутреннего цикла, выполняемом миллионы раз в секунду, то макрос вместовызов
может иметь некоторое измеримое значение, иногда улучшая результат (или хуже). Но в других случаях это бессмысленно для современных x86, поскольку они выполнят вызов/ ответ
за минимальное время, а более короткий двоичный файл может принести преимущества, так как не засоряет кеш инструкций, как раздутый расширенный код.хороший способ создавать разные варианты кода для разных сборок. Вы можете скрыть в макросе, например, другую логику кода
LOG (..)
, который пуст в производстве, но создает журнал отладки в отладочных сборках или помещает третий аргумент для вызовов процедур вrcx
илиrdx
, в зависимости от целевая архитектура и т.д …использование новых кодов операций (пока не поддерживаемых ассемблером) удобным способом
И, вероятно, многие другие, макросы являются мощным инструментом, своего рода превращением низкоуровневой сборки в язык программирования среднего уровня.
Опять же, если вам нужен язык ассемблера сегодня, он чаще всего нужен для настройки производительности. Это означает переписать крошечный бит другого кода, написанного на языке программирования высокого уровня, обрабатывающего большой объем данных (узкое место, обнаруженное при профилировании исходного кода), и в этот момент вам обычно не нужны какие-либо функции «среднего уровня», поскольку вам нужно просто точно создать «горячую» часть кода внутреннего цикла, вероятно, размером в несколько десятков / сотен строк сборки, и соединить ее с исходным кодом, и вы, вероятно, не найдете какого-либо интенсивного использования макросов в такой случай.
Я также могу представить себе множество случаев, когда макросы могут помочь (если вы, например, пишете несколько микротестов, и каждый цикл тестирования имеет определенные идентичные части, но вы не хотите, чтобы они были частью «процедуры», тогда макросы идеально подходят) и т.д …
Но если вы только изучаете основы сборки, вам вообще не нужны макросы, а лучше потратьте свое время на изучение теории архитектуры компьютера и отработку самой сборки.
Сборка
— MASM — Различия между процедурами и макросами
Макрос — это просто замена текста.Таким образом, каждый раз, когда вы «вызываете» макрос, этот вызов будет заменен полным содержимым макроса во время компиляции. Когда вы вызываете процедуру, обычно есть только один экземпляр этой процедуры, поэтому вы переходите в другое место в программе.
Рассмотрим следующий пример:
.586
.model flat, stdcall
вариант casemap: нет
.код
; Умножьте аргумент на 3/2
Mul3_2 MACRO reg
леа рег, [рег + рег * 2]
shr reg, 1
ENDM
; Умножьте аргумент на 3/2 и верните в eax
Mul3_2Proc PROC arg: DWORD
mov eax, [аргумент]
леа eax, [eax + eax * 2]
shr eax, 1
Ret
Mul3_2Proc ENDP
основной ПРОЦЕССОР
Mul3_2 eax
Mul3_2 EBX
invoke Mul3_2Proc, eax; Эквивалентно push eax / call Mul3_2Proc
вызвать Mul3_2Proc, ebx
основной ENDP
КОНЕЦ основной
Если мы позволим препроцессору MASM расширить наш макрос, мы получим следующее:
.586
.model flat, stdcall
вариант casemap: нет
.код
; Умножьте аргумент на 3/2 и верните в eax
Mul3_2Proc PROC arg: DWORD
mov eax, [аргумент]
леа eax, [eax + eax * 2]
shr eax, 1
Ret
Mul3_2Proc ENDP
основной ПРОЦЕССОР
леа eax, [eax + eax * 2]
shr eax, 1
Lea ebx, [ebx + ebx * 2]
shr ebx, 1
invoke Mul3_2Proc, eax; Эквивалентно push eax / call Mul3_2Proc
вызвать Mul3_2Proc, ebx
основной ENDP
КОНЕЦ основной
Как вы можете видеть, в двух местах, где мы использовали макрос Mul3_2
, содержимое этого макроса было вставлено, а reg
заменено тем, что мы передали в качестве аргумента макросу.Сам макрос больше не существует в нашем коде, потому что он выполнил свою задачу.
С другой стороны, два случая вызова процедуры Mul3_2Proc
остаются неизменными. Во время выполнения произойдет переход ( вызов
) из каждого из этих мест в Mul3_2Proc
, который, в свою очередь, вернет ret
urn обратно к месту вызова, когда это будет сделано.
Что-то, чего вы могли не знать о макросе в MASM
Введение
Макрос — это символьное имя, которое вы даете серии символов, называемых текстовым макросом, или присваиваете одному или нескольким операторам, называемым макропроцедурой или функцией.По мере того как ассемблер оценивает каждую строку ваших программ, он просматривает исходный код на предмет имени ранее определенного макроса, а затем заменяет определения макроса на имя макроса. Макропроцедура — это именованный блок операторов языка ассемблера. После определения его можно вызывать (вызывать) много раз, даже получая разные аргументы. Таким образом, вы можете избежать многократного написания одного и того же кода местами.
В этой статье мы поговорим о некоторых случаях использования, которые не обсуждаются подробно или четко не задокументированы.Мы покажем и проанализируем примеры в среде Microsoft Visual Studio IDE. Темы будут по-разному связаны с директивой ECHO
, проверкой типа и размера параметра в макрос-процедуре и генерацией памяти в повторениях с текущим счетчиком местоположения $
.
Все материалы, представленные здесь, были получены мной в течение многих лет [2]. Таким образом, для чтения этой статьи предполагается общее понимание языка ассемблера Intel x86-64 и требуется знание Visual Studio 2010 или более поздней версии.Желательно, прочитав учебник, например [3]; или «Руководство программиста MASM» [5], которое было создано в 1992 году Microsoft, но до сих пор так ценно в сегодняшнем обучении MASM. Если вы посещаете курс программирования на языке ассемблера, это может быть дополнительная литература для чтения или изучения.
Использование ECHO в окне вывода
Согласно MSDN, директива ECHO
отображает сообщение на стандартное устройство вывода. В Visual Studio вы можете использовать ECHO
для отправки строки на панель вывода среды IDE для отображения сообщений сборки / компиляции, таких как предупреждение или ошибка, аналогично тому, что делает MSBuild.
Упрощенный фрагмент кода для тестирования ECHO
будет выглядеть так:
,386 .model flat, stdcall Протокол ExitProcess, dwExitCode: DWORD mTestEcho МАКРОС ЭХО ** Тестовое эхо без ничего ** ENDM .код основной ПРОЦЕССОР mTestEcho вызвать ExitProcess, 0 основной ENDP КОНЕЦ основной
Этот код отлично работал в VS 2008. К сожалению, начиная с VS 2010, директива ECHO
не приводит к записи в окно вывода в Visual Studio. Как видно из [4], это не так, если вы не настроите его для генерации подробного вывода для сборки вашего кода.Для этого вам нужно пойти:
Инструменты
-> Параметры-> Проекты и решения-> Сборка и запуск
В раскрывающемся списке «Подробность вывода сборки проекта MSBuild» выберите параметр « Подробные » (обратите внимание, что по умолчанию установлено « Минимальное »):
Чтобы протестировать макрос, вам нужно только скомпилировать отдельный файл .ASM , а не собирать весь проект. Просто щелкните файл правой кнопкой мыши и выберите Compile :
Чтобы проверить, вы должны смотреть на дисплей в панели вывода VS.Директива ECHO
действительно работает, однако интересующее вас сообщение «** Test Echo with Nothing **» похоронено где-то в сотнях строк, созданных MSBuild. Вы должны утомительно поискать, чтобы найти:
При изучении программирования на MASM Assembly это определенно не лучший способ практиковаться. Я предложил использовать текст «Ошибка:» или «Предупреждение:» с ECHO
, оставив при этом параметр вывода MSBuild по умолчанию «Минимальный» без изменений.
1.Вывод как ошибка
Просто добавьте « Error:
» в оператор ECHO
в макрос и назовите его mTestEchoError
:
mTestEchoError МАКРОС Ошибка ECHO: ** Тестовое эхо с ошибкой ** ENDM
Теперь вызовем mTestEchoError
в основном PROC
. Компилируя код, вы можете увидеть минимальный вывод, такой краткий, как показано ниже. Обратите внимание, что из-за ошибки здесь результат был неудачным.
2.Вывод как предупреждение
Просто добавьте « Предупреждение:
» в оператор ECHO
и назовите его mTestEchoWarning
:
мПоказать EchoWarning MACRO Предупреждение ECHO: ** Тестовое эхо с предупреждением ** ENDM
Затем вызовите mTestEchoWarning
в основном PROC
и скомпилируйте его, вы можете увидеть минимальный результат намного проще, как показано ниже. Поскольку обозначено только предупреждение, компиляция прошла успешно.
Как вы знаете, таким образом, директива ECHO
генерирует краткие и ясные сообщения без необходимости поиска выходных данных.Образец находится в TestEcho.asm для загрузки.
Проверка типа и размера параметра
Когда вы передаете аргумент макропроцедуре, процедура получает его от параметра, хотя это просто текстовая подстановка. Обычно вы проверяете некоторые условия в параметре, чтобы что-то сделать соответствующим образом. Поскольку это происходит во время сборки, это означает, что ассемблер выберет некоторые инструкции, если условие выполнено, иначе предоставит другие инструкции для неудовлетворенных, если это необходимо.Определенно, вы можете проверить постоянные значения аргументов в виде строки или числа. Еще одна полезная проверка, возможно, основана на типе или размере параметра для регистров и переменных. Например, одна макропроцедура принимает только целые числа без знака и игнорирует знаковые, в то время как вторая макрокоманда может работать с 16- и 32-битными без 8- и 64-битных аргументов.
1. Аргумент как переменная памяти
Определим здесь три переменные:
. Данные swVal МЕЧ 1 wVal WORD 2 SDVAL SDWORD 3
При применении операторов TYPE
и
SIZEOF
к этим переменным мы получаем просто:
mov eax, ТИП swVal mov eax, РАЗМЕР swVal mov eax, ТИП wVal mov eax, РАЗМЕР wVal mov eax, ТИП sdVal mov eax, РАЗМЕР sdVal
Как видно выше, нет числовой разницы ни между TYPE
и SIZEOF
, ни между WORD
и SWORD
.Все первые четыре инструкции перемещают счетчик байтов с 2
на EAX
. Однако TYPE
может не только возвращать количество байтов. Давайте попробуем проверить тип и размер SWORD
с параметром par
:
.
м Параметр ТИП МАКРО пар ЕСЛИ ТИП par EQ ТИП МЕЧ Предупреждение ECHO: ** TYPE par is TYPE SWORD. ЕЩЕ Предупреждение ECHO: ** ТИП par НЕ ТИП МЕЧА ENDIF ENDM mParameterSIZEOF MACRO par ЕСЛИ РАЗМЕР НОРМАЛЬНЫЙ РАЗМЕР МЕЧА Предупреждение ECHO: ** РАЗМЕР по номиналу - РАЗМЕР МЕЧА. ЕЩЕ Предупреждение ECHO: ** РАЗМЕР номинала НЕ РАЗМЕР МЕЧА. ENDIF ENDM
Затем вызов двух макросов с передачей указанных выше переменных
Предупреждение ECHO: --- Проверка TYPE и SIZEOF для wVal --- mParameterTYPE wVal mParameterSIZEOF wVal Предупреждение ECHO: --- Проверка TYPE и SIZEOF для swVal --- mParameterTYPE swVal mParameterSIZEOF swVal Предупреждение ECHO: --- Проверка TYPE и SIZEOF для sdVal --- mParameterTYPE sdVal mParameterSIZEOF sdVal
См. Следующие результаты в Выход :
Очевидно, что оператор TYPE
может использоваться для различения переданных аргументов со знаком или без знака, поскольку SWORD
и WORD
относятся к разным типам.В то время как SIZEOF
- это просто сравнение количества байтов, так как SWORD
и WORD
- это 2 байта. Последние две проверки означают, что тип SDWORD
не SWORD
, а размер SDWORD
составляет 4 байта, а не 2.
Кроме того, давайте проведем прямые проверки, поскольку здесь к именам типов данных также могут применяться два оператора:
mCheckTYPE MACRO ЕСЛИ ТИП МЕЧ ТИП ЭКВАЛАЙЗЕРА СЛОВО Предупреждение ECHO: ** TYPE SWORD EQ TYPE WORD ЕЩЕ Предупреждение ECHO: ** ТИП МЕЧА, НЕ ТИП СЛОВА EQ ENDIF ENDM mCheckSIZEOF МАКРО ЕСЛИ РАЗМЕР МЕЧА ЭКВАЛАЙЗЕР РАЗМЕР СЛОВА Предупреждение ECHO: ** РАЗМЕР МЕЧА ЭКВАЛАЙЗЕР РАЗМЕР СЛОВА ЕЩЕ Предупреждение ECHO: ** РАЗМЕР МЕЧА НЕ EQ РАЗМЕР СЛОВА ENDIF ENDM
Следующий результат интуитивно понятен и понятен:
2.Аргумент как регистр
Поскольку аргумент может быть регистром, давайте вызовем два предыдущих макроса, чтобы проверить его TYPE
и SIZEOF
:
м Параметр ТИП AL mParameterSIZEOF AL mParameterTYPE AX mParameterSIZEOF AX
Получаем такие сообщения:
Как мы видим здесь, для проверки типа ни AL
, ни AX
(даже 16-битный) не подписаны WORD
. Фактически, вы не можете применить SIZEOF
к регистру, что вызывает ошибку сборки A2009
.Вы можете проверить это напрямую:
mov ebx, РАЗМЕР al mov ebx, ТИП al
А какой тип для регистров? Ответ таков: по умолчанию все регистры беззнаковые. Просто сделайте это:
м ПараметрTYPE2 МАКРО пар IF TYPE par EQ WORD Предупреждение ECHO: ** TYPE par is WORD ЕЩЕ Предупреждение ECHO: ** номинал ТИП НЕ СЛОВО ENDIF ENDM
И звоните:
м ПараметрTYPE2 AL mParameterTYPE2 AX
Также обратите внимание, что я напрямую использую здесь имя типа данных WORD
, что эквивалентно использованию TYPE WORD
.
3. Пример из практики
Теперь давайте рассмотрим конкретный пример, который требует перемещения аргумента 8-, 16- или 32-разрядного целого числа в EAX
. Чтобы создать такой макрос, мы должны использовать либо инструкцию mov
, либо знаковое расширение movsx
в зависимости от размера параметра. Ниже приводится одно из возможных решений для сравнения типа параметра с требуемыми размерами. % OUT
аналогичен ECHO
в качестве альтернативы.
mParToEAX MACRO intVal ЕСЛИ ТИП intVal LE РАЗМЕР СЛОВА movsx eax, intVal ELSEIF TYPE intVal EQ SIZEOF DWORD mov eax, intVal ЕЩЕ % OUT Ошибка: ******************************************** ***************** % OUT Ошибка: аргумент intVal, передаваемый в mParToEAX, должен быть 8, 16 или 32 бит. % OUT Ошибка: ******************************************** ****************** ENDIF ENDM
Протестируйте его с разными размерами и типами переменных и регистров:
mParToEAX bVal mParToEAX swVal mParToEAX wVal mParToEAX sdVal mParToEAX qVal mParToEAX AH mParToEAX BX mParToEAX EDX mParToEAX RDX
Как и ожидалось, выходные данные показывают следующие сообщения, чтобы разумно отклонить qVal
.Также нормально сообщается об ошибке для RDX
, поскольку наш 32-разрядный проект не распознает 64-разрядный регистр.
Вы можете попробовать загружаемый код в ParToEAX.asm . Кроме того, давайте сгенерируем его файл листинга, чтобы увидеть, какие инструкции ассемблер создал для замены вызовов макросов. Как и ожидалось, bVal
, swVal
, wVal
и sdVal
хороши, но без qVal
; в то время как AH
, BX
и EDX
хорошо, но без RDX
:
00000000.данные 00000000 03 bVal БАЙТ 3 00000001 FFFC swVal МЕЧ -4 00000003 0005 wVal СЛОВО 5 00000005 FFFFFFFA sdVal SDWORD -6 00000009 qVal QWORD 7 0000000000000007 00000000. Код 00000000 main_pe PROC mParToEAX bVal 00000000 0F BE 05 1 movsx eax, bVal 00000000 R mParToEAX swVal 00000007 0F BF 05 1 movsx eax, swVal 00000001 R mParToEAX wVal 0000000E 0F BF 05 1 movsx eax, wVal 00000003 R mParToEAX sdVal 00000015 A1 00000005 R 1 mov eax, sdVal mParToEAX qVal mParToEAX AH 0000001A 0F BE C4 1 movsx eax, AH mParToEAX BX 0000001D 0F BF C3 1 movsx eax, BX mParToEAX EDX 00000020 8B C2 1 mov eax, EDX mParToEAX RDX 1 ЕСЛИ ТИП RDX LE РАЗМЕР СЛОВА AsmCode \ ParToEAX.asm (45): ошибка A2006: неопределенный символ: RDX mParToEAX (1): макрос, вызываемый из AsmCode \ ParToEAX.asm (45): Код основной строки 1 ЕЩЕ AsmCode \ ParToEAX.asm (45): ошибка A2006: неопределенный символ: RDX mParToEAX (3): макрос, вызываемый из AsmCode \ ParToEAX.asm (45): Код основной строки вызвать ExitProcess, 0 00000029 main_pe ENDP КОНЕЦ
Генерация данных в повторении
В этом разделе мы поговорим об использовании макросов для создания блока памяти, массива целых чисел в сегменте данных, а не о вызове макросов в коде.Мы покажем три способа создания одного и того же связанного списка: использование неизмененного счетчика местоположения $
, получение измененных значений из счетчика $
и вызов макроса в сегменте данных.
1. Использование неизмененного счетчика локации $
Я просто позаимствовал фрагмент LinkedList
из учебника [3], чтобы изменить его с восемью узлами как:
LinkedList -> 11h -> 12h -> 13h -> 14h -> 15h -> 16h -> 17h -> 18h -> 00h
Я добавил шесть дополнительных DWORD
s из 01111111h
в конце для заполнения, хотя они и не нужны, но их легко отформатировать в окне памяти для просмотра:
ListNode STRUCT NodeData DWORD? NextPtr DWORD? ListNode ENDS TotalNodeCount = 8 .данные Счетчик = 0 LinkedList LABEL PTR ListNode REPT TotalNodeCount Счетчик = Счетчик + 1 ListNodeENDM ListNode <0,0> DWORD 01111111h, 01111111h, 01111111h, 01111111h, 01111111h, 01111111h
Память создана. Заголовок списка - это метка LinkedList
, псевдоним, указывающий на 0x00404010
:
.
Каждый узел содержит 4-байтовый DWORD
для NodeData
и еще один DWORD
для NextPtr
.Как и Intel IA-32 с прямым порядком байтов, первое целое число в памяти 11 00 00 00
составляет 00000011
в шестнадцатеричном формате; а его следующий указатель 18 40 40 00
- 0x00404018
. Таким образом, две строки покрывают все восемь узлов списка. В третьей строке первый узел с двумя нулевыми DWORD
s действует как хвост (хотя и пустой узел). Сразу следует заполнение шести 01111111
.
Теперь посмотрим, что происходит с счетчиком текущего местоположения $
.Как указано в [3]:
Выражение ( $
+ Counter
* SIZEOF ListNode
) указывает ассемблеру умножить счетчик на размер ListNode
и добавить полученный результат к текущему счетчику местоположения. Значение вставляется в поле NextPtr
в структуре. [Интересно отметить, что значение счетчика местоположения ( $
) остается фиксированным в первом узле списка.]
Это действительно так, что значение $
всегда остается 0x00404010
без изменения на каждой итерации в блоке REPT
.Адрес NextPtr
, вычисленный с помощью ( $
+ Counter
* SIZEOF ListNode
), заставляет узел за узлом связываться вместе, чтобы в конечном итоге создать LinkedList
. Однако вы можете спросить, можем ли мы получить фактический текущий адрес памяти для использования в итерации? да. Вот оно.
2. Получение измененных значений из счетчика местоположения $
. Данные Счетчик = 0 LinkedList2 LABEL PTR ListNode REPT TotalNodeCount Счетчик = Счетчик + 1 ThisPointer = $ ListNodeENDM ListNode <0,0> DWORD 02222222h, 02222222h, 02222222h, 02222222h, 02222222h, 02222222h len = ($ - LinkedList) / ТИП DWORD
Эй, почти ничего не изменилось, кроме как назвать новую символическую константу ThisPointer
= $
, которая просто присваивает текущий адрес памяти $
ThisPointer
.Теперь мы можем использовать ThisPointer
в аналогичных вычислениях для инициализации поля NextPtr
объекта ListNode
с помощью более простого выражения ( ThisPointer
+ SIZEOF ListNode
). Это также заставляет узел за узлом связываться друг с другом, чтобы на этот раз создать LinkedList2
. Вы можете проверить LinkedList2
в памяти, 0x00404070
:
Чтобы различать первый LinkedList
, я позволил Counter + 20h
сделать его как:
LinkedList2 -> 21h -> 22h -> 23h -> 24h -> 25h -> 26h -> 27h -> 28h -> 00h
При сравнении двух блоков памяти оба выполняют одинаковые функции.Обратите внимание, что, наконец, я намеренно вычисляю len
, чтобы увидеть, сколько DWORD
было сгенерировано до сих пор.
len = ($ - LinkedList) / ТИП DWORD
В качестве интересного упражнения подумайте о значении len
в уме. В коде переместите len
в регистр для проверки.
3. Вызов макроса в сегменте данных
Создав третий связанный список, мы можем понять, что вы можете вызывать макрос не только в коде, но также и в сегменте данных.Для этой цели я определяю макрос с именем mListNode
с параметром start
, где просто инициализируется объект ListNode
. Чтобы различать два предыдущих, я делаю Counter + 30h
для NodeData
и назначаю NodePtr как ( start
+ Counter
* SIZEOF ListNode
).
. Данные Запуск mListNode MACRO Счетчик = Счетчик + 1 ListNode <Счетчик + 30h, (начало + Счетчик * SIZEOF ListNode)> ENDM LinkedList3 = $ Счетчик = 0 REPT TotalNodeCount mListNode LinkedList3 ENDM ListNode <0,0> DWORD 03333333h, 03333333h, 03333333h, 03333333h, 03333333h, 03333333h
Третий список выглядит так:
LinkedList3 -> 31h -> 32h -> 33h -> 34h-> 35h -> 36h-> 37h -> 38h-> 00h
Теперь мы извлекем урок из LinkedList2
, имея сначала LinkedList3
= $
.Обратите внимание, что я просто использую символическую константу LinkedList3
в качестве третьего заголовка списка вместо директивы LABEL
. Теперь я установил повторение REPT
только с одним вызовом макроса, передав адрес заголовка LinkedList3
в mListNode
. Вот и все! Смотрите память на 0x004040D0
:
Представьте, что если вы передадите $
в качестве аргумента mListNode
без LinkedList3
= $
?
4.Проверка адреса и просмотр связанного списка
Наконец, давайте объединим все поколения трех списков и запустим LinkedList.asm (доступно для загрузки). В сегменте кода я сначала извлекаю три адреса заголовков списка, как показано ниже:
mov edx, смещение LinkedList mov ebx, СМЕЩЕНИЕ LinkedList2 mov esi, OFFSET LinkedList3 mov eax, len
Как и ожидалось, EDX
, 00404010
для LinkedList
; EBX
, 00404070
для LinkedList2
; и ESI
, 004040D0
для LinkedList3
.Вся память трех списков соседствует друг с другом, как показано:
Уведомление, поскольку LinkedList3
является символическим, нам даже не нужно использовать здесь оператор OFFSET
. Давайте оставим ESI
для LinkedList3
и пройдемся по этому списку, чтобы увидеть все значения NodeData
с таким циклом:
NextNode: mov eax, (ListNode PTR [esi]). NextPtr cmp eax, 0 Je бросить mov eax, (ListNode PTR [esi]).NodeData mov esi, (ListNode PTR [esi]). NextPtr jmp NextNode выйти:
К сожалению, мы не задействовали какую-либо реализацию процедуры вывода, которую можно было бы вызвать здесь, чтобы показать EAX
, который перемещен NodeData
. Но при отладке простой установки там точки останова для просмотра EAX
должно быть достаточно для проверки от 31h
, 32h
,… до 38h
.
Сводка
Изучив приведенные выше примеры, мы выявили кое-что, чего вы, возможно, не знали о макросе в программировании на ассемблере MASM.Программа на ассемблере может выполняться с указанными Intel или AMD инструкциями во время выполнения. С другой стороны, MASM предоставляет множество директив, операторов и символов для управления и организации инструкций и переменных памяти во время сборки, аналогично предварительной обработке в других языках программирования. Фактически, со всеми функциями, сам макрос MASM может рассматриваться как вспомогательный или мини-язык программирования с тремя механизмами управления: последовательным, условным и повторением.
Однако некоторые варианты использования макроса MASM подробно не обсуждались. В этой статье мы впервые представили лучший способ вывода текста об ошибке или предупреждении, который упрощает отслеживание поведения макросов. Затем с помощью структур if
- else
мы представили, как проверить тип и размер параметра макроса, что является обычной практикой для аргументов памяти или регистра. Наконец, мы обсудили повторения макросов с тремя примерами для создания одного и того же связного списка, а также для лучшего понимания использования текущего символа локатора адресов $
.Загружаемый zip-файл содержит все образцы в файлах .asm . Файл проекта MacroTest.vcxproj был создан в VS 2010, но его можно открыть и обновить в любой последней версии VS.
Эта статья не касается горячих технологий, таких как .NET или C #. Ассемблер сравнительно традиционен, но без особых сенсаций. Но, пожалуйста, обратитесь к Индексу сообщества программистов TIOBE: рейтинг языка ассемблера в последнее время растет, а это означает, что разные типы языков ассемблера играют важную роль в разработке новых устройств.С академической точки зрения, программирование на языке ассемблера считается сложным курсом в компьютерных науках. Поэтому я надеюсь, что эта статья может служить примером решения проблем для студентов и, возможно, разработчиков.
Список литературы
История
- 26 февраля 2016 - Исходная версия опубликована
Использование макросов в MASM в качестве примера создания окна / Sudo Null IT News
Еще в 2001 году я потратил много времени на изучение ассемблера под Win32.Затем, после долгих мучений с написанием одного и того же кода сто раз, я решил написать для себя небольшую библиотеку макросов. В результате удалось достаточно серьезно облегчить нашу судьбу и уменьшить необходимость повторения огромных полотенец кода, при необходимости написать простую программу с одним окном.
Недавно наткнулся на эти проекты и решил выложить некоторые из них, может кому пригодится ...
Состав проекта
Итак, приступим. Прилагаемый ниже проект имеет следующую структуру.
\ Macros | Каталог макросов, используемых в приложении | |
Macros.Inc | Вот основные макросы, которые необходимы при написании любой программы Win32. Вот макросы для выделения памяти, упрощающие включение файлов, макросы для определения данных и так далее. | |
Window.Mac | Макросы, упрощающие создание окон | |
Status.Mac | Макросы для создания и использования статуса строки | |
Меню.Mac | Макросы для создания и использования меню | |
Quake.Bmp | Изображение для загрузки и отображения в окне программы | |
Scull.Ico | Значок изображения (просто осколок) | |
Файл определения ресурса | ||
Window.Asm | Главный файл программы | |
Window.Exe | Скомпилированная программа | |
WndExample.Asm | Этот файл содержит исходный код для обработки сообщений, поступающих в окно «Пример» нашей программы |
При запуске Window.Exe отображаемое окно будет выглядеть так:
Самая простая программа, без окна
include macros \ macros.inc
@Начинать
@ Использует kernel32
.код
WinMain Proc
вызвать ExitProcess, 0
WinMain Endp
Конец WinMain
Первая строка здесь - это включение основных макросов, затем макрос Start, который создает начало программы и заменяет информацию о модели памяти, используемом процессоре и так далее.Далее идет макрос Uses, он включает в программу необходимую библиотеку. В этом случае мы будем использовать kernel32.dll, поскольку он содержит функцию, которую мы используем для завершения процесса ExitProcess.
Ниже приведен блок кода, заданный с помощью .code , который содержит основную процедуру программы. На самом деле саму процедуру можно называть как угодно, а название WinMain я дал ей просто с бульдозера. Главное - это , что в конце файла есть строка End {Input_function_name_name}
Эта программа не несет никакой функциональной нагрузки, поэтому после запуска она ничего не будет делать - она просто завершает свою работу .Теперь исходный код программы в архиве:
include macros \ macros.inc
IDC_MAINSTATUS Equ 1
IDC_MENUEXIT Equ 10
@Начинать
@ Использует gdi32, user32, comctl32, kernel32
.xlist
включить макросы \ Menu.mac
включить макросы \ Window.mac
включить макросы \ Status.mac
.список
.данные?
hIcon Dd?
hBrush Dd?
hCursor Dd?
hImage Dd?
hInstance Dd?
@DefineMenu Меню
@DefineStatus Пример
@DefineWindow Пример
.код
; Основной программный цикл
WinMain Proc
mov hInstance, @Result (GetModuleHandle, NULL)
mov hIcon, @Result (LoadIcon, hInstance, 100)
mov hCursor, @Result (LoadCursor, NULL, IDC_ARROW)
mov hBrush, @Result (GetSysColorBrush, COLOR_APPWORKSPACE)
@CreateWindow Пример, hInstance, NULL, 'Example_wnd', \
WS_OVERLAPPED + WS_CAPTION + WS_SYSMENU + WS_VISIBLE, \
WS_EX_APPWINDOW, 'Пример', \
hIcon, hBrush, hCursor, NULL
@SetWndSize Пример, 700, 600
@MoveWnd Пример, 100, 100
@CreateMenu Меню
Меню @AppendMenu, «Выход», IDC_MENUEXIT
@AttachMenu Пример, меню
@CreateStatus Пример, пример, IDC_MAINSTATUS
@SetStatusParts Пример, 2,300, -1,0,0,0,0,0,0,0,0
@SetStatusText Example, 'Пример окна программы... ', 0, 0
@SetStatusText Пример, «CHEMI $ T Copyright (C) 2001», 0, 1
Пример @ProcessMsgs, ЛОЖЬ
@DestroyMenu Меню
@DestroyWindow Пример
вызвать ExitProcess, 0
WinMain Endp
Конец WinMain
Сейчас я поэтапно объясню, что происходит в этом источнике. Во-первых, включены макросы для реализации функций меню, строки состояния и окна. Они оформляются специальными командами макроассемблера .xlist (отключение листинга) и .list (включение листинга) это было сделано только для того, чтобы, если листинг был выдан макроассемблером, не было кода из этих файлов / только для полотенец с дополнительным кодом / Ниже приводится описание неинициализированного блока .данные данные ? , переменные в этом блоке не инициализируются, система просто выделяет память, не сбрасывая ее. Такие переменные использовать без инициализации чревато, ведь в памяти может быть все, что угодно. Здесь выделяется пространство для переменных, которые в первых строках метода WinMain принимают значения загруженных ресурсов и самого экземпляра приложения.
Макросы @DefineMenu, @DefineStatus и @DefineWindow инициализируют переменные, в которых хранятся параметры объектов / меню, статус строки и окна соответственно /
И после всех инициализаций происходит самое интересное.
Первые четыре строки
mov hInstance, @Result (GetModuleHandle, NULL)
mov hIcon, @Result (LoadIcon, hInstance, 100)
mov hCursor, @Result (LoadCursor, NULL, IDC_ARROW)
mov hBrush, @Result (GetSysColorBrush, COLOR_APPWORKSPACE)
Инициализировать переменные / экземпляр приложения, значок, курсор, кисть для рисования окна /. Он использует красивый макрос Result. Этот макрос выполняет указанный вызов API с переданными параметрами и возвращает содержимое регистра EAX, который служит для возврата результатов функции.Если этого макроса не было, каждая строка разбивалась на аналогичный код:
invoke GetModuleHandle, NULL
mov hInstance, eax
Макросы для создания и работы окна должны вызываться последовательно, @CreateWindow - создает окно, @SetWndSize - устанавливает размер окна, @MoveWnd перемещает окно в нужные координаты на экране, @ProcessMsgs выполняет основной цикл обработки сообщений, идущих в ваше окно, @DestroyWindow - удаляет окно. Когда вы создаете окно, вам необходимо создать файл с обработчиками событий для этого окна.В приведенном выше проекте это файл WndExample.Asm. Это имя задано так, чтобы файл обработчика событий автоматически включался маской Wnd .Asm
Я тогда не доделал макросы для создания меню и создания строки состояния, я сделал это только с нужной мне функциональностью.
Макросы меню:
@CreateMenu {Menu_name}
Создайте меню с желаемым именем
@AppendMenu {Menu_name}, {Menu item_name header}, {Message_code}
Добавляет пункт меню с желаемым заголовком.При нажатии на этот пункт меню код сообщения попадет в очередь сообщений.
@AttachMenu {WindowName}, {MenuName}
Добавляет меню в указанное окно.
Макросы для работы со строкой состояния / Требуется ComCtl32 /
@CreateStatus {String_Status_Name}, {Window_Name}, {String_Status ID}
Создание строки статуса для указанного окна
@SetStatusParts {String_StatusParts {String_StatusParts {String_StatusParts {String_StatusName}) of_parts}, {Part_width}, {}, {}, {}, {} / До десяти частей, последний указанный размер = -1, т.е.е. stretch /
Разделение на несколько частей, этот макрос можно было доработать, но как-то видимо я этого не сделал тогда
@SetStatusText {String_status_name}, {Text}, {Style / Не помню почему /}, { String_status_part}
Установка статуса для необходимой части String status
Файл обработчика событий
В этом файле указывается исходный код процедуры главного окна, в которой регистрируются определяемые пользователем обработчики и в которой также должны быть зарегистрированы обработчики событий меню.Каждый обработчик событий окна выглядит так:
@WndHandlerStart {window_name}, {handler_name}
mov eax, TRUE
@WndHandlerEnd {window_name}, {handler_name}
Основная процедура в вышеуказанном проекте конец файла и выглядит так:
@WndProcedureBegin Пример, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT
; Обработчики меню
@WndMenuHandler IDC_MENUEXIT, выход
; Пример пользовательского обработчика
@WndUserHandler Пример, WM_SIZING
@WndProcedureEnd Пример
Здесь назначен обработчик пункта меню, которому назначен обработчик Message_Code IDC_MENUEXIT с именем Exit.А также зарегистрирован пользовательский обработчик сообщений WM_SIZING. У настраиваемого обработчика событий должно быть имя обрабатываемого сообщения. Все события, которые предопределены в окне, можно просмотреть в файле Window.Mac в макросе @WndProcedureBegin. Список этих событий: Close, Paint, Help, Activate, Deactivate, SysCommand, Show, Hide, Create, Destroy, KeyDown, KeyUp, Resize, DblClick, MouseUp, MouseDown, WheelDown, WheelUp. Примеры этих обработчиков включены в исходный код проекта, и вы можете щелкнуть F1 в окне и вращать колесико мыши.
В принципе, все, что связано с этими событиями, можно посмотреть в MSDN и в исходнике, ничего сложного в этом нет, и я не буду включать это в это описание.
Компиляция программы
Для компиляции требуется пакет masm32 (можно найти здесь) После установки рекомендуется добавить путь к каталогу masm32 \ bin в переменную среды Path и отредактировать masm32 / bin / build. bat файл , исправив вызов компилятора ml и компоновщика, чтобы добавить пути к библиотекам и включенным файлам, и мне не приходилось постоянно регистрировать данные пути в коде.
Значит, при вызове ML.Exe нужно добавить еще один параметр / IF: \ masm32 \ include - вместо F: \ masm32 нужно указать путь, по которому вы установили пакет masm32. И в двух вызовах компоновщика Link.exe нужно добавить путь к библиотеке с помощью параметра / LIBPATH: F: \ masm32 \ lib. Опять же, замените путь на тот, который соответствует вашему.
Далее в директории с проектом даем две команды: bres ( bres.bat компилирует rsrc.rc в текущем каталоге), а затем окно сборки ( build.bat - компилирует и связывает проект).
Проекты, перенесенные на GitHub
Concatenar no montador de macro
Eu quero usar no meu código algo assim:
. Данные?
my_macro_var db MAX_PATH DUP (?)
.код
mov esi, смещение my_macro_var
my_macro esi, "привет и пока"
Para isso eu crio esta macro mas Recebo alguns erros que não consigo resolver..
my_macro макрос reg, символы
ЛОКАЛЬНЫЙ v1, v2, v3
МЕСТНОЕ c1, c2, c3, c4, cTotal
v1 TEXTEQU% (@ SizeStr ()) -2 ;; -2 для двойной кавычки
v2 textequ% (v1 / 4)
v3 = 0
;% echo @SubStr (<символы>, 2, 4)
повторить v2
c1 Substr <символы>, v3,1
cОбщее экв c1
mov dword ptr [reg + v3], cTotal
v3 = v3 + 4
конец
конец
Eu quero esse resultado ..
mov dword ptr [esi + 00d], "lleh"
mov dword ptr [esi + 04d], "нет о"
mov dword ptr [esi + 08d], "yb d"
mov dword ptr [esi + 12d], "e"
Estes são os erros que Recebo:
Код.asm (14): ошибка A2090: ожидается положительное значение
MacroLoop (1): итерация 1: макрос, вызываемый из
my_macro (16): макрос, вызываемый из
Code.asm (14): код основной линии
Code.asm (14): ошибка A2006: неопределенный символ: a
MacroLoop (4): итерация 1: макрос, вызываемый из
my_macro (16): макрос, вызываемый из
Code.asm (14): код основной линии
Code.asm (14): ошибка A2006: неопределенный символ: l
MacroLoop (4): итерация 2: макрос, вызываемый из
my_macro (16): макрос, вызываемый из
Code.asm (14): код основной линии
Код.asm (14): ошибка A2006: неопределенный символ: a
MacroLoop (4): итерация 3: макрос, вызываемый из
my_macro (16): макрос, вызываемый из
Code.asm (14): код основной линии
Eu acho que meu проблема será resolvido se eu puder usar substr e concatenar 4 varável ...
Ответов:
2 абзаца ответа № 1
O primeiro erro é o causado pelo fato deSUBSTR usa uma indexação baseada e voiceê está tentando usar indexação baseada em zero com ela. O restante dos erros são o resultado de cTotal não ter caracteres de aspas.
Então, para corrigir os erros, tente isto:
my_macro макрос reg, символы
ЛОКАЛЬНЫЙ v1, v2, v3
МЕСТНОЕ c1, c2, c3, c4, cTotal
v1 TEXTEQU% (@ SizeStr ()) -2 ;; -2 для двойной кавычки
v2 textequ% (v1 / 4)
v3 = 0
;% echo @SubStr (<символы>, 2, 4)
повторить v2
c1 Substr <символы>, v3 + 1, 4
cTotal CATSTR <">, c1, <">
mov dword ptr [reg + v3], cTotal
v3 = v3 + 4
конец
конец
Обновление... Ċ MASM6.11-ProgrammersGuide.pdf (986k) Тушар Б. Куте, 6 декабря 2010 г., 00:06 Тушар Б. Куте, 6 декабря 2010 г., 12:04 AM Ċ MasmProgrammingReferenceManual.pdf (1353k) Tushar B Kute, 6 декабря 2010 г., 00:08 Ċ MicrosoftMASMOverview.pdf (25k) Tus , 2010, 0:08 Ċ WindowsAssemblyProgrammingTutorial.pdf (121k) Тушар Б. Куте, 6 декабря 2010 г., 00:10 |
Ключевые моменты программирования на Ассемблере
Предварительные требования
В данной статье предполагается, что читатель установил MASM32. Если нет, его можно найти на http://www.masm32.com/.
Введение
В этой третьей и заключительной части этого руководства я расскажу об общих арифметических функциях и некоторых макросах в MASM, которые значительно помогают в реализации Ассемблера.Перейдите к «Введение в программирование на языке ассемблера», если хотите начать с начала этой серии.
Увеличение и уменьшение
Операции увеличения и уменьшения: inc и dec . Например:
TestProc proc mov eax, 5 Dec Eax Dec Eax mov dl, 10 inc dl inc dl Ret TestProc endp
При использовании декремента dec , если результат равен нулю, устанавливается нулевой флаг. Это можно использовать для реализации циклов.Например:
TestProc proc mov ecx, 10 xor eax, eax; эффективный способ сказать eax = 0 LoopStart: inc eax dec ecx jnz LoopStart ; eax теперь равно 10 Ret TestProc endp
Сложение и вычитание
Инструкции сложения и вычитания: сложение и sub . Они имеют общий вид:
add / sub (назначение), (источник)
Вы можете добавлять регистры к регистрам, регистры к константам и регистры к содержимому памяти.Размер (в битах) источника и назначения должен быть одинаковым. Оба влияют на флаги системы. Например, если результат sub равен нулю, устанавливается нулевой флаг. Например:
AddValues proc dwValue1: DWORD, dwValue2: DWORD mov eax, dwValue1 добавить eax, dwValue2 Ret AddValues endp
Этот метод складывает два переданных значения и возвращает результат.
Умножение и деление
Инструкции для умножения и деления: mul и div .Оба работают только с регистром накопителя (eax) и используют регистр данных (edx) как переполнение. Часть регистров определяется размером операнда.
Следующая диаграмма демонстрирует, как аккумулятор и регистры данных подходят друг к другу при использовании инструкциями.
Поэтому, чтобы получить ожидаемые результаты, рекомендуется установить edx в ноль перед вызовом mul или div . Например:
TestProc proc mov eax, 10 xor edx, edx; установить edx на ноль мул 10 div 10 Ret TestProc endp
Логические операции
Обычные логические операции преобразуются или , и и xor .Они имеют следующий вид:
логическая операция (место назначения), (источник)
Размер в битах источника и назначения должен быть одинаковым. Например:
LogicalFunction proc xor eax, eax; эффективный способ сказать eax = 0 мов топор, 100 mov bx, 5 и топор, 1 или топор, bx Ret LogicalFunction endp
Битовые операции
Команды shl и shr сдвигают заданные биты регистра влево и вправо на заданное количество бит.Они очень эффективны и должны быть предпочтительнее инструкций mul и div для параметров, которые являются степенями двойки. Например:
ShiftFunction proc mov eax, 1 shl eax, 2; сдвинуть биты eax влево 2 раза: т.е. eax * = 4 shr eax, 2; сдвинуть биты eax вправо 2 раза: т.е. eax / = 4 Ret ShiftFunction endp
Инструкции по тестированию / петли
Существует ряд инструкций, которые можно использовать для проверки определенных условий.Они выполняют те же операции, что и арифметические операции, но не изменяют значения в регистрах; они просто влияют на флаги.
Команда cmp эффективно вычитает источник из места назначения, но не сохраняет результирующее значение. Например:
CmpFunction proc mov eax, 100 cmp eax, 100 ; прыгать если равно je Equals ; не равный mov eax, 2 jmp EndIf Равно: mov eax, 1 Конец Если: Ret CmpFunction endp
Команда test выполняет операции и над исходным и целевым операндами и соответственно устанавливает флаги без сохранения результата.
Цикл Инструкция уменьшает ecx на единицу и переходит в указанное место, если результат не равен нулю.
LoopFunction proc xor eax, eax mov ecx, 10 LoopStart: inc eax цикл LoopStart Ret LoopFunction endp
Макросы MASM
В MASM доступно огромное количество макросов, призванных облегчить жизнь разработчикам на ассемблере. Однако я расскажу лишь о некоторых из них.
Первый - это оператор «.if».Он предоставляет возможность сравнивать два операнда (используя стандартные операторы C ++, такие как =, & gt =, & lt = и т. Д.:
IfProc proc mov eax, 100 mov ecx, 200 .if eax == ecx ; сделай что-нибудь .еще ; сделай что-нибудь еще .endif Ret IfProc endp
Второй - это . Повтор -. До петли. Существуют различные формы этого. .untilcxz уменьшает ecx на единицу и продолжает цикл, если результат не равен нулю. . До нуля? продолжает цикл до тех пор, пока не будет установлен нулевой флаг.
LoopProc proc xor eax, eax mov ecx, 100 .повторить inc eax .untilcxz Ret LoopProc endp
При выполнении циклов в циклах, чтобы освободить использование регистров eax, ebx и edx, значение ecx внешнего цикла может быть push ed, а затем pop ped при выходе из внутреннего цикла. Например:
Процесс LoopInLoopProc xor eax, eax mov ecx, 100 .повторить толкнуть ecx mov ecx, 100 .повторить inc eax .доcxz поп-эккс .untilcxz Ret LoopInLoopProc endp
Вызов функций из внутреннего ассемблера
Вы вызываете функции внутри кода Ассемблера, используя , вызывая , за которым следует имя функции и ее список параметров, разделенный запятыми. Например:
Function1 proc dwValue: DWORD добавить eax, 100 Ret Функция1 endp MainFunction proc mov eax, 100 вызвать Function1, eax ; eax now = 200, т.е. eax + = 100 Ret MainFunction endp
Обратите внимание, что между именем функции и первым параметром стоит запятая.
Локальная память
MASM позволяет выделять локальную память для функций и соответствующим образом маркировать ее. Потенциально это можно рассматривать как локальные переменные, но если вы изучите основной машинный язык, вы увидите, что на самом деле это просто еще одна сокращенная форма для доступа к памяти.
Вы определяете память в начале функции. Если вы изучите разборку, вы увидите, что на самом деле происходит выделение блока статической памяти перед первой инструкцией функции.Размер памяти определяется основными типами MASM; другими словами, BYTE, WORD или DWORD.
Пример LocalMemory proc ЛОКАЛЬНЫЙ dwValue: DWORD; выделяет 4 байта и маркирует его dwValue МЕСТНОЕ wValue: WORD; выделяет 2 байта и маркирует его wValue LOCAL bValue: BYTE; выделяет 1 байт и маркирует его "bValue" xor eax, eax mov dwValue, eax mov wValue, ax mov bValue, al Ret ПримерLocalMemory endp
Оптимизация
При попытке написать эффективный код необходимо учитывать, что не каждая инструкция занимает одинаковое время для выполнения.Например, операции mul и div относительно медленны по сравнению с операциями битового сдвига shr и shl . Полный список времени каждой операции доступен в файлах справки MASM.
При написании эффективного кода еще одним соображением является количество инструкций, задействованных внутри циклов. Чем меньше инструкций, тем быстрее будет код.
При написании кода доступ к памяти происходит медленнее, чем доступ к регистрам, поэтому всегда старайтесь использовать регистры, а не локальную функциональную память.
Кроме того, эффективность jmp зависит от количества байтов, которые нужно перескочить. Эта инструкция принимает смещения размером 8, 16 или 32 бита, и 8-битный переход значительно более эффективен, чем 32-битный переход. Это, очевидно, влияет на циклы: циклы, размер инструкций которых меньше 128 байтов, более эффективны, чем циклы, содержащие большие блоки кода.
Основное внимание уделяется самому алгоритму. Самые быстрые алгоритмы всегда самые простые, потому что они всегда содержат наименьшее количество необходимых инструкций.Всегда лучше пересмотреть алгоритм, который вы используете для конкретной задачи, и если вы можете обменять некоторую точность или гибкость в пользу значительного повышения скорости, сделайте это.
Есть много-много других соображений, когда дело доходит до оптимизации ассемблера. Опять же, файлы справки MASM - бесценный источник для тонкой настройки вашего кода.
Если вы хотите узнать больше об оптимизации Ассемблера, я рекомендую вам
прочитать руководство Agner Fog на www.agner.org.Это даст вам представление о том, как работает процессор, и посоветует, как по-настоящему оптимизировать ваш ассемблерный код.
Заключение
Я надеюсь, что этот набор руководств был интересным и информативным. Он ни в коем случае не является полным, потому что предназначен только для введения. Для получения дополнительной информации обратитесь к руководствам и файлам справки, которые поставляются с MASM.
Но я надеюсь, что я продемонстрировал тот факт, что Assembler нетрудно написать, и вы должны иметь возможность значительно повысить скорость своих приложений и выполнять задачи, которые вы никогда не считали возможными в реальном времени.
.