Макросы в 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 – это код символа «>».

        1. Преобразование строки в число

Как
говорилось выше, консольный ввод-вывод
осуществляется исключительно как
ввод-вывод текстов. Поэтому, если вы
вводите с клавиатуры «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
      ListNode 
   ENDM
   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 = $
      ListNode 
   ENDM
   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.Затем, после долгих мучений с написанием одного и того же кода сто раз, я решил написать для себя небольшую библиотеку макросов. В результате удалось достаточно серьезно облегчить нашу судьбу и уменьшить необходимость повторения огромных полотенец кода, при необходимости написать простую программу с одним окном.

Недавно наткнулся на эти проекты и решил выложить некоторые из них, может кому пригодится ...

Состав проекта

Итак, приступим. Прилагаемый ниже проект имеет следующую структуру.

9069 Rsrc.rc
\ 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
конец

конец
  

MASM - Архитектура процессора и интерфейс

Microsoft Macro Assembler (MASM) - ассемблер x86 для MS-DOS и Microsoft Windows.Он поддерживает широкий спектр макросов и идиом структурированного программирования, включая высокоуровневые функции для циклов и процедур. В более поздних версиях добавлена ​​возможность создания программ для Windows. MASM - один из немногих инструментов разработки Microsoft, ориентированных на 16-, 32-разрядные версии, и поставляется в виде 64-разрядной версии ML64.EXE для 64-разрядных платформ. Версии 5.0 и более ранние были приложениями MS-DOS. Версии 5.1 и 6.0 были доступны как приложения MS-DOS, так и OS / 2. Версии с 6.12 по 6.14 были реализованы как патчи для версии 6.11, которые преобразовали их из 16-битных исполняемых файлов MZ в 32-битные исполняемые файлы PE. Все более поздние версии представляли собой 32-битные исполняемые файлы PE, созданные как приложения режима консоли Win32.

Microsoft Assembler находится в производстве с 1981 года и обновляется Microsoft, чтобы идти в ногу с потребностями операционной системы и разработками процессоров. До Microsoft Assembler аббревиатура MASM относилась к Meta Assembler . MASM 6.0 был выпущен в 1992 году и был первой версией, которая включала поддержку программирования высокого уровня (в частности, макросы if / endif) и синтаксис, более похожий на C.К концу года в версии 6.1A было обновлено управление памятью для обеспечения совместимости с кодом, созданным Visual C ++. В 1993 году была добавлена ​​полная поддержка 32-битных приложений и набора инструкций Pentium, а также расширитель DOS Phar Lap. К концу 1997 года MASM полностью поддерживал Windows 95, не требовал расширителя DOS и включал некоторые инструкции AMD.

В 1999 году Intel выпустила макросы для инструкций SIMD и MMX, которые вскоре были изначально поддержаны MASM.С выпуском 6.15 в 2000 году Microsoft прекратила поддержку MASM как отдельного продукта, вместо этого включив его в набор инструментов Visual Studio, хотя он все еще был совместим с Windows 98, хотя текущие версии Visual Studio - нет. Поддержка 64-битных процессоров не была добавлена ​​до выпуска Visual Studio 2005, при этом MASM получил номер версии 8.0.

Ссылка: Википедия, бесплатная энциклопедия

Обновление...

Ċ

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 нетрудно написать, и вы должны иметь возможность значительно повысить скорость своих приложений и выполнять задачи, которые вы никогда не считали возможными в реальном времени.

.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *