Написание программ на ассемблере под 64 битную. Условные выражения и циклы. Регистры общего назначения

Написание программ на ассемблере под 64 битную. Условные выражения и циклы. Регистры общего назначения

25.03.2019

Хотя и написано 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
sxw equ
sxd equ
sxq equ

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

If sxq {=rax || sxd edx}=r12d || sxw r12w}sp || sxb {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,
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 ,rcx ;hWndParent=0
0000000100401017:48894DB8 mov ,rcx ;hMenu=0
000000010040101B:48894DC8 mov ,rcx ;lpParam=0
000000010040101F:488D157A100000 lea rdx, ;lpClassName=1004020A0h
0000000100401026:4C8D0580100000 lea r8, ;lpWindowName=1004020ADh
000000010040102D:41B90000CF10 mov r9d,10CF0000h ;dwStyle=10CF0000h
0000000100401033:B800000080 mov eax,80000000h ;eax=80000000h
0000000100401038:894590 mov ,eax ;x=eax
000000010040103B:894598 mov ,eax ;y=eax
000000010040103E:8945A0 mov ,eax ;nWidth=eax
0000000100401041:8945A8 mov ,eax ;nHeight=eax
0000000100401044:488B051D100000 mov rax, ;rax=
000000010040104B:488945C0 mov ,rax ;hInstance=rax
000000010040104F:FF15EB0F0000 call qword ptr ;call CreateWindowEx

Загрузка параметров происходит не последовательно, а с учётом повторяющихся параметров. Если ecx равен нулю, то вместо and qword ptr ,0 будет mov ,rcx что компактнее. Подобные вещи обычно делают оптимизирующие компиляторы. Константа dptr CW_USEDEFAULT, является типизированной. Так как invoke не использует прототипы функций, то размер аргумента должен быть так или иначе задан, по умолчанию размер аргумента 8 байт. dptr перед CW_USEDEFAULT делает подсказку, что это DWORD а не QWORD и вместо mov ,rax ставится более компактное mov ,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>

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


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

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

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

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

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

Простите, у вас не найдется минутки поговорить о спасителе нашем, ассемблере? В прошлой статье мы написали наше первое hello world приложение на асме , научились его компилировать и отлаживать, а также узнали, как делать системные вызовы в Linux. Сегодня же мы познакомимся непосредственно с ассемблерными инструкциями, понятием регистров, стека и вот этого всего. Ассемблеры для архитектур x86 (a.k.a i386) и x64 (a.k.a amd64) очень похожи, в связи с чем нет смысла рассматривать их в отдельных статьях. Притом акцент я постараюсь делать на x64, попутно отмечая отличия от x86, если они есть. Далее предполагается, что вы уже знаете, например, чем стек отличается от кучи, и объяснять такие вещи не требуется.

Регистры общего назначения

Регистр — это небольшой (обычно 4 или 8 байт) кусочек памяти в процессоре с чрезвычайно большой скоростью доступа. Регистры делятся на регистры специального назначения и регистры общего назначения. Нас сейчас интересуют регистры общего назначения. Как можно догадаться по названию, программа может использовать эти регистры под свои нужды, как ей вздумается.

На x86 доступно восемь 32-х битных регистров общего назначения — eax, ebx, ecx, edx, esp, ebp, esi и edi. Регистры не имеют заданного наперед типа, то есть, они могут трактоваться как знаковые или беззнаковые целые числа, указатели, булевы значения, ASCII-коды символов, и так далее. Несмотря на то, что в теории эти регистры можно использовать как угодно, на практике обычно каждый регистр используется определенным образом. Так, esp указывает на вершину стека, ecx играет роль счетчика, а в eax записывается результат выполнения операции или процедуры. Существуют 16-и битные регистры ax, bx, cx, dx, sp, bp, si и di, представляющие собой 16 младших бит соответствующих 32-х битных регистров. Также доступны и 8-и битовые регистры ah, al, bh, bl, ch, cl, dh и dl, которые представляют собой старшие и младшие байты регистров ax, bx, cx и dx соответственно.

Рассмотрим пример. Допустим, выполняются следующие три иснтрукции:

(gdb) x/3i $pc
=> 0x8048074: mov $0xaabbccdd,%eax
0x8048079: mov $0xee,%al
0x804807b: mov $0x1234,%ax

Значения регистров после записи в eax значения 0 x AABBCCDD:

(gdb) p/x $eax
$1 = 0xaabbccdd
(gdb) p/x $ax
$2 = 0xccdd
(gdb) p/x $ah
$3 = 0xcc
(gdb) p/x $al
$4 = 0xdd

Значения после записи в регистр al значения 0 x EE:

(gdb) p/x $eax
$5 = 0xaabbccee
(gdb) p/x $ax
$6 = 0xccee
(gdb) p/x $ah
$7 = 0xcc
(gdb) p/x $al
$8 = 0xee

Значения регистров после записи в ax числа 0 x 1234:

(gdb) p/x $eax
$9 = 0xaabb1234
(gdb) p/x $ax
$10 = 0x1234
(gdb) p/x $ah
$11 = 0x12
(gdb) p/x $al
$12 = 0x34

Как видите, ничего сложного.

Примечание: Синтаксис GAS позволяет явно указывать размеры операндов путем использования суффиксов b (байт), w (слово, 2 байта), l (длинное слово, 4 байта), q (четверное слово, 8 байт) и некоторых других . Например, вместо команды mov $0xEE , % al можно написать movb $0xEE , % al , вместо mov $0x1234 , % ax movw $0x1234 , % ax , и так далее. В современном GAS эти суффиксы являются опциональными и я лично их не использую. Но не пугайтесь, если увидите их в чужом коде.

На x64 размер регистров был увеличен до 64-х бит. Соответствующие регистры получили название rax, rbx, и так далее. Кроме того, регистров общего назначения стало шестнадцать вместо восьми. Дополнительные регистры получили названия r8, r9, …, r15. Соответствующие им регистры, которые представляют младшие 32, 16 и 8 бит, получили название r8d, r8w, r8b, и по аналогии для регистров r9-r15. Кроме того, появились регистры, представляющие собой младшие 8 бит регистров rsi, rdi, rbp и rsp — sil, dil, bpl и spl соответственно.

Про адресацию

Как уже отмечалось, регистры могут трактоваться, как указатели на данные в памяти. Для разыменования таких указателей используется специальный синтаксис:

mov (% rsp ) , % rax

Эта запись означает «прочитай 8 байт по адресу, записанному в регистре rsp, и сохрани их в регистр rax». При запуске программы rsp указывает на вершину стека, где хранится число аргументов, переданных программе (argc), указатели на эти аргументы, а также переменные окружения и кое-какая другая информация. Таким образом, в результате выполнения приведенной выше инструкции (разумеется, при условии, что перед ней не выполнялось каких-либо других инструкций) в rax будет записано количество аргументов, с которыми была запущена программа.

В одной команде можно указывать адрес и смешение (как положительное, так и отрицательное) относительно него:

mov 8 (% rsp ) , % rax

Эта запись означает «возьми rsp, прибавь к нему 8, прочитай 8 байт по получившемуся адресу и положи их в rax». Таким образом, в rax будет записан адрес строки, представляющей собой первый аргумент программы, то есть, имя исполняемого файла.

При работе с массивами бывает удобно обращаться к элементу с определенным индексом. Соответствующий синтаксис:

# инструкция xchg меняет значения местами
xchg 16 (% rsp ,% rcx , 8 ) , % rax

Читается так: «посчитай rcx*8 + rsp + 16, и поменяй местами 8 байт (размер регистра) по получившемуся адресу и значение регистра rax». Другими словами, rsp и 16 все так же играют роль смещения, rcx играет роль индекса в массиве, а 8 — это размер элемента массива. При использовании данного синтаксиса допустимыми размерами элемента являются только 1, 2, 4 и 8. Если требуется какой-то другой размер, можно использовать инструкции умножения, бинарного сдвига и прочие, которые мы рассмотрим далее.

Наконец, следующий код тоже валиден:

Data
msg:
. ascii "Hello, world!\n"
. text

Globl _start
_start:
# обнуление rcx
xor % rcx , % rcx
mov msg(,% rcx , 8 ) , % al
mov msg, % ah

В смысле, что можно не указывать регистр со смещением или вообще какие-либо регистры. В результате выполнения этого кода в регистры al и ah будет записан ASCII-код буквы H, или 0 x 48.

В этом контексте хотелось бы упомянуть еще одну полезную ассемблерную инструкцию:

# rax:= rcx*8 + rax + 123
lea 123 (% rax ,% rcx , 8 ) , % rax

Инструкция lea очень удобна, так как позволяет сразу выполнить умножение и несколько сложений.

Fun fact! На x64 в байткоде инструкций никогда не используются 64-х битовые смещения. В отличие от x86, инструкции часто оперируют не абсолютными адресами, а адресами относительно адреса самой инструкции, что позволяет обращаться к ближайшим +/- 2 Гб оперативной памяти. Соответствующий синтаксис:

movb msg(% rip) , % al

Сравним длины опкодов «обычного» и «относительного» mov (objdump -d ):

4000b0: 8a 0c 25 e8 00 60 00 mov 0x6000e8,%cl
4000b7: 8a 05 2b 00 20 00 mov 0x20002b(%rip),%al # 0x6000e8

Как видите, «относительный» mov еще и на один байт короче! Что это за регистр такой rip мы узнаем чуть ниже.

Для записи же полного 64-х битового значения в регистр предусмотрена специальная инструкция:

movabs $0x1122334455667788 , % rax

Другими словами, процессоры x64 так же экономно кодируют инструкции, как и процессоры x86, и в наше время нет особо смысла использовать процессоры x86 в системах, имеющих пару гигабайт оперативной памяти или меньше (мобильные устройства, холодильники, микроволновки, и так далее). Скорее всего, процессоры x64 будут даже более эффективны за счет большего числа доступных регистров и большего размера этих регистров.

Арифметические операции

Рассмотрим основные арифметические операции:

# инциализируем значения регистров
mov $123 , % rax
mov $456 , % rcx

# инкремент: rax = rax + 1 = 124
inc % rax

# декремент: rax = rax - 1 = 123
dec % rax

# сложение: rax = rax + rcx = 579
add % rcx , % rax

# вычитание: rax = rax - rcx = 123
sub % rcx , % rax

# изменение знака: rcx = - rcx = -456
neg % rcx

Здесь и далее операндами могут быть не только регистры, но и участки памяти или константы. Но оба операнда не могут быть участками памяти. Это правило применимо ко всем инструкциям ассемблера x86/x64, по крайней мере, из рассмотренных в данной статье.

Пример умножения:

mov $100 , % al
mov $3 , % cl
mul % cl

В данном примере инструкция mul умножает al на cl, и сохраняет результат умножения в пару регистров al и ah. Таким образом, ax примет значение 0 x 12C или 300 в десятичной нотации. В худшем случае для сохранения результата перемножения двух N-байтовых значений может потребоваться до 2*N байт. В зависимости от размера операнда результат сохраняется в al:ah, ax:dx, eax:edx или rax:rdx. Притом в качестве множителей всегда используется первый из этих регистров и переданный инструкции аргумент.

Знаковое умножение производится точно так же при помощи инструкции imul. Кроме того, существуют варианты imul с двумя и тремя аргументами:

mov $123 , % rax
mov $456 , % rcx

# rax = rax * rcx = 56088
imul % rcx , % rax

# rcx = rax * 10 = 560880
imul $10 , % rax , % rcx

Инструкции div и idiv производят действия, обратные mul и imul. Например:

mov $0 , % rdx
mov $456 , % rax
mov $123 , % rcx

# rax = rdx:rax / rcx = 3
# rdx = rdx:rax % rcx = 87
div % rcx

Как видите, был получен результат целочисленного деления, а также остаток от деления.

Это далеко не все арифметические инструкции. Например, есть еще adc (сложение с учетом флага переноса), sbb (вычитание с учетом займа), а также соответствующие им инструкции, выставляющие и очищающие соответствующие флаги (ctc, clc), и многие другие. Но они распространены намного меньше, и потому в рамках данной статьи не рассматриваются.

Логические и битовые операции

Как уже отмечалось, особой типизации в ассемблере x86/x64 не предусмотрено. Поэтому не стоит удивляться, что в нем нет отдельных инструкций для выполнения булевых операций и отдельных для выполнения битовых операций. Вместо этого есть один набор инструкций, работающих с битами, а уж как интерпретировать результат — решает конкретная программа.

Так, например, выглядит вычисление простейшего логического выражения:

mov $0 , % rax # a = false
mov $1 , % rbx # b = true
mov $0 , % rcx # c = false

# rdx:= a || !(b && c)
mov % rcx , % rdx # rdx = c
and % rbx , % rdx # rdx &= b
not % rdx # rdx = ~ rdx
or % rax , % rdx # rdx |= a
and $1 , % rdx # rdx &= 1

Заметьте, что здесь мы использовали по одному младшему биту в каждом из 64-х битовых регистров. Таким образом, в старших битах образуется мусор, который мы обнуляем последней командой.

Еще одна полезная инструкция — это xor (исключающее или). В логических выражениях xor используется нечасто, однако с его помощью часто происходит обнуление регистров. Если посмотреть на опкоды инструкций, то становится понятно, почему:

4000b3: 48 31 db xor %rbx,%rbx
4000b6: 48 ff c3 inc %rbx
4000b9: 48 c7 c3 01 00 00 00 mov $0x1,%rbx

Как видите, инструкции xor и inc кодируются всего лишь тремя байтами каждая, в то время, как делающая то же самое инструкция mov занимает целых семь байт. Каждый отдельный случай, конечно, лучше бенчмаркать отдельно, но общее эвристическое правило такое — чем короче код, тем больше его помещается в кэши процессора, тем быстрее он работает.

В данном контексте также следует вспомнить инструкции побитового сдвига, тестирования битов (bit test) и сканирования битов (bit scan):

# положим что-нибудь в регистр
movabs $0xc0de1c0ffee2beef , % rax

# сдвиг влево на 3 бита
# rax = 0x0de1c0ffee2beef0
shl $4 , % rax

# сдвиг вправо на 7 бит
# rax = 0x001bc381ffdc57dd
shr $7 , % rax

# циклический сдвиг вправо на 5 бит
# rax = 0xe800de1c0ffee2be
ror $5 , % rax

# циклический сдвиг влево на 5 бит
# rax = 0x001bc381ffdc57dd
rol $5 , % rax

# то же самое + установить бит (bit test and set)

bts $13 , % rax

# то же самое + сбросить бит (bit test and reset)
# rax = 0x001bc381ffdc57dd, CF = 1
btr $13 , % rax

# то же самое + инвертировать бит (bit test and complement)
# rax = 0x001bc381ffdc77dd, CF = 0
btc $13 , % rax

# найти самый младший ненулевой байт (bit scan forward)
# rcx = 0, ZF = 0
bsf % rax , % rcx

# найти самый старший ненулевой байт (bit scan reverse)
# rdx = 52, ZF = 0
bsr % rax , % rdx

# если все биты нулевые, ZF = 1, значение rdx неопределено
xor % rax , % rax
bsf % rax , % rdx

Еще есть битовые сдвиги со знаком (sal, sar), циклические сдвиги с флагом переноса (rcl, rcr), а также сдвиги двойной точности (shld, shrd). Но используются они не так уж часто, да и утомишься перечислять вообще все инструкции. Поэтому их изучение я оставляю вам в качестве домашнего задания.

Условные выражения и циклы

Выше несколько раз упоминались какие-то там флаги, например, флаг переноса. Под флагами понимаются биты специального регистра eflags / rflags (название на x86 и x64 соответственно). Напрямую обращаться к этому регистру при помощи инструкций mov, add и подобных нельзя, но он изменяется и используется различными инструкциями косвенно. Например, уже упомянутый флаг переноса (carry flag, CF) хранится в нулевом бите eflags / rflags и используется, например, в той же инструкции bt. Еще из часто используемых флагов можно назвать zero flag (ZF, 6-ой бит), sign flag (SF, 7-ой бит), direction flag (DF, 10-ый бит) и overflow flag (OF, 11-ый бит).

Еще из таких неявных регистров следует назвать eip / rip, хранящий адрес текущей инструкции. К нему также нельзя обращаться напрямую, но он виден в GDB вместе с eflags / rflags, если сказать info registers , и косвенно изменяется всеми инструкциям. Большинство инструкций просто увеличивают eip / rip на длину этой инструкции, но есть и исключения из этого правила. Например, инструкция jmp просто осуществляет переход по заданному адресу:

# обнуляем rax
xor % rax , % rax
jmp next
# эта инструкция будет пропущена
inc % rax
next:
inc % rax

В результате значение rax будет равно единице, так как первая инструкция inс будет пропущена. Заметьте, что адрес перехода также может быть записан в регистре:

xor % rax , % rax
mov $next, % rcx
jmp *% rcx
inc % rax
next:
inc % rax

Впрочем, на практике такого кода лучше избегать, так как он ломает предсказание переходов и потому менее эффективен.

Примечание: GAS позволяет давать меткам цифирные имена типа 1: , 2: , и так далее, и переходить к ближайшей предыдущей или следующей метке с заданным номером инструкциями вроде jmp 1b и jmp 1f . Это довольно удобно, так как иногда бывает трудно придумать меткам осмысленные имена. Подробности можно найти .

Условные переходы обычно осуществляются при помощи инструкции cmp, которая сравнивает два своих операнда и выставляет соответствующие флаги, за которой следует инструкция из семейства je, jg и подобных:

cmp % rax , % rcx

je 1f # перейти, если равны (equal)
jl 1f # перейти, если знаково меньше (less)
jb 1f # перейти, если беззнаково меньше (below)
jg 1f # перейти, если знаково больше (greater)
ja 1f # перейти, если беззнаково больше (above)

Существует также инструкции jne (перейти, если не равны), jle (перейти, если знаково меньше или равны), jna (перейти, если беззнаково не больше) и подобные. Принцип их именования, надеюсь, очевиден. Вместо je / jne часто пишут jz / jnz, так как инструкции je / jne просто проверяют значение ZF. Также есть инструкции, проверяющие другие флаги — js, jo и jp, но на практике они используются редко. Все эти инструкции вместе взятые обычно называют jcc. То есть, вместо конкретных условий пишутся две буквы «c», от «condition». можно найти хорошую сводную таблицу по всем инструкциям jcc и тому, какие флаги они проверяют.

Помимо cmp также часто используют инструкцию test:

test % rax , % rax
jz 1f # перейти, если rax == 0
js 2f # перейти, если rax < 0
1 :
# какой-то код
2 :
# какой-то еще код

Fun fact! Интересно, что cmp и test в душе являются теми же sub и and, только не изменяют своих операндов. Это знание можно использовать для одновременного выполнения sub или and и условного перехода, без дополнительных инструкций cmp или test.

Еще из инструкций, связанных с условными переходами, можно отметить следующие.

jrcxz 1f
# какой-то код
1 :

Инструкция jrcxz осуществляет переход только в том случае, если значение регистра rcx равно нулю.

cmovge % rcx , % rax

Инструкции семейства cmovcc (conditional move) работают как mov, но только при выполнении заданного условия, по аналогии с jcc.

setnz % al

Инструкции setcc присваивают однобайтовому регистру или байту в памяти значение 1, если заданное условие выполняется, и 0 иначе.

cmpxchg % rcx , (% rdx )

Сравнить rax с заданным куском памяти. Если равны, выставить ZF и сохранить по указанному адресу значение указанного регистра, в данном примере rcx. Иначе очистить ZF и загрузить значение из памяти в rax. Также оба операнда могут быть регистрами.

cmpxchg8b (% rsi )
cmpxchg16b (% rsi )

Инструкция cmpxchg8b главным образом нужна в x86. Она работает аналогично cmpxchg, только производит compare and swap сразу 8-и байт. Регистры edx:eax используются для сравнения, а регистры ecx:ebx хранят то, что мы хотим записать. Инструкция cmpxchg16b по тому же принципу производит compare and swap сразу 16-и байт на x64.

Важно! Примите во внимание, что без префикса lock все эти compare and swap инструкции не атомарны.

mov $10 , % rcx
1 :
# какой-то код
loop 1b
# loopz 1b
# loopnz 1b

Инструкция loop уменьшает значение регистра rcx на единицу, и если после этого rcx != 0 , осуществляет переход на заданную метку. Инструкции loopz и loopnz работают аналогично, только условия более сложные — (rcx != 0) && (ZF == 1) и (rcx != 0) && (ZF == 0) соответственно.

Не нужно быть семи пядей во лбу, чтобы изобразить при помощи этих инструкций конструкцию if-then-else или циклы for / while, поэтому двигаемся дальше.

«Строковые» операции

Рассмотрим следующий кусок кода:

mov $str1, % rsi
mov $str2, % edi
cld
cmpsb

В регистры rsi и rdi кладутся адреса двух строк. Командой cld очищается флаг направления (DF). Инструкция, выполняющая обратное действие, называется std. Затем в дело вступает инструкция cmpsb. Она сравнивает байты (%rsi) и (%rdi) и выставляет флаги в соответствии с результатом сравнения. Затем, если DF = 0, rsi и rdi увеличиваются на единицу (количество байт в том, что мы сравнивали), иначе — уменьшаются. Аналогичные инструкции cmpsw, cmpsl и cmpsq сравнивают слова, длинные слова и четверные слова соответственно.

Инструкции cmps интересны тем, что могут использоваться с префиксом rep, repe (repz) и repne (repnz). Например:

mov $str1, % rsi
mov $str2, % edi
mov $len, % rcx
cld
repe cmpsb
jne not_equal

Префикс rep повторяет инструкцию заданное в регистре rcx количество раз. Префиксы repz и repnz делают то же самое, но только после каждого выполнения инструкции дополнительно проверяется ZF. Цикл прерывается, если ZF = 0 в случае c repz и если ZF = 1 в случае с repnz. Таким образом, приведенный выше код проверяет равенство двух буферов одинакового размера.

Аналогичные инструкции movs перекладывает данные из буфера, адрес которого указан в rsi, в буфер, адрес которого указан в rdi (легко запомнить — rsi значит source, rdi значит destination). Инструкции stos заполняет буфер по адресу из регистра rdi байтами из регистра rax (или eax, или ax, или al, в зависимости от конкретной инструкции). Инструкции lods делают обратное действие — копируют байты по указанному в rsi адресу в регистр rax. Наконец, инструкции scas ищут байты из регистра rax (или соответствующих регистров меньшего размера) в буфере, адрес которого указан в rdi. Как и cmps, все эти инструкции работают с префиксами rep, repz и repnz.

На базе этих инструкций легко реализуются процедуры memcmp, memcpy, strcmp и подобные. Интересно, что, например, для обнуления памяти инженеры Intel рекомендуют использовать на современных процессорах rep stosb , то есть, обнулять побайтово, а не, скажем, четверными словами.

Работа со стеком и процедуры

Со стеком все очень просто. Инструкция push кладет свой аргумент на стек, а инструкция pop извлекает значение со стека. Например, если временно забыть про инструкцию xchg, то поменять местами значение двух регистров можно так:

push % rax
mov % rcx , % rax
pop % rcx

Существуют инструкции, помещающие на стек и извлекающие с него регистр rflags / eflags:

pushf
# делаем что-то, что меняет флаги
popf
# флаги восстановлены, самое время сделать jcc

А так, к примеру, можно получить значение флага CF:

pushf
pop % rax
and $1 , % rax

На x86 также существуют инструкции pusha и popa, сохраняющие на стеке и восстанавливающие с него значения всех регистров. В x64 этих инструкций больше нет. Видимо, потому что регистров стало больше и сами регистры теперь длиннее — сохранять и восстанавливать их все стало сильно дороже.

Процедуры, как правило, «создаются» при помощи инструкций call и ret. Инструкция call кладет на стек адрес следующей инструкции и передает управление по указанному в аргументе адресу. Инструкция ret читает со стека адрес возврата и передает по нему управление. Например:

someproc:
# типичный пролог процедуры
# для примера выделяем 0x10 байт на стеке под локальные переменные
# rbp - указатель на фрейм стека
push % rbp
mov % rsp , % rbp
sub $0x10 , % rsp

# тут типа какие-то вычисления...
mov $1 , % rax

# типичный эпилог процедуры
add $0x10 , % rsp
pop % rbp

# выход из процедуры
ret

Start:
# как и в случае с jmp, адрес перехода может быть в регистре
call someproc
test % rax , % rax
jnz error

Примечание: Аналогичный пролог и эпилог можно написать при помощи инструкций enter $0x10 , $0 и leave . Но в наше время эти инструкции используются редко, так как они выполняются медленнее из-за дополнительной поддержки вложенных процедур.

Как правило, возвращаемое значение передается в регистре rax или, если его размера не достаточно, записывается в структуру, адрес которой передается в качестве аргумента. К вопросу о передаче аргументов. Соглашений о вызовах существует великое множество . В одних все аргументы всегда передаются через стек (отдельный вопрос — в каком порядке) и за очистку стека от аргументов отвечает сама процедура, в других часть аргументов передается через регистры, а часть через стек, и за очистку стека от аргументов отвечает вызывающая сторона, плюс множество вариантов посередине, с отдельными правилами касательно выравнивания аргументов на стеке, передачи this, если это ООП язык, и так далее. В общем случае для произвольно взятой архитектуры, компилятора и языка программирования соглашение о вызовах может быть вообще каким угодно.

I] ;
}
return hash;
}

