Ассемблер MASM

Используемая среда разработки

Главная
Обновления дизассемблера за просмотр рекламы (текущая версия 1.6)
Рекламная пауза
Среда разработки
Совмещение 32 и 64-битного кода
Эпиморфный ассемблер
База данных
Calculation Engine (Длинные числа)
Документация
Копирование сайтов
Ссылки
Гостевая книга

MASM64
Генерация заголовочных файлов
Новые макросы
Новый invoke
Пролог и Эпилог
Макрос v
Тестирование отдельных битов test vs bt

MASM64
    Код библиотеки написан на 64-битном ассемблере MASM+RadASM. Хотя это и редко используемый язык можете скачать мой комплект заголовочных файлов masm64.zip 3.83 Мб.
    64-битный ассемблерный транслятор ml64 не распространяется отдельно, и входит в комплект C++ компиляторов от Microsoft. Для установки сначала потребуется скачать web-инсталятор SDK для Windows и .NET Framework 4.0. Полное название Microsoft Windows SDK for Windows 7 and .NET Framework 4, хотя и написано 7 у меня получалось установить на XP. Во время установки там будет предложено выбрать устанавливаемые компоненты, и если для установки выбрать только C++ компиляторы то потребуется 157 Мб трафика. Также если у вас не установлен .NET Framework 4.0, то и его нужно будет установить.
    После установки 64-битный ассемблер будет находится в папках C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\x86_amd64 и C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\amd64, где C:\ это системный диск, обычно с папкой Windows. link я использую 64-битный из папки amd64, а ml64 уже 32-битный из папки x86_amd64. В папке amd64 есть 64-битная версия ml64, но при работе с большим количеством макросов он почему-то зависает или просто медленно работает.

Генерация заголовочных файлов
    В архиве masm64.zip находится много заголовочных файлов, сделанных для Windows XP. Для их создания я использовал утилиту dll2asmblank.zip, исходник прилагается. Вообще-то сначала я её делал как утилиту генерирующую исходники подложных (фэйковых) библиотек (*.dll), что и неплохо получилось. При запуске подложная библиотека загружает оригинальный, но уже переименованный модуль, и все экспортируемые функции оказываются перехваченными. Этот метод лучше подходит для виртуальных машин, когда можно сравнительно легко переименовывать и подменять системные библиотеки. В дальнйшем эту утилиту не сложно было использовать для получения отсутствующих инлайн файлов (*.lib)
    Если вам нужно будет создать более полный комплект заголовочных файлов для своей 64-битной ОС, то для этого следует в папке с dll2asmblank.exe создать файл коммандной строки (*.bat), со следующим содержимым.

@echo off
dll2asmblank "%Папка с Windows%\system32\*.dll" /OUTDIR:"Папка для сохранения заголовочных файлов" /PATTERN:"pattern1.asm"
dll2asmblank "%Папка с Windows%\system32\*.exe" /OUTDIR:"Папка для сохранения заголовочных файлов" /PATTERN:"pattern2.asm"
pause

Файлы pattern1.asm и pattern2.asm, находятся в одной папке с dll2asmblank.exe. Там следует обратить внимание на последние 4 строки в обоих файлах.

PatternBat PATTERN
ML64 /c /Cp /I"C:\masm64\include" %s.asm
LINK %s.obj /LIBPATH:"C:\masm64\lib" /SUBSYSTEM:WINDOWS /ENTRY:DllEntryPoint /DLL /DEF:%s.def
ENDPATTERN

Вместо ML64 и LINK должны быть полные пути к этим файлам, взятые в двойный кавычки. Вместо "C:\masm64\include" ваш путь к заголовочным файлам. Вместо "C:\masm64\lib" ваш путь к инлайн файлам (*.lib). После определения путей нужно будет запустить этот коммандный файл. 
    Когда коммандный файл завершит работу в "Папка для сохранения заголовочных файлов" будет находится коммандный файл build.bat. Выполняться он может очень долго, и когда он завершит работу в "Папка для сохранения заголовочных файлов" будут находится также и все инлайн файлы (*.lib). Дальше останется только разместсить *.inc и *.lib по своему усмотрению.
    Для получения заголовочного файла одной библиотеки в коммандном файле должно быть.

