Ассемблер. вступление

Содержание:

CreateDialogParam+WndProc+Class

здесь
Код (ASM):

  1. format PE GUI
  2. include ‘win32ax.inc’
  3. ; import data code in the same section
  4. IDM_GETTEXT equ
  5. IDM_CLEAR equ 1
  6. IDM_EXIT equ 2
  7. IDC_BUTTON equ 3
  8. IDC_EXIT equ 4
  9. IDC_EDIT equ 5
  10. IDC_MENU equ 100
  11. IDC_DIALOG equ 200
  12. size_of_buffer equ 96
  13.  ; +—————————+
  14.  ; | register the window class |
  15.  ; +—————————+
  16. start xor ebx,ebx
  17. mov esi,400000h
  18. invoke RegisterClassEx,esp,sizeof.WNDCLASSEX,CS_HREDRAW or CS_VREDRAW,WndProc,\
  19. ebx,DLGWINDOWEXTRA,esi,ebx,10005h,COLOR_BTNFACE+1,IDC_MENU,ClassName,ebx
  20. invoke CreateDialogParam,esi,IDC_DIALOG,ebx,ebx,ebx
  21. mov edi,esp
  22. mov edi+sizeof.MSG,eax
  23. @@ invoke GetMessage,edi,ebx,ebx,ebx
  24. or eax,eax
  25. jz exit_msg_loop
  26. invoke IsDialogMessage,dwordedi+sizeof.MSG,edi
  27. or eax,eax
  28. jnz @r
  29. invoke DispatchMessage,edi
  30. jmp @r
  31. exit_msg_loopinvoke ExitProcess,
  32.  ; +———————-+
  33.  ; | the dialog procedure |
  34.  ; +———————-+
  35. proc WndProc hDlg, uMsg, wParam, lParam
  36. local buffer size_of_bufferBYTE
  37. lea esi,buffer
  38. mov edi,hDlg
  39. cmp uMsg,WM_CLOSE
  40. jz wmCLOSE
  41. cmp uMsg,WM_COMMAND
  42. jz wmCOMMAND
  43. leave
  44. jmp DefDlgProc
  45. wmCOMMANDmovzx eax,wordwParam
  46. cmp lParam,
  47. jz @f; choose menu
  48. ; choose button or EditBox
  49. cmp wordwParam+2,BN_CLICKED
  50. jne wmBYE
  51. @@ jmp dword handlers+eax*4
  52. GETTEXTinvoke GetDlgItemText,edi,IDC_EDIT,esi,size_of_buffer
  53. invoke MessageBox,NULL,esi,AppName,MB_OK
  54. jmp wmBYE
  55. SAYHELLOpush TestString
  56. jmp @f
  57. CLEAR push
  58. @@ invoke SetDlgItemText,edi,IDC_EDIT
  59. jmp wmBYE
  60. wmCLOSEinvoke DestroyWindow,edi
  61. invoke PostQuitMessage,
  62. wmBYE ret
  63. handlers dd GETTEXT,CLEAR,wmCLOSE,SAYHELLO,wmCLOSE
  64. endp
  65. ;——————————-
  66. ;experiment variables
  67. TestString db «Wow! I’m in an edit box now»,
  68. AppName    db «Our Third Dialog Box»,
  69. ClassName  db ‘DLGCLASS’,
  70. ;——————————-
  71. data import
  72.      library   USER32, ‘USER32.DLL’,\
  73.       kernel32, ‘kernel32.dll’
  74.      import    USER32,\
  75. DispatchMessage, ‘DispatchMessageA’,\
  76. GetMessage, ‘GetMessageA’,\
  77. PostQuitMessage, ‘PostQuitMessage’,\
  78. MessageBox, ‘MessageBoxA’,\
  79. IsDialogMessage, ‘IsDialogMessageA’,\
  80. SetDlgItemText, ‘SetDlgItemTextA’,\
  81. CreateDialogParam, ‘CreateDialogParamA’,\
  82. GetDlgItemText, ‘GetDlgItemTextA’,\
  83. RegisterClassEx, ‘RegisterClassExA’,\
  84. DefDlgProc, ‘DefDlgProcA’,\
  85. DestroyWindow, ‘DestroyWindow’
  86.       import kernel32,\
  87. ExitProcess, ‘ExitProcess’
  88. end data
  89. section ‘.rsrc’ resource from ‘tut_10c.res’ data readable

Код (ASM):

  1. #include «resource.h»
  2. #define IDM_GETTEXT    
  3. #define IDM_CLEAR     1
  4. #define IDM_EXIT     2
  5. #define IDC_BUTTON     3
  6. #define IDC_EXIT     4
  7. #define IDC_EDIT     5
  8. #define IDC_MENU 100
  9. #define IDC_DIALOG 200
  10. IDC_DIALOG DIALOGEX 10, 10, 230, 60
  11. STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
  12. WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
  13. CAPTION «Iczelion Tutorial #10c: CreateDialogParam+WndProc+Class»
  14. CLASS «DLGCLASS»
  15. BEGIN
  16.     EDITTEXT IDC_EDIT,   15,17,111,13, ES_AUTOHSCROLL | ES_LEFT |WS_TABSTOP
  17.     DEFPUSHBUTTON   «Say Hello», IDC_BUTTON,141,10,52, 13,WS_TABSTOP
  18.     PUSHBUTTON    «E&xit», IDC_EXIT,  141,26,52, 13,WS_TABSTOP
  19. END
  20. IDC_MENU  MENU
  21. BEGIN
  22.     POPUP «Test Controls»
  23.     BEGIN
  24. MENUITEM «Get Text», IDM_GETTEXT
  25. MENUITEM «Clear Text», IDM_CLEAR
  26. MENUITEM SEPARATOR
  27. MENUITEM «E&xit», IDM_EXIT
  28.     END
  29. 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 проход, вначале исходный текст токенизируется, затем в нем распознаются и обрабатываются все директивы процессора, раскрываются все макросы и все символические константы. Поскольку дополнительных проходов этой стадией не предусмотрено любой элемент языка обрабатываемый на этой стадии должен быть вначале объявлен, и только затем он может быть использован.

Синтаксический анализ

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

Ассемблирование

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

Приведение выходных данных к запрошенному формату

Блоки выходных данных обрамляются необходимыми для формата заголовками, обрабатываются взаимосвязи данных и заголовков, рассчитываются контрольные суммы, размеры блоков… Генерируется выходной файл.

#Инструменты

Для нормально функционирования нам понадобятся следующие вещи:

  1. Собственно какая-либо unix-совместимая ось. (например linux, или лучше FreeBSD ),
  2. Компилятор fasm. ( www.flatassembler.net )
  3. Линкер ld ( есть почти в любом дистрибутиве unix ),
  4. Особый склад ума, причем последнее — самое главное. Если у вас этого нет, то ни один, даже самый последний 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.

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

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