Дизассемблерный листинг (при компиляции с -O0 , комментарии мои):

# типичный пролог процедуры
# регистр rsp не изменяется, так как процедура не вызывает никаких
# других процедур
400950: 55 push %rbp
400951: 48 89 e5 mov %rsp,%rbp

# инициализация локальных переменных:
# -0x08(%rbp) - const unsigned char *data (8 байт)
# -0x10(%rbp) - const size_t data_len (8 байт)
# -0x14(%rbp) - unsigned int hash (4 байта)
# -0x18(%rbp) - int i (4 байта)
400954: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400958: 48 89 75 f0 mov %rsi,-0x10(%rbp)
40095c: c7 45 ec 4b 43 41 48 movl $0x4841434b,-0x14(%rbp)
400963: c7 45 e8 00 00 00 00 movl $0x0,-0x18(%rbp)

# rax:= i. если достигли data_len, выходим из цикла
40096a: 48 63 45 e8 movslq -0x18(%rbp),%rax
40096e: 48 3b 45 f0 cmp -0x10(%rbp),%rax
400972: 0f 83 28 00 00 00 jae 4009a0

# eax:= (hash << 5) + hash
400978: 8b 45 ec mov -0x14(%rbp),%eax
40097b: c1 e0 05 shl $0x5,%eax
40097e: 03 45 ec add -0x14(%rbp),%eax

