Ассемблер. вступление
Содержание:
CreateDialogParam+WndProc+Class
здесь
Код (ASM):
- format PE GUI
- include ‘win32ax.inc’
- ; import data code in the same section
- IDM_GETTEXT equ
- IDM_CLEAR equ 1
- IDM_EXIT equ 2
- IDC_BUTTON equ 3
- IDC_EXIT equ 4
- IDC_EDIT equ 5
- IDC_MENU equ 100
- IDC_DIALOG equ 200
- size_of_buffer equ 96
- ; +—————————+
- ; | register the window class |
- ; +—————————+
- start xor ebx,ebx
- mov esi,400000h
- invoke RegisterClassEx,esp,sizeof.WNDCLASSEX,CS_HREDRAW or CS_VREDRAW,WndProc,\
- ebx,DLGWINDOWEXTRA,esi,ebx,10005h,COLOR_BTNFACE+1,IDC_MENU,ClassName,ebx
- invoke CreateDialogParam,esi,IDC_DIALOG,ebx,ebx,ebx
- mov edi,esp
- mov edi+sizeof.MSG,eax
- @@ invoke GetMessage,edi,ebx,ebx,ebx
- or eax,eax
- jz exit_msg_loop
- invoke IsDialogMessage,dwordedi+sizeof.MSG,edi
- or eax,eax
- jnz @r
- invoke DispatchMessage,edi
- jmp @r
- exit_msg_loopinvoke ExitProcess,
- ; +———————-+
- ; | the dialog procedure |
- ; +———————-+
- proc WndProc hDlg, uMsg, wParam, lParam
- local buffer size_of_bufferBYTE
- lea esi,buffer
- mov edi,hDlg
- cmp uMsg,WM_CLOSE
- jz wmCLOSE
- cmp uMsg,WM_COMMAND
- jz wmCOMMAND
- leave
- jmp DefDlgProc
- wmCOMMANDmovzx eax,wordwParam
- cmp lParam,
- jz @f; choose menu
- ; choose button or EditBox
- cmp wordwParam+2,BN_CLICKED
- jne wmBYE
- @@ jmp dword handlers+eax*4
- GETTEXTinvoke GetDlgItemText,edi,IDC_EDIT,esi,size_of_buffer
- invoke MessageBox,NULL,esi,AppName,MB_OK
- jmp wmBYE
- SAYHELLOpush TestString
- jmp @f
- CLEAR push
- @@ invoke SetDlgItemText,edi,IDC_EDIT
- jmp wmBYE
- wmCLOSEinvoke DestroyWindow,edi
- invoke PostQuitMessage,
- wmBYE ret
- handlers dd GETTEXT,CLEAR,wmCLOSE,SAYHELLO,wmCLOSE
- endp
- ;——————————-
- ;experiment variables
- TestString db «Wow! I’m in an edit box now»,
- AppName db «Our Third Dialog Box»,
- ClassName db ‘DLGCLASS’,
- ;——————————-
- data import
- library USER32, ‘USER32.DLL’,\
- kernel32, ‘kernel32.dll’
- import USER32,\
- DispatchMessage, ‘DispatchMessageA’,\
- GetMessage, ‘GetMessageA’,\
- PostQuitMessage, ‘PostQuitMessage’,\
- MessageBox, ‘MessageBoxA’,\
- IsDialogMessage, ‘IsDialogMessageA’,\
- SetDlgItemText, ‘SetDlgItemTextA’,\
- CreateDialogParam, ‘CreateDialogParamA’,\
- GetDlgItemText, ‘GetDlgItemTextA’,\
- RegisterClassEx, ‘RegisterClassExA’,\
- DefDlgProc, ‘DefDlgProcA’,\
- DestroyWindow, ‘DestroyWindow’
- import kernel32,\
- ExitProcess, ‘ExitProcess’
- end data
- section ‘.rsrc’ resource from ‘tut_10c.res’ data readable
Код (ASM):
- #include «resource.h»
- #define IDM_GETTEXT
- #define IDM_CLEAR 1
- #define IDM_EXIT 2
- #define IDC_BUTTON 3
- #define IDC_EXIT 4
- #define IDC_EDIT 5
- #define IDC_MENU 100
- #define IDC_DIALOG 200
- IDC_DIALOG DIALOGEX 10, 10, 230, 60
- STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
- WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
- CAPTION «Iczelion Tutorial #10c: CreateDialogParam+WndProc+Class»
- CLASS «DLGCLASS»
- BEGIN
- EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT |WS_TABSTOP
- DEFPUSHBUTTON «Say Hello», IDC_BUTTON,141,10,52, 13,WS_TABSTOP
- PUSHBUTTON «E&xit», IDC_EXIT, 141,26,52, 13,WS_TABSTOP
- END
- IDC_MENU MENU
- BEGIN
- POPUP «Test Controls»
- BEGIN
- MENUITEM «Get Text», IDM_GETTEXT
- MENUITEM «Clear Text», IDM_CLEAR
- MENUITEM SEPARATOR
- MENUITEM «E&xit», IDM_EXIT
- END
- END
Результат
Повышение производительности с использованием uop-кэша на Sandy Bridge+
В современных x86 процессорах Intel конвеер можно разделить на 2 части: Front End и Back End.
Front End отвечает за загрузку кода из памяти и его декодирование в микрооперации.
Back End отвечает за выполнение микроопераций, пришедших от Front End. Поскольку эти микрооперации могут выполняться ядром не по порядку, то Back End также следит за тем, чтобы результат выполнения этих микроопераций строго соответствовал порядку в котором они идут в коде.
В большинстве случаев неэффективное использование Front End’a не оказывает заметного влияние на производительность. Пиковая пропускная способность на большинстве процессоров Intel — 4 микрооперации за такт, поэтому, например, для Memory/L3-bound кода ЦПУ не сможет полностью ее утилизировать.
Однако в некоторых случаях различие в производительности может быть достаточно существенно. Под катом — анализ влияния кэша микроопераций на производительность.
Формат записи инструкций
Используется Intel-синтаксис записи инструкций.
Единственное существенное отличие от формата, принятого в других ассемблерах (MASM, TASM в режиме совместимости с MASM) — значение ячейки памяти всегда записывается как , а просто означает адрес (то есть порядковый номер) ячейки. Это позволяет обходиться без ключевого слова . Также в fasm при переопределении размера операнда вместо пишется просто , вместо — и т. д. Не позволяется использовать несколько квадратных скобок в одном операнде, таким образом вместо необходимо писать . Эти изменения синтаксиса привели к более унифицированному и лёгкому для чтения коду.
What options are there to parse other kinds of syntax?
In some cases a command that assembler needs to parse may begin with
something different than a name of instruction or a label. It may be
that a name is preceded by a special character, like «.» or «!»,
or that it is an entirely different kind of construction. It is then
necessary to use «macro ?» to intercept whole lines of source text
and process any special syntax of such kind.
For example, if it was required to allow a command written as «.CODE»,
it would not be possible to implement it directly as a macroinstruction,
because initial dot causes the symbol to be interpreted as a local one
and globally defined instruction could never be executed this way.
The intercepting macroinstruction provides a solution:
macro ? line&
match .=CODE?, line
CODE
else match .=DATA?, line
DATA
else
line
end match
end macro
The lines that contain either «.CODE» or «.DATA» text are processed here
in such a way, that they invoke the global macroinstruction with
corresponding name, while all other intercepted lines are executed without
changes. This method allows to filter out any special syntax and let
the assembler process the regular instructions as usual.
Sometimes unconventional syntax is expected only in a specific area
of source text, like inside a block with defined boundaries. The
parsing macroinstruction should then be applied only in this place,
and removed with «purge» when the block ends:
macro concise
macro ? line&
match =end =concise, line
purge ?
else match dest+==src, line
ADD dest,src
else match dest-==src, line
SUB dest,src
else match dest==src, line
LD dest,src
else match dest++, line
INC dest
else match dest--, line
DEC dest
else match any, line
err "syntax error"
end match
end macro
end macro
concise
C=0
B++
A+=2
end concise
A macroinstruction defined this way does not intercept lines that contain
directives controlling the flow of the assembly, like «if» or «repeat», and
they can still be used freely inside such a block. This would change if
the declaration was in the form «macro ?! line&». Such a variant would
intercept every line with no exception.
Another option to catch special commands might be to use «struc ?»
to intercept only lines that do not start with a known instruction
(the initial symbol is then treated as label). Since this one only tests
unknown commands, it should cause less overhead on the assembly:
struc (head) ? tail&
match .=CODE?, head
CODE tail
else
head tail
end match
end struc
All these approaches hide a subtle trap. A label defined with «:» may be
followed by another instruction in the same line. If that next instruction
(which here becomes hidden in the «tail» parameter) is a control directive
like «if», putting it inside the «else» clause is going to cause broken nesting
of control blocks. A possible solution is to somehow invoke «tail» contents
outside of «match» block. One way could be to call a special macro:
struc (head) ? tail&
local invoker
match .=CODE?, head
macro invoker
CODE tail
end macro
else
macro invoker
head tail
end macro
end match
invoker
end struc
A simpler option is to call the original line directly and when override
is needed, cause it to be ignored with help of another line interceptor
(disposing of itself immediately after):
struc (head) ? tail&
match .=CODE?, head
CODE tail
macro ? line&
purge ?
end macro
end match
head tail
end struc
However, a much better way of avoiding this kinds of pitfalls is to use
CALM instructions instead of standard macros. There it is possible to
process arguments and assemble the original or modified line without
use of any control directives. CALM instructions also offer a much better
performance, which might be especially important in case of interceptors
that get called for nearly every line in source text.
Процесс компиляции
Компиляция исходного кода проходит в несколько изолированных друг от друга стадий.
Препроцессирование
Первая стадия, она выполняется в 1 проход, вначале исходный текст токенизируется, затем в нем распознаются и обрабатываются все директивы процессора, раскрываются все макросы и все символические константы. Поскольку дополнительных проходов этой стадией не предусмотрено любой элемент языка обрабатываемый на этой стадии должен быть вначале объявлен, и только затем он может быть использован.
Синтаксический анализ
Вторая стадия, на этой стадии происходит дополнительная классификация токенов (т.к. даже типы токенов и требования к ним на стадиях препроцессирования и ассемблирования немного разные), для некоторых токенов создаются дополнительные структуры свойств, которые впоследствии будут использоваться при ассемблировании.
Ассемблирование
Главная стадия, выполняется в столько проходов, сколько потребуется на разрешение всех неоднозначностей прохода предшествующего, на этой стадии определяются адреса меток, обрабатываются условные директивы, раскрываются циклы.
Приведение выходных данных к запрошенному формату
Блоки выходных данных обрамляются необходимыми для формата заголовками, обрабатываются взаимосвязи данных и заголовков, рассчитываются контрольные суммы, размеры блоков… Генерируется выходной файл.
#Инструменты
Для нормально функционирования нам понадобятся следующие вещи:
- Собственно какая-либо unix-совместимая ось. (например linux, или лучше FreeBSD ),
- Компилятор fasm. ( www.flatassembler.net )
- Линкер ld ( есть почти в любом дистрибутиве unix ),
- Особый склад ума, причем последнее — самое главное. Если у вас этого нет, то ни один, даже самый последний RedHat на пару со свежим fasm’ом вам не поможет.
И еще, о компиляторах — в unix обычно используются AS с AT&T синтаксисом, который для многих людей, выросших на tasm’е и masm’е, кажется полной абракадаброй. Поэтому, для начала, мы будем использовать привычные компиляторы с Intel’овским синтаксисом (fasm или nasm). Хотя позже, если найдутся желающие, можно будет рассмотреть и AT&T asm.
Префиксы повторения
Префикс REP, если он установлен перед строковой инструкцией, например, , вызывает повторение инструкции на основе счётчика, размещённого в регистре CX. выполняет инструкцию, уменьшает CX на 1 и проверяет, равен ли CX нулю. Он повторяет обработку инструкций, пока CX не станет равным нулю.
Флаг направления (DF) определяет направление операции:
Используйте (англ. «Clear Direction Flag» = «Сбросить флаг направления», DF = 0), чтобы выполнить операцию слева направо.
Используйте (англ. «Set Direction Flag» = «Установить флаг направления», DF = 1), чтобы выполнить операцию справа налево.
Префикс REP также имеет следующие вариации:
— это безусловное повторение, которое повторяет операцию, пока CX не станет равным нулю.
или — это условное повторение, которое повторяет операцию до тех пор, пока нулевой флаг указывает на равенство/ноль. Он останавливается, когда ZF указывает на неравенство/ноль или, когда CX равен нулю.
или — это также условное повторение, которое повторяет операцию до тех пор, пока нулевой флаг указывает на неравенство/ноль. Он останавливается, когда ZF указывает на равенство/ноль или, когда CX уменьшается до нуля.
#Общие сведения
Unix, который мы будем использовать — 32 битная система, работающая в защищенном режиме, и использующая плоскую модель памяти.
Как и большинство операционных систем, Unix предоставляет программе набор различных функций (по другому — Api). Но, в отличие от, например, WinAPI, где вызовы производятся с помощью call’ов, в unix — больше свободы: можно вызывать функция ядра напрямую, а можно использовать многочисленные библиотеки. Рассмотрим для начала первый способ.
Системный вызов производится с помощью прерывания 0x80 (чаще всего). К сожалению, (а может и к счастью) существует несколько конвенций вызова, что приводит к несовместимости кода между многими unix-like осями. Я рассмотрю только две, самые популярные платформы: Linux и *BSD.
FreeBSD (а также OpenBSD и NetBSD)
Эта система использует традиционную unix конвенцию вызова: номер функции помещается в eax, параметры в стек, вызов производится с помощью функции содержащей int 0x80, а результат возвращается в eax.
Наверное, понятнее будет, если рассмотреть это на примере:
sys_call:
int 0x80
ret
start:
push msg_len ; размер строки
push msg ; адрес строки
push 1 ; stdout
mov eax,4 ; номер системной функции - sys_write
call sys_call
add esp,4*3 ; очищаем за собой стек
Впрочем, от функции sys_call можно отказаться, достаточно просто помещать в стек лишний dword:
start:
push msg_len ; размер строки
push msg ; адрес строки
push 1 ; stdout
mov eax,4 ; номер системной функции - sys_write
push eax ; все что угодно
int 0x80
add esp,4*3 ; очищаем за собой стек
Также FreeBSD поддерживает конвенцию вызова, применяемую в linux. Для это необходимо включить linux emulation. Еще эта эмуляция потребуется для запуска fasm. А еще нужна утилита brandelf (наверняка она у вас есть). Дело в том, что пока не существует версии fasm’а конкретно для BSD систем. Но это легко исправить, вот так:
Brandelf –t Linux fasm
Если это не сработает (а такое возможно из-за не совместимости форматов), придется перекомпилировать fasm, заменив формат файла “format PE executable” на простой “format ELF”, а потом слинковать ld.
Linux
В линуксе используется fastcall конвенция. Номер функции, все так же,
помещается в eax, а вот параметры, вместо стека, помещаются в регистры. Пример:
mov edx,msg_len
mov ecx,msg
mov ebx,1
mov eax,4
int 0x80
Порядок размещения параметров такой:
| № параметра | Регистры |
| 1 | ebx |
| 2 | ecx |
| 3 | edx |
| 4 | esi |
| 5 | edi |
| 6 | ebp |
Как видите максимальное количество параметров — 6. Если их больше,
приходиться помещать все параметры в структуру и передавать ее адрес в ebx.