Макрос — макрокоманда, макроопределение.
У большинства популярных ассемблеров (TASM, MASM, FASM), имеется определённая «вкусность», которая помогает писать более читабельный и понятный код, а также уменьшает вероятность ошибок. Мы имеем ввиду макросы. Макрос — миникод, который определяет алгоритм действий основных команд ассемблера. Этот код либо уже создан и входит в комплект ассемблера, либо пишется пользователем самостоятельно. В данной статье мы выясним, как использовать макрос функции (процедуры), встроенный в TASM.
Как уже говорилось, в коде одной программы могут быть реализованы несколько приемлемых вариантов и не только стандартных (конвенционных) — выбор за вами. Общие понятия и ключевые моменты изложены в статье: «MS-DOS и TASM 2.0. Часть 15. Процедуры (функции)» и мы не будем повторяться.
Мы будем использовать макроопределение вызова процедуры PASCAL, которое поддерживается TASM.
Реализация конвенции PASCAL в TASM.
Не будем копать слишком глубоко — нам необходимо понимание наиболее оптимальных способов организации вызова функций при программировании с использованием TASM в MS-DOS.
Мы рассмотрим пример реализации конвенции PASCAL. Это связано с тем, что мы используем TASM — программный пакет компании Borland. А Borland — это синоним слову «Рascal» (Object Pascal->Borland Delphi->Embarcadero Delphi). А в TASM конвенция вызова функций PASCAL реализована очень круто для своего времени посредством макроса и существенно упрощает программирование ассемблером в системе MS-DOS.
Конвенция вызова функций PASCAL (Pascal, Basic, Fortran и др.): параметры загоняются в стек слева направо — сверху вниз, стек очищается вызываемой функцией.
Функция (процедура) содержит пять параметров:
myFunc (a,b,c,d,e)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | ;Ассемблерный код: push a; Первый параметр (самый левый) - сверху push b; Второй параметр push c; push d; push e; Пятый параметр - снизу call myFunc ... ;---------------------------------------------------------------------- ;Функция содержит пять параметров: myFunc: push bp mov bp,sp; Создаём стековый кадр. В bp - указатель на стековый кадр, регистр bp использовать нельзя! a equ [bp+12]; Первый параметр - сверху ([bp+12]) b equ [bp+10] c equ [bp+8] d equ [bp+6] e equ [bp+4] ... (команды, которые могут использовать стек): mov ax,e ; считать параметр 5 - [bp+4]. Можно и так, но это менее понятно: mov ax,[bp+4] ; Его адрес в сегменте стека ВР + 4, потому что при выполнении ; команды CALL при вызове функции, в стек поместили адрес возврата - 2 байта для процедуры ; типа NEAR (или 4 - для FAR), а потом еще и ВР - 2 байта (push bp - в начале нашей функции) mov bx,с ; считать параметр 3 - [bp+8]. Можно и так, но это менее понятно: mov bx,[bp+8] (ещё команды) ... pop bp ret 10 ;Из стека дополнительно извлекается 10 байт - стек освобождает вызываемая функция |
Ниже — исходный код несколько модернизированной программы goblin.asm. Конвенция PASCAL реализована по всем правилам, «ручками» и без всяких макросов.
Пример чистой реализации конвенции PASCAL.
Исходный код GBLPROC1.ASM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | ;goblproc1.asm .model tiny ; for СОМ .code ; code segment start org 100h ; offset in memory = 100h (for COM) start: main proc begin: mov ah,09h mov dx,offset prompt int 21h inpt: mov ah,01h int 21h cmp al,'m' je mode_man cmp al,'w' je mode_woman push offset mes_gobl push offset mes_wow call my_prnt_func ;call my_prnt_func PASCAL,offset mes_gobl, offset mes_wow jmp begin mode_man: push offset mes_man push offset mes_wow call my_prnt_func ;call my_prnt_func PASCAL, offset mes_man, offset mes_wow; addr mes_man,addr mes_man jmp cont mode_woman: push offset mes_womn push offset mes_wow call my_prnt_func ;call my_prnt_func PASCAL, offset mes_womn, offset mes_wow cont: ;call my_prnt_func mov ax,4c00h int 21h main endp ;my_prnt_func proc PASCAL pMessage1:WORD, pMessage2:WORD my_prnt_func proc near push bp mov bp,sp pMessage1 equ [bp+6] pMessage2 equ [bp+4] mov dx,pMessage2 mov ah,09h int 21h mov ah,09h mov dx,pMessage1 int 21h pop bp ret 4; Чистим стек my_prnt_func endp ;DATA prompt db 'Are you Man or Woman [m/w]? : $' mes_wow db 0Dh,0Ah,"Wow!",0Dh,0Ah,24h ; строка для вывода. 24h = '$' . mes_man db 0Dh,0Ah,"Hello, Strong Man!",0Dh,0Ah,'$' ; строка для вывода. Вместо ASCII смвола '$' можно написать машинный код 24h mes_womn db 0Dh,0Ah,"Hello, Beautyful Woman!",0Dh,0Ah,'$' ; строка для вывода mes_gobl db 0Dh,0Ah,"Hello, Strong and Beautyful GOBLIN!",0Dh,0Ah,24h ; строка для вывода. 24h = '$' . len = $ - mes_gobl;len equ $ - mes_gobl end start |
Можете прогнать наш GBLPROC1.COM через IDA и посмотреть, как сработал компилятор.
Макрос функции конвенции PASCAL, реализованный в TASM.
Теперь будем использовать встроенный в TASM макрос функции. Он очень прост:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | call my_prnt_func PASCAL, offset arg1, offset arg2; ..... my_prnt_func proc PASCAL pMessage1:WORD, pMessage2:WORD local tmp:WORD; локальная переменная mov ax,pMessage2 mov tmp,ax mov ah,09h mov dx,pMessage1 int 21h mov ah,09h mov dx,tmp int 21h ret my_prnt_func endp |
Исходный код GBLPROC2.ASM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | ;gblproc2.asm .model tiny ; for СОМ .code ; code segment start org 100h ; offset in memory = 100h (for COM) start: main proc begin: mov ah,09h mov dx,offset prompt int 21h inpt: mov ah,01h int 21h cmp al,'m' je mode_man cmp al,'w' je mode_woman call my_prnt_func PASCAL,offset mes_gobl, offset mes_wow; макрос функции jmp begin mode_man: call my_prnt_func PASCAL, offset mes_man, offset mes_wow; addr mes_man,addr mes_man; макрос функции jmp cont mode_woman: call my_prnt_func PASCAL, offset mes_womn, offset mes_wow; макрос функции! cont: mov ax,4c00h int 21h main endp my_prnt_func proc PASCAL pMessage1:WORD, pMessage2:WORD; макрос функции local tmp:WORD mov ax,pMessage2 mov tmp,ax mov ah,09h mov dx,pMessage1 int 21h mov ah,09h mov dx,tmp int 21h ret my_prnt_func endp ;DATA prompt db 'Are you Man or Woman [m/w]? : $' mes_wow db 0Dh,0Ah,"Wow!",0Dh,0Ah,24h ; строка для вывода. 24h = '$' . mes_man db 0Dh,0Ah,"Hello, Strong Man!",0Dh,0Ah,'$' ; строка для вывода. Вместо ASCII смвола '$' можно написать машинный код 24h mes_womn db 0Dh,0Ah,"Hello, Beautyful Woman!",0Dh,0Ah,'$' ; строка для вывода mes_gobl db 0Dh,0Ah,"Hello, Strong and Beautyful GOBLIN!",0Dh,0Ah,24h ; строка для вывода. 24h = '$' . len = $ - mes_gobl;len equ $ - mes_gobl end start |
Прогоняем код через IDA.
чуть-чуть подрабатываем ручками комментарии (для Вас, исключительно, для Вас — любимых!) и сохраняем в ассемблерном варианте (GBLARC2.ASM) , а также в текстовом варианте (GBLPROC2.LST).
GBLPROC2.ASM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | ; ; ╔═════════════════════════════════════════════════════════════════╗ ; ║ This file is generated by The Interactive Disassembler (IDA) FW ║ ; ║ Copyright (c) 1997 by DataRescue sprl, <ida@datarescue.com> ║ ; ║ Professional version of IDA is at http://www.idapro.com ║ ; ╚═════════════════════════════════════════════════════════════════╝ ; ;──────────────────────────────────────────────────────────────────────────── ; File Name D:\UTILS\IDAFW\GBLPROC2.COM ; Format COM File ; Base Address: 1000h Range: 10100h - 10205h Loaded length: 0105h ;════════════════════════════════════════════════════════════════════════════ seg000 segment byte public 'CODE' assume cs:seg000 org 100h assume es:nothing, ss:nothing, ds:seg000 start: ; CODE XREF: seg000:012Aj mov ah, 9 mov dx, 182h; offset prompt int 21h ; DOS - PRINT STRING ; DS:DX -> string terminated by "$" mov ah, 1 int 21h ; DOS - KEYBOARD INPUT ; Return: AL = character read cmp al, 6Dh jz mode_man cmp al, 77h jz mode_woman push ax push bp mov bp, sp mov word ptr [bp+2], 1DDh; offset mes_gobl pop bp push ax push bp mov bp, sp mov word ptr [bp+2], 1A1h; offset mes_wow pop bp call my_print_func jmp short start ;──────────────────────────────────────────────────────────────────────────── mode_man: ; CODE XREF: seg000:010Dj push ax push bp mov bp, sp mov word ptr [bp+2], 1AAh; offset mes_man pop bp push ax push bp mov bp, sp mov word ptr [bp+2], 1A1h; offset mes_wow pop bp call my_print_func jmp short cont ;──────────────────────────────────────────────────────────────────────────── nop mode_woman: ; CODE XREF: seg000:0111j push ax push bp mov bp, sp mov word ptr [bp+2], 1C1h; offset mes_woman pop bp push ax push bp mov bp, sp mov word ptr [bp+2], 1A1h; offset mes_wow pop bp call my_print_func cont: ; CODE XREF: seg000:0143j mov ax, 4C00h int 21h ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT) ; AL = exit code ;████████████████████████████████████████████████████████████████████████████ ; S u b r o u t i n e my_print_func proc near ; CODE XREF: seg000:0127p seg000:0140p ; ... push bp mov bp, sp sub sp, 2 mov ax, [bp+4] mov [bp-2], ax mov ah, 9 mov dx, [bp+6] int 21h ; DOS - PRINT STRING ; DS:DX -> string terminated by "$" mov ah, 9 mov dx, [bp-2] int 21h ; DOS - PRINT STRING ; DS:DX -> string terminated by "$" mov sp, bp pop bp retn 4 my_print_func endp ;──────────────────────────────────────────────────────────────────────────── prompt db 'Are you Man or Woman [m/w]? : $' mes_wow db 0Dh,0Ah db 'Wow!',0Dh,0Ah,'$' mes_man db 0Dh,0Ah db 'Hello, Strong Man!',0Dh,0Ah,'$' mes_woman db 0Dh,0Ah db 'Hello, Beautyful Woman!',0Dh,0Ah,'$' mes_gobl db 0Dh,0Ah db 'Hello, Strong and Beautyful GOBLIN!',0Dh,0Ah,'$' seg000 ends end start |
GBLPROC2.LST
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | ; ; ╔═════════════════════════════════════════════════════════════════╗ ; ║ This file is generated by The Interactive Disassembler (IDA) FW ║ ; ║ Copyright (c) 1997 by DataRescue sprl, <ida@datarescue.com> ║ ; ║ Professional version of IDA is at http://www.idapro.com ║ ; ╚═════════════════════════════════════════════════════════════════╝ ; seg000:0100 ;──────────────────────────────────────────────────────────────────────────── seg000:0100 ; File Name D:\UTILS\IDAFW\GBLPROC2.COM seg000:0100 ; Format COM File seg000:0100 ; Base Address: 1000h Range: 10100h - 10205h Loaded length: 0105h seg000:0100 seg000:0100 seg000:0100 ;════════════════════════════════════════════════════════════════════════════ seg000:0100 seg000:0100 seg000 segment byte public 'CODE' seg000:0100 assume cs:seg000 seg000:0100 org 100h seg000:0100 seg000:0100 assume es:nothing, ss:nothing, ds:seg000 seg000:0100 seg000:0100 start: ; CODE XREF: seg000:012Aj seg000:0100 mov ah, 9 seg000:0102 mov dx, 182h; seg000:0182 prompt seg000:0105 int 21h ; DOS - PRINT STRING seg000:0105 ; DS:DX -> string terminated by "$" seg000:0107 mov ah, 1 seg000:0109 int 21h ; DOS - KEYBOARD INPUT seg000:0109 ; Return: AL = character read seg000:010B cmp al, 6Dh seg000:010D jz mode_man seg000:010F cmp al, 77h seg000:0111 jz mode_woman seg000:0113 push ax seg000:0114 push bp seg000:0115 mov bp, sp seg000:0117 mov word ptr [bp+2], 1DDh; seg000:01DD mes_gobl seg000:011C pop bp seg000:011D push ax seg000:011E push bp seg000:011F mov bp, sp seg000:0121 mov word ptr [bp+2], 1A1h; seg000:01A1 mes_wow seg000:0126 pop bp seg000:0127 call my_print_func seg000:012A jmp short start seg000:012C ;──────────────────────────────────────────────────────────────────────────── seg000:012C seg000:012C mode_man: ; CODE XREF: seg000:010Dj seg000:012C push ax seg000:012D push bp seg000:012E mov bp, sp seg000:0130 mov word ptr [bp+2], 1AAh; seg000:01AA mes_man seg000:0135 pop bp seg000:0136 push ax seg000:0137 push bp seg000:0138 mov bp, sp seg000:013A mov word ptr [bp+2], 1A1h; seg000:01A1 mes_wow seg000:013F pop bp seg000:0140 call my_print_func seg000:0143 jmp short cont seg000:0145 ;──────────────────────────────────────────────────────────────────────────── seg000:0145 nop seg000:0146 seg000:0146 mode_woman: ; CODE XREF: seg000:0111j seg000:0146 push ax seg000:0147 push bp seg000:0148 mov bp, sp seg000:014A mov word ptr [bp+2], 1C1h; seg000:01C1 mes_woman seg000:014F pop bp seg000:0150 push ax seg000:0151 push bp seg000:0152 mov bp, sp seg000:0154 mov word ptr [bp+2], 1A1h; seg000:01A1 mes_wow seg000:0159 pop bp seg000:015A call my_print_func seg000:015D seg000:015D cont: ; CODE XREF: seg000:0143j seg000:015D mov ax, 4C00h seg000:0160 int 21h ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT) seg000:0160 ; AL = exit code seg000:0162 seg000:0162 ;████████████████████████████████████████████████████████████████████████████ seg000:0162 seg000:0162 ; S u b r o u t i n e seg000:0162 seg000:0162 my_print_func proc near ; CODE XREF: seg000:0127p seg000:0162 ; seg000:0140p seg000:0162 ; ... seg000:0162 push bp seg000:0163 mov bp, sp seg000:0165 sub sp, 2 seg000:0168 mov ax, [bp+4] seg000:016B mov [bp-2], ax seg000:016E mov ah, 9 seg000:0170 mov dx, [bp+6] seg000:0173 int 21h ; DOS - PRINT STRING seg000:0173 ; DS:DX -> string terminated by "$" seg000:0175 mov ah, 9 seg000:0177 mov dx, [bp-2] seg000:017A int 21h ; DOS - PRINT STRING seg000:017A ; DS:DX -> string terminated by "$" seg000:017C mov sp, bp seg000:017E pop bp seg000:017F retn 4 seg000:017F my_print_func endp seg000:017F seg000:017F ;──────────────────────────────────────────────────────────────────────────── seg000:0182 prompt db 'Are you Man or Woman [m/w]? : $' seg000:01A1 mes_wow db 0Dh,0Ah seg000:01A1 db 'Wow!',0Dh,0Ah,'$' seg000:01AA mes_man db 0Dh,0Ah seg000:01AA db 'Hello, Strong Man!',0Dh,0Ah,'$' seg000:01C1 mes_woman db 0Dh,0Ah seg000:01C1 db 'Hello, Beautyful Woman!',0Dh,0Ah,'$' seg000:01DD mes_gobl db 0Dh,0Ah seg000:01DD db 'Hello, Strong and Beautyful GOBLIN!',0Dh,0Ah,'$' seg000:01DD seg000:01DD seg000 ends seg000:01DD seg000:01DD seg000:01DD end start |
Макросы в TASM — будьте внимательны!
Хочется обратить Ваше внимание на определённый момент. Макросы в TASM продуманы и подогнаны под совместное использование друг с другом. Вы заметили, что в коде, написанном вручную отсутствует макрос local (local tmp:WORD). Локальная переменная (в нашем случае) будет работать корректно только, если мы используем макрос функции func PASCAL, arg1, arg2… так как локальные переменные тоже используют стек. Не представляя, как реально будет отображён макрос в реальном коде, в целях избежания сложно определяемых ошибок, нужно пользоваться правилами.
- Старайтесь не смешивать использование макросов и чистого кода, по крайней мере при написании одного блока программа, например — одной функции. Выбирайте один подход — либо макрокоманды, либо чистый код.
- Проверяйте код каждой функции через отладчик (обязательно) и дизассемблер (желательно).
- Старайтесь изучить, понять и запомнить реальный код, в который преобразуется макроопределение.
Мы продемонстрировали, как использовать макрос функции для упрощения кода программы, набор макросов и других «приятностей» у TASM большой — советуем просмотреть в нашем архиве (DOS-1.RAR) «Справочник по системе программирования ТУРБО АССЕМБЛЕР 2.0 (под руководством Орлова С.Б.)»: D:\TASM.2_0\DOC\ — между прочем, на русском языке.
Не только макросы могут удивить програмиста странным кодом — результатом. Сам ассемблер, TASM в этом не исключение, преподносит часто сюрпризы, транслируя ваш код, как ему захочется, исходя из соображений скорости исполнения либо экономии объёма используемой памяти. Отсюда вывод: отладчик и дизассемблер должны стать вашими постоянными и лучшими друзьями.