# eax += data[i]
400981: 48 63 4d e8 movslq -0x18(%rbp),%rcx
400985: 48 8b 55 f8 mov -0x8(%rbp),%rdx
400989: 0f b6 34 0a movzbl (%rdx,%rcx,1),%esi
40098d: 01 f0 add %esi,%eax

# hash:= eax
40098f: 89 45 ec mov %eax,-0x14(%rbp)

# i++ и перейти к началу цикла
400992: 8b 45 e8 mov -0x18(%rbp),%eax
400995: 83 c0 01 add $0x1,%eax
400998: 89 45 e8 mov %eax,-0x18(%rbp)
40099b: e9 ca ff ff ff jmpq 40096a

# возвращаемое значение (hash) кладется в регистр eax
4009a0: 8b 45 ec mov -0x14(%rbp),%eax

# типичный эпилог
4009a3: 5d pop %rbp
4009a4: c3 retq

Здесь мы встретили две новые инструкции — movs и movz. Они работают точно так же, как mov, только расширяют один операнд до размера второго, знаково и беззнаково соответственно. Например, инструкция movzbl (%rdx,%rcx,1),%esi читайт байт (b) по адресу (%rdx,%rcx,1) , расширяет его в длинное слово (l) путем добавления в начало нулей (z) и кладет результат в регистр esi.