@echo off
dll2asmblank "имя библиотеки.dll" /OUTDIR:"Папка для сохранения заголовочных файлов" /PATTERN:"pattern1.asm"
pause

    Для получения заголовочного файла исполняемого модуля.

@echo off
dll2asmblank "имя исполняемого модуля.exe" /OUTDIR:"Папка для сохранения заголовочных файлов" /PATTERN:"pattern2.asm"
pause

    Для создания подложных библитек и модулей нужно использовать аналогичные коммандные файлы, но вместо pattern1.asm нужно использовать pattern3.asm, и вместо pattern2.asm pattern4.asm соответственно. pattern3.asm и pattern4.asm тоже находятся в одной папке с dll2asmblank.exe.
    32-битные модули могут быть обработаны, но для них потребуются другие pattern файлы.

Новые макросы
    Использовать windows.inc из пакета masm32 не получилось. От туда время от времени копирую недостающие константы, а структуры приходиться объявлять в ручную. В архиве masm64.zip/include есть заголовочный файл с комплектом макросов temphls.inc. Это прежде всего invoke, .if, .elseif, .else, .endif, .while, .repeat и другие.
    Cтиль синтаксиса я в них определил довольно своеобразный. Ниже краткие правила.

Логический оператор Значение
== равно
{} или ~= не равно
} больше
}= больше или равно
{ меньше
{= или ={ меньше или равно
& тестирование битов
| установка битов
&& логическое И
|| логическое ИЛИ
CARRY? CF флаг займа/переноса
OVERFLOW? OV флаг переполнения
PARITY? PF флаг четности бит
SIGN? SF флаг знака
ZERO? ZF флаг равенства нулю
CARRY?|ZERO? некоторые сочетания флагов
~ZERO?&(SIGN?==OVERFLOW?)
SIGN?==OVERFLOW?
SIGN?{}OVERFLOW?
ZERO?|(SIGN?{}OVERFLOW?)

    Конечно лучше-бы это были встроеные макросы, как это обстоит с 32-битной версией ассемблерного транслятора. Знаки переменных SWORD/SDWORD не учитываются. Обычные макросы не принимают уголки <>. Но и в таких макросах есть свои положительные стороны. С синтаксисом теперь можно эксперементировать.

   Я сделал пару новых макросов. Первый это .skip для перехода на начало тела цикла. .continue осуществляет переход на проверку условия продолжения цикла т. е. если остатот цикла нужно вместе с проверкой условия пропустить то можно использовать .skip/.skip .if.

Пример использования

.while TRUE
   inc eax
   .skip .if edx==eax
   dec edx
.endw

Тоже самое но с использованием только старых макросов.

.while TRUE
   .repeat
      inc eax
   .until edx~=eax
   dec edx
.endw

Второй макрос это .goto label/.goto label .if для условных переходов.

Пример использования

   .goto Exit .if eax==5 || esi}=edi
   inc eax
Exit:

Тоже самое но с использованием только старых макросов.

   .if eax~=5 && esi{edi
      inc eax
   .endif
Exit:

   Для сравнения с учётом знака можно использовать одну из четырёх текстовых макро-констант.
sxb equ <sbyte ptr>
sxw equ <sword ptr>
sxd equ <sdword ptr>
sxq equ <sqword ptr>

Пример использования

.if sxq [rdx]{=rax || sxd edx}=r12d || sxw r12w}sp || sxb [rax]{0
   add rax,4
.endif

Новый invoke
    В temphls.inc определён макрос invoke, который может обрабатывать до 14 параметров. Для вызова функций с большим количеством аргументов можно использовать макрос invoke14. invoke отличается от invoke14, тем что он обрабатывает повторяющиеся параметры, для загрузки параметров использует rbp или rsp регистр в зависимости от количества памяти выделеной под локальные переменные, использует типизированные по размеру константы, и вместо addr при загрузке указателей может использоваться знак амперсанда (&, почти как в C/C++).

Исходник пустого окна
OPTION DOTNAME
option casemap:none
include temphls.inc
include win64.inc
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
OPTION PROLOGUE:rbpFramePrologue
OPTION EPILOGUE:rbpFrameEpilogue
.const
wcex label WNDCLASSEX
    cbSize dd sizeof WNDCLASSEX
    style dd 0
    lpfnWndProc dq offset WndProc
    cbClsExtra dd 0
    cbWndExtra dd 0
    hInstance dq 100400000h
    hIcon dq 10003h
    hCursor dq 10003h
    hbrBackground dq COLOR_WINDOW
    lpszMenuName dq 0
    lpszClassName dq offset ClassName
    hIconSm dq 10003h
    ClassName db 'Asm64 window',0
    AppName db 'The window',0
.code
WinMain proc <12> ;parmarea 12*8 bytes
LOCAL msg:MSG
    invoke RegisterClassEx,&wcex ;можно написать по старому addr wcex
    mov r10d,CW_USEDEFAULT
    invoke CreateWindowEx,0,addr ClassName,addr AppName,WS_OVERLAPPEDWINDOW or WS_VISIBLE,\
        dptr CW_USEDEFAULT,dptr CW_USEDEFAULT,dptr CW_USEDEFAULT,dptr CW_USEDEFAULT,\
        0,0,hInstance,0
    lea rdi,msg
    .while TRUE
        invoke GetMessage,rdi,0,0,0
        .break .if ~eax
        invoke TranslateMessage,rdi
        invoke DispatchMessage,rdi
    .endw
    invoke ExitProcess,[rdi][MSG.wParam]
WinMain endp
WndProc proc <4> hWnd:QWORD,uMsg:QWORD,wParam:WPARAM,lParam:LPARAM
    .if edx==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        leavef
        jmp DefWindowProc
    .endif
    xor eax,eax
    ret
WndProc endp
end

Ассемблирование и линковка
@echo off
ML64 /I"masm64\include" /I"\masm64\include" /c /Cp "window.asm"
LINK /LIBPATH:"\masm64\lib" /BASE:0x100400000 /ENTRY:WinMain /SUBSYSTEM:WINDOWS window.obj user32.lib kernel32.lib
pause

invoke с CreateWindowEx разворачивается в следующую последовательность инструкций

0000000100401011:33C9                            xor ecx,ecx                 ;dwExStyle=0
0000000100401013:48894DB0                        mov [rbp-50h],rcx           ;hWndParent=0
0000000100401017:48894DB8                        mov [rbp-48h],rcx           ;hMenu=0
000000010040101B:48894DC8                        mov [rbp-38h],rcx           ;lpParam=0
000000010040101F:488D157A100000                  lea rdx,[1004020A0h]        ;lpClassName=1004020A0h
0000000100401026:4C8D0580100000                  lea r8,[1004020ADh]         ;lpWindowName=1004020ADh
000000010040102D:41B90000CF10                    mov r9d,10CF0000h           ;dwStyle=10CF0000h
0000000100401033:B800000080                      mov eax,80000000h           ;eax=80000000h
0000000100401038:894590                          mov [rbp-70h],eax           ;x=eax
000000010040103B:894598                          mov [rbp-68h],eax           ;y=eax
000000010040103E:8945A0                          mov [rbp-60h],eax           ;nWidth=eax
0000000100401041:8945A8                          mov [rbp-58h],eax           ;nHeight=eax
0000000100401044:488B051D100000                  mov rax,[100402068h]        ;rax=[100402068h]
000000010040104B:488945C0                        mov [rbp-40h],rax           ;hInstance=rax
000000010040104F:FF15EB0F0000                    call qword ptr [100402040h] ;call CreateWindowEx

Загрузка параметров происходит не последовательно, а с учётом повторяющихся параметров. Если ecx равен нулю, то вместо and qword ptr [rbp-50h],0 будет mov [rbp-50h],rcx что компактнее. Подобные вещи обычно делают оптимизирующие компиляторы. Константа dptr CW_USEDEFAULT, является типизированной. Так как invoke не использует прототипы функций, то размер аргумента должен быть так или иначе задан, по умолчанию размер аргумента 8 байт. dptr перед  CW_USEDEFAULT делает подсказку, что это DWORD а не QWORD и вместо mov [rbp-70h],rax ставится более компактное mov [rbp-70h],eax.

Пролог и Эпилог
    В temphls.inc есть пролог и эпилог. Для их использования код должен начинаться со строк.
OPTION PROLOGUE:rbpFramePrologue
OPTION EPILOGUE:rbpFrameEpilogue
    Для аддрессации к локальным переменным используется регистр rbp, только потому-что так проще. На x64 системах регистры rax, rbx, rcx, rdx, rsp, rsi, rdi для этой цели тоже подходят не меньше чем rbp. Регистры r8..r15 требуют REX префикса, но тоже подходят. На x86 выбор только между ebp и esp, с eax, ebx, ecx, edx, esi, edi в редких случаях возможны проблемы из-за сегментации. Директиву uses можно использовать как и раньше в 32-битном коде, только там нужно указывать имена 64-битных регистров, а не 32-битных.
    Если в процедуре под локальные переменные выделяется больше 4 кб, то по возможности используется функция __chkstk. Эта функция находится в библиотеке kernel32.dll поэтому в исходнике должны быть две строки.
include kernel32.inc
includelib kernel32.lib
    Выделение стэковой памяти происходит таким образом, что стэк выравнивается на границу 16 байт. Пролог работает по принципу "Если для меня стэк выравняли, то и я это сделаю". При вызове функций можно использовать макрос invoke, если вы хотите в ручную загрузить аргументы, то для этого нужно использовать регистры rcx, rdx, r8, r9 для первых 4 аргументов и для остальных макро-определения rbpArg5..rbpArg14 с аддрессацией через rbp регистр или rspArg5..rspArg14 с аддрессацией через rsp. Объявления этих макро-определений находятся в temphls.inc. На x86 для загрузки аргументов обычно используются последоватьльности из push`ей, но для x64 это не целесообразно, потому-что стэк перед вызовом WINAPI функции должен быть выровнен на границу 16-байт и выравненное значение rsp лучше лишний раз не менять.
    Пролог в уголках <> может принимать до 5 параметров.
Пример:
WndProc proc <12,8,4,8,8> hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM

    ret
WndProc endp
    Первый параметр 12 - означает что в процедуре можно вызывать функции которым передаётся не более 12 аргументов. Для большей части WINAPI функций этот параметр должен принимать значения от 4 до 14. Этот параметр не должен быть меньше 4, даже если в процедуре вызываются только WINAPI функции которым передаётся 3 и менее 3 аргументов, в противном случае могут возникнуть трудно отслеживаемые баги. Однако этот параметр может быть равен нулю если процедура не вызывает ни одной функции. Если уголки вообще не использовать для определения параметров, то по умолчанию пролог выделяет в стэке место для 7 аргументов. 
    Остальные четыре числа 8,4,8,8 это размеры в байтах, первых четырёх аргументов которые в начале находятся в регистрах rcx, rdx, r8, r9. Эти параметры могут быть равны 1, 2, 4 или 8. Если эти параметры не указывать то аргументы из регистров rcx, rdx, r8, r9 не будут сохраняться в отведённое для них место в стэке, но это можно будет сделать в ручную.
Сохранение первых 4 аргументов в ручную:
WndProc proc <12> hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
    mov hWnd,rcx
    mov uMsg,edx
    mov wParam,r8
    mov lParam,r9
   
    ret
WndProc endp
    Макрос leavef удаляет фрэйм выделенный в прологе, ret отличается от leavef только тем, что он оканчивается инструкцией retn для возврата к вызвавшей процедуре.

Макрос v
Макрос v (сокращенно от eval, находится в masm64\include\temphls.inc) раскрывает выражения, помогая более понятно оформлять код. Макрос не обрабатывает деление. Сколько я макрос не использовал необходимости в этом действии просто не возникло.

Поддерживаемые операторы в порядке увеличения приоритета
Оператор Значение Инструкции выполняющие действие
= равно mov, lea
| логическое или or
^ исключающее или xor
and (обязательно маленькими буквами) логическое и and
~ инверсия всех бит not
+ операция сложения add, sub, inc, dec, lea, neg
- операция вычитания add, sub, inc, dec, lea, neg
* операция умножения imul, lea
}} логический сдвиг вправо shr
{{ логический сдвиг влево shl

    Из-за логических сдвигов может быть изменено значение регистра cl, далеко не всегда, но это нужно учитывать. При умножении последний множитель должен завершать строку выражения или он должен быть отделён скобками.
Можно написать так
    v r9=rax*r8*rsi*r15
Вот так
    v r9=(rax*r8*rsi*r15)|qword ptr [rbp-10h]
Но только не так
    v r9=rax*r8*rsi*r15|qword ptr [rbp-10h]

Макрос v не на столько высокоуровневый, чтобы можно было забыть про то что у процессора нет инструкций где оба операнда память. Нельзя копировать из памяти в память, нельзя умножить память на память и. т. д.
Можно написать так
    v qword ptr [rbp-10h]=rax-rdi
Вот так
    v rax=qword ptr [rbp-10h]+qword ptr [rbp-18h]
Но только не так
    v qword ptr [rbp-10h]=rax+qword ptr [rbp-18h]

Размеры операндов должны быть одинаковыми. Это могут быть регистры, память или непосредственные значения. Исключение возможно при сдвигах.
Можно написать так
    v edi=dword ptr [rbp-30h]{{(al+bl)
Макрос развернется в последовательность
    mov edi,dword ptr [rbp-30h]
    mov cl,al
    add cl,bl
    shl edi,cl

С помощью амперсанда доступно масштабирование с инструкцией lea, но место назначения должно быть регистром.
Можно написать так
    v edi=&[4*esi]-dword ptr [rbp-40h]
Вот так
    v edi=dword ptr [rbp-40h]+&[4*esi]
Но только не так
    v dword ptr [rbp-40h]=edi+&[4*esi]

При написании макроса возникло много проблем связаных с ограничениями макросистемы. Уровень вложености при ветвлении IF/ENDIF ограничен всего 20 уровнями. Само по себе это не было-б проблемой, 20 уровней для .if/.endif мне всегда хватало, но похоже что уровни при рекурсии суммируются, а макрос v как раз рекурсивный. В некоторых местах выходило так что ключевое слово ELSE тихо игнорировалось и приходилось использовать директиву GOTO, в паре с объявлением макрометки.

Тестирование отдельных битов test vs bt
В макросах masm64.zip/Include/temphls.inc внесено немного изменений. Ранее в макросах для проверки битов использовалась только инструкция test, ей соответствовал логический оператор амперсанд &, но опкод инструкции bt более компактный, а также с bt можно еще проверять старшие 32 бита в 64-битных регистрах и переменных.

Например такой записи раньше не допускалось.

   .if rax&(1 shl 55)
      inc edi
   .endif

Развернется как

      bt rax,55
      jnc labelxx
      inc edi
   labelxx:

Раньше происходила-б ошибка

      test rax,(1 shl 55)       ;в опкоде test есть только 4 байта для чисел, число (1 shl 55) не уместится
      jz labelxx
      inc edi
   labelxx:

При тестировании самого старшего бита в WORD`е, DWORD`е или QWORD`е все-же используется инструкция test, потому-что старший бит копируется в SF флаг основного регистра флагов RFLAGS.

   .if rbx&(1 shl 63)
      inc edi
   .endif

Развернется как

      test rbx,rbx
      jns labelxx       ;проверка SF флага
      inc edi
   labelxx:

Знаковый бит можно еще тестировать используя ключевое слово SIGN?

   .if rbx&SIGN?
      inc edi
   .endif

Макрос отвечающий за генерацию этого кода изменен таким образом, что если вы ранее им пользовались и остались исходники то для замены test`ов на bt везде где это нужно достаточно просто переассемблировать исходники.