Как видите, два аргумента были переданы процедуре через регистры rdi и rsi. По всей видимости, используется конвенция под названием System V AMD64 ABI . Утверждается, что это стандарт де-факто под x64 на *nix системах. Я не вижу смысла пересказывать описание этой конвенции здесь, заинтересованные читатели могут ознакомиться с полным описанием по приведенной ссылке.

Заключение

Само собой разумеется, в рамках одной статьи, описать весь ассемблер x86/x64 не представляется возможным (более того, я не уверен, что сам знаю его прямо таки весь ). Как минимум, за кадром остались такие темы, как операции над числами с плавающей точкой, MMX-, SSE- и AVX-инструкции, а также всякие экзотические инструкции вроде lidt, lgdt, bswap , rdtsc, cpuid, movbe, xlatb, или prefetch. Я постараюсь осветить их в следующих статьях, но ничего не обещаю. Следует также отметить, что в выводе objdump -d для большинства реальных программ вы очень редко увидите что-то помимо описанного выше.

Еще интересный топик, оставшийся за кадром — это атомарные операции, барьеры памяти, спинлоки и вот это все. Например, compare and swap часто реализуется просто как инструкция cmpxchg с префиксом lock . По аналогии реализуется атомарный инкремент, декремент, и прочее. Увы, все это тянет на тему для отдельной статьи.

В качестве источников дополнительной информации можно рекомендовать книгу Modern X86 Assembly Language Programming , и, конечно же, мануалы от Intel . Также довольно неплоха книга x86 Assembly на wikibooks.org.

Из онлайн-справочников по ассемблерным инструкциям стоит обратить внимание на следующие:

А знаете ли вы ассемблер, и если да, то находите ли это знание полезным?

UASM is a free MASM-compatible assembler based on JWasm with these features (old HJWasm):

native support for output formats Intel OMF, MS Coff (32/64-bit), Elf (32/64-bit), Binary, Windows PE (32/64-bit) and DOS MZ.
precompiled UASM binaries are available for Windows, Linux and OSX.
Instructions up to AVX2 and AVX512F are supported including all new extensions for VMX, MPX, AES, BND, F16C etc.
Support for MS Vectorcall on x64.
Support for Borland Register Calling Convention.
Full support for SystemV Calling Convention.
Integrated Macro Library with OO support.
Numerous new HLL features (as described in the extended manual).
UASM is written in C. The source is portable and has successfully been tested with Open Watcom, MS VC, GCC and more.
As far as programming for MS Windows is concerned, UASM can be used with both WinInc (32/64-bit) and Masm32 (32-bit).
C header files can be converted to include files for UASM with h2incX.
UASM"s source code is released under the Sybase Open Watcom Public License, which allows free commercial and non-commercial use.
There"s a bunch of source samples available - they are supplied with the precompiled binary packages.
JWasm started as a fork of Open Watcom"s Wasm in March 2008. Today, the part of Wasm source lines still contained in JWasm is approximately 15%.
UASM is a continued evolution of JWasm.

Updated

  1. UASM 2 . 47 (32bit) 17 / 11 / 2018 uasm247_x86. zip 32bit Binary Package (Windows)
  2. UASM 2 . 47 (64bit) 17 / 11 / 2018 uasm247_x64. zip 64bit Binary Package (Windows)
  3. UASM 2 . 47 (Linux 64bit) 17 / 11 / 2018 uasm247_linux64. zip 64bit Linux Executable (GCC)
  4. UASM 2 . 47 (OSX Universal) 17 / 11 / 2018 uasm247_osx. zip 64bit OSX Executable (GCC)

--> 32bit <-- , --> 64bit <-- , --> Linux 64bit <-- , --> OSX <--

Full SDK 10.0 translate for 64 and 32 bits --> Link <--
--> Site <--
Примеры с этими хидерами - --> Link <--

NOTES:
Adapting constants switchs to your system,use this tool:
sdkrc81\Before_use_me.exe.He will give you the correct swiths for your system.
Modify translate.inc with this switchs.The defaut system is windows 10.0

With a correct alignment,all translated structures,can be used as they are.
There is no need of any modifie.

Easy Code IDE 32/64-bit

Easy Code - Visual assembler
(Updated on February 4, 2018)

(Windows 95/98/ME/NT40/2000/XP/2003/2008/Vista/7/8/8.1/10)
- Version 2.x Fasm, GoAsm, JWasm, UASM (HJWasm) , Masm and PoAsm, using different tools for the various assemblers
--> Easy Code 2.02.0.0007 <--

RadASM 32/64-bit

Current version: 2.2.2.3 - Last updated: 08/01/2018

  1. Downloads
  2. 1 . 74 MB Assembly. zip
  3. 1 . 14 MB HighLevel. zip
  4. 203 KB Language. zip
  5. 4 . 6 MB RadASM- 2 . 2 . 2 . 2 - FullPackage. zip
  6. 1 . 03 MB RadASM- 2 . 2 . 2 . 2 - IDEOnly. zip
  7. 549 KB RAHelp. zip
  8. Source code (zip)
  9. Source code (tar. gz)

RadASM is a lightweight IDE tool for all kinds of programmers who are looking for a straightforward application in which to edit their code.

It supports a wide variety of programming languages and assemblers such has MASM, NASM, FASM, TASM, Borland C, C++, Bcet Basic, VC6 C/C++, Free Pascal and FreeBASIC.

It was created by Ketil Olsen aka KetilO

This is a fork from the original RadASM v2.x source located on SourceForge here

I have made some very minor adjustments, which are detailed in the WhatsNew.txt

Features

User friendly interface.
Project browsing and properties.
Inbuilt programmer tools: ‘Ascii Table’, ‘Colref’, ‘Toolbar Creator’ etc.
Multiple Undo/Redo.
Find & Replace keywords.
Block indents, outdents and comments.
Bookmarks.
Syntax highlighting.
Custom controls.
Automatic code completion.
Record macros for ease of use.
Addins to extend its functionality.
Template system for automatic code generation.
Supports various programming languages.
Setup

Visit the wiki entry Setting up the RadASM environment for more details.

Credits

  1. Written and Programmed by KetilO
  2. Minor updates including Multilingual installer by fearless

Source Code Pro Font

Source Code Pro is a set of OpenType fonts that have been designed to work well in user interface (UI) environments. In addition to a functional OpenType font, this open source project provides all of the source files that were used to build this OpenType font by using the AFDKO makeotf tool.

Download the fonts (OTF, TTF, WOFF, WOFF2, EOT)

Latest release
All releases
Font installation instructions

macOS
Windows
Linux/Unix-based systems

Актуальные книги программисту на X64 ассемблере - --> Link <--
P a s s - Assembler64

  1. AMD64 Architecture Programmer"s Manual\
  2. Russinovich M. - Windows Internals, Part 1 , 6th Edition - 2012 \
  3. Russinovich M. E. , Solomon D. A. , Ionescu A. - Windows Internals, 6th ed. - 2012 \
  4. Windows 7 Device Driver\
  5. Randall Hyde The Art of Assembly Language. epub
  6. Andrew Whitechapel, Sean McKenna- Windows Phone 8 Development Internals- Microsoft Press (2013 ) . pdf
  7. Ata Elahi, Trevor Arjeski auth. ARM Assembly Language with Hardware Experiments. pdf
  8. coder32. pdf
  9. coder64. pdf
  10. Daniel Kusswurm Modern X86 Assembly Language Programming 32 - bit, 64 - bit, SSE, and AVX. pdf
  11. Dmitry Vostokov- Memory Dump Analysis Anthology. 7 - Opentask (2014 ) . pdf
  12. Heiko Falk, Peter Marwedel auth. Source Code Optimization Techniques for Data Flow Dominated Embedded Software. pdf
  13. Jeff Duntemann Assembly Language Step- by- Step Programming with Linux. pdf
  14. Joseph Cavanagh X86 Assembly Language and C Fundamentals. pdf
  15. Kip Irvine Assembly Language for x86 Processors 6th Edition. pdf
  16. Larry D. Pyeatt Modern Assembly Language Programming with the ARM Processor. pdf
  17. Manuel Sojer auth. Reusing Open Source Code Value Creation and Value Appropriation Perspectives on Knowledge Reuse. pdf
  18. Margush, Timothy S Some Assembly Required Assembly Language Programming with the AVR Microcontroller . pdf
  19. Mark E. Russinovich, David A. Solomon, Alex Ionescu- Windows Internals, 5th Edition- Microsoft Press (2009 ) . pdf
  20. Mastering CMake. Ken Martin. 2009 . pdf
  21. Modern X86 Assembly Language Programming 32 - bit, 64 - bit, SSE, and AVX. pdf
  22. Muhammad Ali Mazidi, Sarmad Naimi, Sepehr Naimi, Janice Mazidi ARM Assembly Language Programming and Architecture. pdf
  23. Professor James T. Streib auth. Guide to Assembly Language A Concise Introduction. pdf
  24. Randall Hyde The Art of Assembly Language. pdf
  25. Ray Seyfarth Introduction to 64 Bit Intel Assembly Language Programming for Linux. pdf
  26. Susan Elliott Sim auth. , Susan Elliott Sim, Rosalva E. Gallardo- Valencia eds. Finding Source Code on the Web for Remix and Reuse. pdf
  27. Vincent Mahout Assembly Language Programming ARM Cortex- M3. pdf
  28. Windows Driver Foundation. pdf
  29. Yury Magda Visual C+ + optimization with assembly code . pdf
  30. М. Руссинович, Д. Соломон - Внутреннее устройство Microsoft Windows 6 - е издание (2013 ) . pdf
  31. Род Стивенс - Алгоритмы. Теория и практическое применение. pdf
  32. Руссинович М. , Соломон Д. - Внутреннее устройство Microsoft Windows. Часть 2 . Основные подсистемы ОС. 6 - е издание - 2014 . pdf
  33. KIP R. IRVINE- Assembly Language for x86 Processors- Pearson (2014 )
  34. + IRVINE Src

Что нужно иметь ещё ?! -
1. PoLink или лучше всю папку bin от --> Pelles C <-- для x64
2. ml64 linker и подобное от MS от Visual Studio 2017, можно без оптимизации тоже скопировать всю папку, сегодня места хватает на всё.
3. Возможно конвертёр.H файлов headinc.exe, который поставляется вместе с одной IDE под названием EditMasm от автора хидер файлов, синтаксис одинаковый. Отдельно здесь - --> Link <--
4. Обновлённые макросы для x64 систем - --> Link <--
5. Мини Пакет разработки драйверов совместно с Full SDK 10.0 -

| Сообщение посчитали полезным: zds, plutos, VOLKOFF, elch, Gideon Vi, VodoleY, Vintersorg, HandMill, Orlyonok, DenCoder, Isaev, BlackCode, 4kusNick

Первой вопрос, который, я хочу рассмотреть и который, мне кажется, сейчас очень важным, это 64-битовое программирование. Естественно речь пойдет об ассемблере. В качестве такового возьмем fasm. Во-первых, поддерживает 64-битовое программирование, во-вторых, этот ассемблер является кроссплатформенным. Для того, чтобы начать писать 64-битовые программы достаточно 1. Узнать структуру программы; 2. Использовать правильное соглашение вызова системных и вообще функций.
Программирование в Windows.
В 64-битовых Windows принята следующая конвенция вызова функций (в том числе вызов функций API).
1. Первые четыре параметра передаются в функцию через регистры: rcx, rdx, r8, r9. Остальные параметры (если они есть) передаются через стек.
2. Перед вызовом функции резервируется область в стеке, на случай, если вызываемая функция "захочет" временно сохранить параметры в стеке. Таким образом, параметры, которые передаются через стек, помещаются туда после резервируемой области.
3. При передаче параметров, размер которых меньше 64 бит, передаются как 64-битовые параметры. При этом следует обнулить старшие биты. Параметры, большие 64-бит передаются по ссылке.
4. Данные возвращаются через регистр rax. Если возвращаемое значение имеет размер больший 64 бит, то данное передается через область памяти, адрес которой передается в первом параметре. Для возвращения может также использоваться регистр xmm0.
5. Все регистры при вызове функций сохраняются за исключением rax, rcx, rdx, r8, r9, r10, r11, сохранность которых не гарантируется.
6. Граница стека должна быть выровнена по адресу кратному 16.
Рассмотрим в общих чертах схему вызова функции API с пятью параметрами.
...
sub rsp,40 ; резервируем стек
mov qword ptr ,par5
mov r9,par4
mov r8,par3,
mov rdx,par2
mov rcx,par1
call f_api64
...
add rsp,40 ;восстанавливаем стек
...

Обратите внимание, что в нашем случае стек с учетом адреса возврата при вызове функции оказывается выровненным на величину кратную 16 (48 байт). С учетом такого выравнивания вызов функции, содержащей только 4 параметра будет выглядеть так

sub rsp,40 ; резервируем стек
mov r9,par4
mov r8,par3,
mov rdx,par2
mov rcx,par1
call f_api64
...
add rsp,40 ;восстанавливаем стек

Рассмотрим следующую программу

format PE64 GUI
entry start
section ".text" code readable executable
start:
sub rsp,8*5
mov r9,0
lea r8,[_caption]
lea rdx,[_message]
mov rcx,0
call
add rsp,40
sub rsp,16
mov ecx,eax
call
section ".data" data readable writeable
_caption db "Win64 assembly program",0
_message db "Hello World!",0
section ".idata" import data readable writeable
dd 0,0,0,RVA kernel_name,RVA kernel_table
dd 0,0,0,RVA user_name,RVA user_table
dd 0,0,0,0,0
kernel_table:
ExitProcess dq RVA _ExitProcess
dq 0
user_table:
MessageBoxA dq RVA _MessageBoxA
dq 0
kernel_name db "KERNEL32.DLL",0
user_name db "USER32.DLL",0
_ExitProcess dw 0
db "ExitProcess",0
_MessageBoxA dw 0
db "MessageBoxA",0

Если имя программы prog.asm, то компилируется она просто командой
fasm prog.asm

Как видите, в Windows все просто.

Пирогов В.Ю , (



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows