Макросы в TASM — будьте внимательны!
Хочется обратить Ваше внимание на определённый момент. Макросы в TASM продуманы и подогнаны под совместное использование друг с другом
Вы заметили, что в коде, написанном вручную отсутствует макрос local (local tmp:WORD). Локальная переменная (в нашем случае) будет работать корректно только, если мы используем макрос функции func PASCAL, arg1, arg2… так как локальные переменные тоже используют стек. Не представляя, как реально будет отображён макрос в реальном коде, в целях избежания сложно определяемых ошибок, нужно пользоваться правилами.
- Старайтесь не смешивать использование макросов и чистого кода, по крайней мере при написании одного блока программа, например — одной функции. Выбирайте один подход — либо макрокоманды, либо чистый код.
- Проверяйте код каждой функции через отладчик (обязательно) и дизассемблер (желательно).
- Старайтесь изучить, понять и запомнить реальный код, в который преобразуется макроопределение.
Мы продемонстрировали, как использовать макрос функции для упрощения кода программы, набор макросов и других «приятностей» у TASM большой — советуем просмотреть в нашем архиве (DOS-1.RAR) «Справочник по системе программирования ТУРБО АССЕМБЛЕР 2.0 (под руководством Орлова С.Б.)»: D:\TASM.2_0\DOC\ — между прочем, на русском языке.
Не только макросы могут удивить програмиста странным кодом — результатом. Сам ассемблер, TASM в этом не исключение, преподносит часто сюрпризы, транслируя ваш код, как ему захочется, исходя из соображений скорости исполнения либо экономии объёма используемой памяти. Отсюда вывод: отладчик и дизассемблер должны стать вашими постоянными и лучшими друзьями.
Ассемблер — программирование или искусство?
Скажем так, все зависит от того, в чьих руках он находится. Ассемблер — это первичный элемент мира процессора, из сочетаний этих элементов складывается его душа, его самосознание. Подобно тому, как вся музыка, написанная в истории человечества, состоит из сочетаний семи нот, так и сочетание ассемблерных команд наполняет компьютерный мир цифровой жизнью. Кто знает лишь три аккорда — это «попса», кому же известна вся палитра — это классика.
Почему же наука так жаждет проникнуть в квантовые глубины и захватить в свои руки неуловимый первичный кирпичик материи? Чтобы получить над ней власть, изменять ее по своей воле, стать на уровень Творца Вселенной. В чьи руки попадет такая власть — это еще вопрос. В отличие от науки, в мире программирования тайн нет, нам известны кирпичики, его составляющие, а следовательно, и та власть над процессором, которую нам дает знание ассемблера.
Чтобы программирование на языке ассемблера поднялось на уровень искусства, нужно постичь его красоту, скрывающуюся за потоком единиц и нулей. Как и в любой отрасли человеческой деятельности, в программировании можно быть посредственностью, а можно стать Мастером. И то и другое отличает степень культуры, образования, труда и, главное, то, сколько души автор вкладывает в свое творение.
Ассемблер и терминатор
Не так давно Джеймс Кэмерон выпустил в свет 3D-версию второго «Терминатора», и в качестве интересного исторического факта можно отметить один любопытный момент из жизни киборга-убийцы…
Кадр из фильма «Терминатор»
Здесь мы видим «зрение» терминатора, а слева на нем отображается ассемблерный листинг. Судя по нему, знаменитый Уничтожитель работал на процессоре MOS Technology 6502 либо на MOS Technology 6510. Этот процессор впервые был разработан в 1975 году, использовался на компьютерах Apple и, помимо всего прочего, на знаменитых игровых приставках того времени Atari 2600 и Nintendo Entertainment System (у нас более известной как Dendy). Имел лишь три 8-разрядных регистра: А-аккумулятор и два индексных регистра X и Y. Такое малое их количество компенсировалось тем, что первые 256 байт оперативной памяти (так называемая нулевая страница) могли адресоваться специальным образом и фактически использовались в качестве 8-разрядных или 16-разрядных регистров. У данного процессора было 13 режимов адресации на всего 53 команды. У терминатора идет цепочка инструкций LDA-STA-LDA-STA… В семействе 6502 программы состояли чуть менее чем полностью из LDA/LDY/LDX/STA/STX/STY:
Чтение и запись в порты ввода-вывода также выполнялись этими командами, и программа терминатора имеет вполне осмысленный вид, а не представляет собой бестолковую фантазию сценариста: MOS Technology 6502 / Система команд.
Выражения
Компилятор позволяет
использовать в программе выражения
которые могут состоять ,
и . Все выражения
являются 32-битными.
Операнды
Могут быть использованы
следующие операнды:
- Метки определённые
пользователем (дают значение
своего положения). - Переменные определённые
директивой - Константы определённые
директивой - Числа заданные в формате:
- Десятичном (принят по
умолчанию): 10, 255 - Шестнадцатеричном (два
варианта записи): 0x0a, $0a, 0xff,
$ff - Двоичном: 0b00001010, 0b11111111
- Восьмеричном (начинаются с
нуля): 010, 077
- Десятичном (принят по
- PC — текущее значение
программного счётчика (Programm
Counter)
Операторы
Компилятор поддерживает ряд
операторов которые перечислены в
таблице (чем выше положение в
таблице, тем выше приоритет
оператора). Выражения могут
заключаться в круглые скобки, такие
выражения вычисляются перед
выражениями за скобками.
Приоритет | Символ | Описание |
14 | ||
14 | ||
14 | ||
13 | ||
13 | ||
12 | ||
12 | ||
11 | ||
11 | ||
10 | ||
10 | ||
10 | ||
10 | ||
9 | ||
9 | ||
8 | ||
7 | ||
6 | ||
5 | ||
4 |
Логическое
отрицание
Символ: ! Описание:
Возвращает 1 если выражение равно 0,
и наоборот Приоритет: 14 Пример: ldi r16, !0xf0 ; В
r16 загрузить 0x00
Побитное отрицание
Символ: ~ Описание:
Возвращает выражение в котором все
биты проинвертированы Приоритет: 14 Пример: ldi r16, ~0xf0 ; В
r16 загрузить 0x0f
Минус
Символ: - Описание:
Возвращает арифметическое
отрицание выражения Приоритет: 14 Пример: ldi r16,-2 ;
Загрузить -2(0xfe) в r16
Деление
Символ: / Описание:
Возвращает целую часть результата
деления левого выражения на правое
Приоритет: 13 Пример: ldi r30, label/2
Вычитание
Символ: - Описание:
Возвращает результат вычитания
правого выражения из левого Приоритет: 12 Пример: ldi r17, c1-c2
Сдвиг влево
Символ: << Описание:
Возвращает левое выражение
сдвинутое влево на число бит
указанное справа Приоритет: 11 Пример: ldi r17,
1<<bitmask ; В r17 загрузить 1
сдвинутую влево bitmask раз
Сдвиг вправо
Символ: >> Описание:
Возвращает левое выражение
сдвинутое вправо на число бит
указанное справа Приоритет: 11 Пример: ldi r17,
c1>>c2 ; В r17 загрузить c1 сдвинутое
вправо c2 раз
Меньше чем
Символ: < Описание:
Возвращает 1 если левое выражение
меньше чем правое (учитывается
знак), и 0 в противном случае Приоритет: 10 Пример: ori r18,
bitmask*(c1<c2)+1
Меньше или равно
Символ: <= Описание:
Возвращает 1 если левое выражение
меньше или равно чем правое
(учитывается знак), и 0 в противном
случаеПриоритет: 10 Пример: ori r18,
bitmask*(c1<=c2)+1
Больше чем
Символ: > Описание:
Возвращает 1 если левое выражение
больше чем правое (учитывается
знак), и 0 в противном случаеПриоритет: 10 Пример: ori r18,
bitmask*(c1>c2)+1
Больше или равно
Символ: >= Описание:
Возвращает 1 если левое выражение
больше или равно чем правое
(учитывается знак), и 0 в противном
случаеПриоритет: 10 Пример: ori r18,
bitmask*(c1>=c2)+1
Равно
Символ: == Описание:
Возвращает 1 если левое выражение
равно правому (учитывается знак), и 0
в противном случаеПриоритет: 9 Пример: andi r19,
bitmask*(c1==c2)+1
Не равно
Символ: != Описание:
Возвращает 1 если левое выражение
не равно правому (учитывается знак),
и 0 в противном случаеПриоритет: 9 Пример: .SET flag =
(c1!=c2) ;Установить flag равным 1 или 0
Побитное
исключающее ИЛИ
Символ: ^ Описание:
Возвращает результат побитового
исключающего ИЛИ выражений Приоритет: 7 Пример: ldi r18, Low(c1^c2)
Логическое И
Символ: && Описание:
Возвращает 1 если оба выражения не
равны нулю, и 0 в противном случае
Приоритет: 5 Пример: ldi r18,
Low(c1&&c2)
Логическое ИЛИ
Символ: || Описание:
Возвращает 1 если хотя бы одно
выражение не равно нулю, и 0 в
противном случаеПриоритет: 4 Пример: ldi r18, Low(c1||c2)
- LOW(выражение) возвращает
младший байт выражения - HIGH(выражение) возвращает
второй байт выражения - BYTE2(выражение) то же что и
функция HIGH - BYTE3(выражение) возвращает
третий байт выражения - BYTE4(выражение) возвращает
четвёртый байт выражения - LWRD(выражение) возвращает биты
0-15 выражения - HWRD(выражение) возвращает биты
16-31 выражения - PAGE(выражение) возвращает биты
16-21 выражения - EXP2(выражение) возвращает 2 в
степени (выражение) - LOG2(выражение) возвращает целую
часть log2(выражение)
Использование программы
Этот раздел описывает
использование компилятора и
встроенного редактора
Открытие
файлов
В WAVRASM могут быть открыты как
новые так и существующие файлы.
Количество открытых файлов
ограничено размером памяти, однако
объём одного файла не может
превышать 28 килобайт (в связи с
ограничением MS-Windows). Компиляция
файлов большего размера возможна,
но они не могут быть редактируемы
встроенным редактором. Каждый файл
открывается в отдельном окне.
Сообщения об
ошибках
После компиляции программы
появляется окно сообщений. Все
обнаруженные компилятором ошибки
будут перечислены в этом окне. При
выборе строки с сообщением о
ошибке, строка исходного файла, в
которой найдена ошибка, становится
красной. Если же ошибка находится
во вложенном файле, то этого
подсвечивания не произойдёт.
Если по строке в окне сообщений
клацнуть дважды, то окно файла с
указанной ошибкой становится
активным, и курсор помещается в
начале строки содержащей ошибку.
Если же файл с ошибкой не открыт
(например это вложенный файл) то он
будет автоматически открыт.
Учтите, что если Вы внесли
изменения в исходные тексты
(добавили или удалили строки), то
информация о номерах строк в окне
сообщений не является корректной.
Опции
Некоторые установки программы
могут быть изменены через пункт
меню Options. Если выбрать этот пункт
то появится вот такое диалоговое
окно:
В поле ввода озаглавленном
«List-file extension» вводится
расширение используемое для файла
листинга, а в поле «Output-file
extension» находится расширение для
файлов с результатом компиляции
программы. В прямоугольнике «Output
file format» можно выбрать формат
выходного файла (как правило
используется интеловский). Однако
это не влияет на объектный файл
(используемый AVR Studio), который
всегда имеет один и тот же формат, и
расширение OBJ. Если в исходном файле
присутствует сегмент EEPROM то будет
также создан файл с расширением EEP.
Установки заданные в данном окне
запоминаются на постоянно, и при
следующем запуске программы, их нет
необходимости переустанавливать.
Опция «Wrap relative jumps» даёт
возможность «заворачивать»
адреса. Эта опция может быть
использована только на чипах с
объёмом программной памяти 4К слов
(8К байт), при этом становится
возможным делать относительные
переходы (rjmp) и вызовы подпрограмм
(rcall) по всей памяти.
Опция «Save before assemble» указывает
программе на необходимость
автоматического сохранения
активного окна (и только его) перед
компиляцией.
Если Вы хотите чтобы при закрытии
программы, закрывались все
открытые окна, то поставьте галочку
в поле «Close all windows before exit».
Atmel, AVR являются
зарегистрированными товарными
знаками фирмы Atmel Corporation
Перевод выполнил Руслан
Шимкевич, [email protected]
Техника программирования
В состав полной версии системы программирования на макроассемблере (дистрибутивного пакета) входит набор макроопределений и подпрограмм различного назначения и примеры их использования. Внимательно просмотрите все подкаталоги вашей версии макроассемблера, наверняка в них найдутся полезные и интересные решения, которые можно применять в вашей работе.
Не забывайте о том, что при составлении программ можно использовать модули библиотек, входящих в состав систем программирования на языках высокого уровня, например, на Си. Правда, для этого надо иметь описание этих библиотек, но без него невозможно их использование и в рамках той системы программирования, для которой они созданы.
Если у вас есть доступ к сети Internet, то на разных ее сайтах можно найти множество готовых решений буквально на все случаи жизни
Только обращайте внимание на даты разработки программ и подпрограмм. Многие из них морально устарели и при использовании новых инструкций микропроцессоров могут быть существенно упрощены.
В процессе работы с ассемблером у вас постепенно будет накапливаться набор собственных решений, которые пригодятся в новых разработках
Для удобства их последующего использования старайтесь при написании программ составлять как можно больше подпрограмм, оформленных в виде отдельных модулей, лучше исходных, а не объектных ― первые проще модифицировать в случае необходимости. Применение подпрограмм несколько увеличивает размеры задачи и замедляет процесс ее выполнения. Но взамен вы получаете возможность отлаживать большую программу по частям и использовать отлаженные подпрограммы в других своих разработках.
Не забывайте о существовании такого эффективного средства, как макросы ― макроопределения и макровызовы. Они позволяют сокращать не только исходный текст программы, но и количество возможных ошибок при его наборе. Кроме того, макровызовы удобно применять при запросе системных функций, которые выполняют много полезных действий таких, как работа с окнами, с файловой системой, ввод и вывод данных и пр.
3.3.9. Условное ассемблирование
#if#ifdef#ifndef
if выражение … endif |
if выражение … else … endif |
if выражение1 … elseif выражение2 … elseif выражение3 … else … endif |
- IF1/ELSEIF1 — если ассемблер выполняет первый проход ассемблирования;
- IF2/ELSEIF2 — если ассемблер выполняет второй проход ассемблирования (часто не работает на современных ассемблерах);
- IFE выражение/ELSEIFE выражение — если выражение равно нулю (ложно);
- IFDEF метка/ELSEIFDEF метка — если метка определена;
- IFNDEF метка/ELSEIFNDEF метка — если метка не определена;
- IFB <аргумент>/ELSEIFB <аргумент> — если значение аргумента — пробел (эти и все следующие директивы используются в макроопределениях для проверки параметров);
- IFNB <аргумент>/ELSEIFNB <аргумент> — если значение аргумента — не пробел (используется в макроопределениях для проверки переданных параметров);
- IFDIF <аргумент1>,<аргумент2>/ELSEIFDIF <аргумент1>,<аргумент2> — если аргументы отличаются (с различием больших и маленьких букв);
- IFDIFI <аргумент1>,<аргумент2>/ELSEIFDIFI <аргумент1>,<аргумент2> — если аргументы отличаются (без различия больших и маленьких букв);
- IFIDN <аргумент1>,<аргумент2>/ELSEIFIDN <аргумент1>,<аргумент2> — если аргументы одинаковы (с различием больших и маленьких букв);
- IFIDNI <аргумент1>,<аргумент2>/ELSEIFIDNI <аргумент1>,<аргумент2> — если аргументы одинаковы (без различия больших и маленьких букв).
if $ gt 65535 ; Если адрес вышел за пределы сегмента. .err endif |
- .ERR1 — ошибка при первом проходе ассемблирования;
- .ERR2 — ошибка при втором проходе ассемблирования;
- .ERRE выражение — ошибка, если выражение равно нулю (ложно);
- .ERRNZ выражение — ошибка, если выражение не равно нулю (истинно);
- .ERRDEF метка — ошибка, если метка определена;
- .ERRNDEF метка — ошибка, если метка не определена;
- .ERRB <аргумент> — ошибка, если аргумент пуст (эта и все следующие директивы используются в макроопределениях для проверки параметров);
- .ERRNB <аргумент> — ошибка, если аргумент не пуст;
- .ERRDIF <аргумент1>,<аргумент2> — ошибка, если аргументы различны;
- .ERRDIFI <аргумент1>,<аргумент2> — ошибка, если аргументы отличаются (сравнение не различает большие и маленькие буквы);
- .ERRIDN <аргумент1>,<аргумент2> — ошибка, если аргументы совпадают;
- .ERRIDNI <аргумент1>,<аргумент2> — ошибка, если аргументы совпадают (сравнение не различает большие и маленькие буквы).
I.3. Особенности терминологии
MASM
MacroConstant EQU 123 ;; Числовая макроконстанта MacroVar = 123 ;; Числовая макропеременная MacroText EQU <string> ;; строковая макропеременная MacroText TEXTEQU <string> ;; строковая макропеременная |
MacroConstant EQU 123 ;; numeric equates MacroVar = 123 ;; numeric equates MacroText EQU <string> ;; text macro MacroText TEXTEQU <string> ;; text macro |
Что имеет ввиду автор? Посмотрите что такое макроопределение — это некий текст, который как бы «вставляется» препроцессором в исходный текст программы в месте вызова макро. А что такое в терминологии MASM numeric equates, или text macro — это некоторые переменные, значения которых «подставляются» в исходный текст программы во время компиляции вместо имён этих переменных. Таким образом, можно сказать, что определения представленные выше — макро, но в упрощённом их виде. |
MacroConstant EQU 123 ;; Числовая макроконстанта MacroVar = 123 ;; Числовая макропеременная |
Макросы для работы со стеком
Код (ASM):
- ; Сохранение аргументов в стеке
- PushArg macro arg ; заголовок макроопределения
- local par ; описание локальной переменной par
- ; цикл формирования последовательности команд push arg
- irp par,; par = arg — имя очередного аргумента
- push par ; push arg — заготовка формируемой команды push
- endm ; конец цикла (действия директивы irp)
- endm ; конец макроопределения
- ; Выборка аргументов из стека
- PopArg macro arg ; заголовок макроопределения
- local par ; описание локальной переменной par
- ; цикл формирования последовательности команд pop arg
- irp par,; par = arg — имя очередного аргумента
- pop par ; pop arg — заготовка формируемой команды pop
- endm ; конец цикла (действия директивы irp)
- endm ; конец макроопределения
I.4. Благодарности
MASMFour-FCyberManiacCyberManiacFatMoonrustamThe SvinEdmond/HI-TECH
II. Лень – двигатель Макро
Если есть что-то похожее, что нужно делать очень часто, я могу оформить это как макроопределение. |
ООПMASM32 ООП MASM32
m2m MACRO M1, M2 push M2 pop M1 ENDM return MACRO arg mov eax, arg ret ENDM |
Код (ASM):
- push переменная2
- pop переменная1
Код (ASM):
- mov wc.cbWndExtra, NULL
- m2m wc.hInstance, hInst
- mov wc.hbrBackground, COLOR_BTNFACE+1
- . . . .
- mov wc.cbWndExtra, NULL
- push hInst
- pop wc.hInstance,
- mov wc.hbrBackground, COLOR_BTNFACE+1
push/popm2mm2m
Мощь макро была бы сказочной, если бы MASM умел следить за кодом, или ему можно было бы указать, что, например, сейчас регистр ebx == 0, или eax никем не используется. Хотя мы попробуем достичь подобного эффекта самостоятельно. |
push/popmov eax,…m2m
- Эффективность кода
- Совершенство стилистики
m2m
Это одна из вечных задач архитектора – найти баланс между эффективностью в коде и совершенством стилистики. |
Если, объединяя что-то в одно целое, я улучшаю стиль кода – это можно сделать в виде макроопределения. |
Код (ASM):
- $$$WIN32START macro
- PUBLIC l$_ExitProgram
- _start
- xor ebx,ebx
- endm
- $$$WIN32END macro
- l$_ExitProgram
- push $$$__null
- call ExitProcess
- end _start
- endm
main
Если ты используешь технологию программирования – попытайся заключить её в комплекс макроопределений. Например, для модульного программирования нужно создать макросы для определения модуля, его частей, кода и данных. |
PROGRAM_IMAGE_BASE EQU 400000h |
Relock GetModuleHandlePROGRAM_IMAGE_BASE PROGRAM_IMAGE_BASE
III. Макромир MASM
Пример: Создайте небольшой модуль с именем macro.asm. И напишите в нём несколько строчек
Так действует директива echo. С помощью неё можно подсмотреть значения переменных.
Взгляните на код программы под отладчиком. Что у вас получилось? Что будет, если вы измените текст внутри макроопределения?
Каким будет вывод на экран во время компиляции? |
ML
MacroName macro paramlist макроопределение endm |
… MacroName |
mov eax, MacroName() … |
MyMacro macro echo Это макро 1 endm MyMacro macro echo Это макро 2 endm MyMacro |
PURGE
PURGE macroname |
macroname
Выражения
Компилятор позволяет
использовать в программе выражения
которые могут состоять ,
и . Все выражения
являются 32-битными.
Операнды
Могут быть использованы
следующие операнды:
- Метки определённые
пользователем (дают значение
своего положения). - Переменные определённые
директивой - Константы определённые
директивой - Числа заданные в формате:
- Десятичном (принят по
умолчанию): 10, 255 - Шестнадцатеричном (два
варианта записи): 0x0a, $0a, 0xff,
$ff - Двоичном: 0b00001010, 0b11111111
- Восьмеричном (начинаются с
нуля): 010, 077
- Десятичном (принят по
- PC — текущее значение
программного счётчика (Programm
Counter)
Операции
Компилятор поддерживает ряд
операций, которые перечислены в
таблице (чем выше положение в
таблице, тем выше приоритет
операции). Выражения могут
заключаться в круглые скобки, такие
выражения вычисляются перед
выражениями за скобками.
Приоритет | Символ | Описание |
14 | ||
14 | ||
14 | ||
13 | ||
13 | ||
12 | ||
12 | ||
11 | ||
11 | ||
10 | ||
10 | ||
10 | ||
10 | ||
9 | ||
9 | ||
8 | ||
7 | ||
6 | ||
5 | ||
4 |
Логическое
отрицание
Символ: ! Описание:
Возвращает 1 если выражение равно 0,
и наоборот Приоритет: 14 Пример: ldi r16, !0xf0═ ; В
r16 загрузить 0x00
Побитное отрицание
Символ: ~ Описание:
Возвращает выражение в котором все
биты проинвертированы Приоритет: 14 Пример: ldi r16, ~0xf0═ ; В
r16 загрузить 0x0f
Минус
Символ: - Описание:
Возвращает арифметическое
отрицание выражения Приоритет: 14 Пример: ldi r16,-2═ ;
Загрузить -2(0xfe) в r16
Деление
Символ: / Описание:
Возвращает целую часть результата
деления левого выражения на правое
Приоритет: 13 Пример: ldi r30, label/2
Вычитание
Символ: - Описание:
Возвращает результат вычитания
правого выражения из левого Приоритет: 12 Пример: ldi r17, c1-c2
Сдвиг влево
Символ: << Описание:
Возвращает левое выражение
сдвинутое влево на число бит
указанное справа Приоритет: 11 Пример: ldi r17,
1<<bitmask═ ; В r17 загрузить 1
сдвинутую влево bitmask раз
Сдвиг вправо
Символ: >> Описание:
Возвращает левое выражение
сдвинутое вправо на число бит
указанное справа Приоритет: 11 Пример: ldi r17,
c1>>c2═ ; В r17 загрузить c1 сдвинутое
вправо c2 раз
Меньше чем
Символ: < Описание:
Возвращает 1 если левое выражение
меньше чем правое (учитывается
знак), и 0 в противном случае Приоритет: 10 Пример: ori r18,
bitmask*(c1<c2)+1
Меньше или равно
Символ: <= Описание:
Возвращает 1 если левое выражение
меньше или равно чем правое
(учитывается знак), и 0 в противном
случаеПриоритет: 10 Пример: ori r18,
bitmask*(c1<=c2)+1
Больше чем
Символ: > Описание:
Возвращает 1 если левое выражение
больше чем правое (учитывается
знак), и 0 в противном случаеПриоритет: 10 Пример: ori r18,
bitmask*(c1>c2)+1
Больше или равно
Символ: >= Описание:
Возвращает 1 если левое выражение
больше или равно чем правое
(учитывается знак), и 0 в противном
случаеПриоритет: 10 Пример: ori r18,
bitmask*(c1>=c2)+1
Равно
Символ: == Описание:
Возвращает 1 если левое выражение
равно правому (учитывается знак), и 0
в противном случаеПриоритет: 9 Пример: andi r19,
bitmask*(c1==c2)+1
Не равно
Символ: != Описание:
Возвращает 1 если левое выражение
не равно правому (учитывается знак),
и 0 в противном случаеПриоритет: 9 Пример: .SET flag =
(c1!=c2)═ ;Установить flag равным 1 или 0
Побитное
исключающее ИЛИ
Символ: ^ Описание:
Возвращает результат побитового
исключающего ИЛИ выражений Приоритет: 7 Пример: ldi r18, Low(c1^c2)
Логическое И
Символ: && Описание:
Возвращает 1 если оба выражения не
равны нулю, и 0 в противном случае
Приоритет: 5 Пример: ldi r18,
Low(c1&&c2)
Логическое ИЛИ
Символ: || Описание:
Возвращает 1 если хотя бы одно
выражение не равно нулю, и 0 в
противном случаеПриоритет: 4 Пример: ldi r18, Low(c1||c2)
- LOW(выражение) возвращает
младший байт выражения - HIGH(выражение) возвращает
второй байт выражения - BYTE2(выражение) то же что и
функция HIGH - BYTE3(выражение) возвращает
третий байт выражения - BYTE4(выражение) возвращает
четвёртый байт выражения - LWRD(выражение) возвращает биты
0-15 выражения - HWRD(выражение) возвращает биты
16-31 выражения - PAGE(выражение) возвращает биты
16-21 выражения - EXP2(выражение) возвращает 2 в
степени (выражение) - LOG2(выражение) возвращает целую
часть log2(выражение)
Почему следует изучать язык ассемблера?
В современной практике индустриального программирования языки ассемблера применяются крайне редко. Для разработки низкоуровневых программ практически в большинстве случаев используется язык си, позволяющий достигать тех же целей многократно меньшими затратами труда, причем с такой же, а иногда и большей эффективностью получаемого исполняемого кода (последнее достигается за счет применения оптимизаторов). На ассемблере сейчас реализуются очень специфические участки ядер операционных систем и системных библиотек. Более того, программирование на ассемблере было вытеснено и из такой традиционно ассемблерной области, как программирование микроконтроллеров. Большей частью прошивки для них также пишут на си. Тем не менее программирование на языке ассемблера очень часто применяется при написании программ, использующих возможности процессора, не реализуемые языками высокого уровня, а также при программировании всевозможных нестандартных программистских хитростей. Отдельные ассемблерные модули, как и ассемблерные вставки в текст на других языках, присутствуют и в ядрах операционных систем, и в системных библиотеках того же языка си и других языков высокого уровня. Сегодня едва ли кому придет в голову сумасшедшая мысль писать крупную программу на чистом ассемблере.
Так зачем же тратить время на его изучение? По ряду веских причин, и вот одна из них: ассемблер — это краеугольный камень, на котором покоится все бесконечное пространство программирования, начиная от рождения первого процессора. Каждый физик мечтает разгадать тайну строения вселенной, найти эти загадочные первичные неделимые (низкоуровневые) элементы, из которых она состоит, не удовлетворяясь лишь смутным о том представлением квантовой теории. Ассемблер же и есть та первичная материя, из которой состоит вселенная процессора. Он — тот инструмент, который дает человеку способность мыслить в терминах машинных команд. А подобное умение просто необходимо любому профессиональному программисту, даже если никогда в жизни он не напишет ни единой ассемблерной строчки. Нельзя отрицать того, что невозможно стать математиком, совершенно не имея понятия об элементарной арифметике. На каком бы языке вы ни писали программы, необходимо хотя бы в общих чертах понимать, что конкретно будет делать процессор, исполняя ваше высочайшее повеление. Если такого понимания нет, программист начинает бездумно применять все доступные операции, совершенно не ведая, что на самом деле он творит.
Вообще, профессиональный пользователь компьютера, системный ли администратор, или программист, может позволить себе что-то не знать, но ни в коем случае не может позволить не понимать сути происходящего, как устроена вычислительная система на всех ее уровнях, от электронных логических схем до громоздких прикладных программ. А непонимание чего-то влечет за собой ощущение в глубине подсознания некоей загадочности, непостижимого таинства, происходящего по мановению чьей-то волшебной палочки. Такое ощущение для профессионала недопустимо категорически. Он просто обязан быть уверен вплоть до глубинных слоев подсознания, что то устройство, с которым он имеет дело, ничего волшебного и непознаваемого собой не представляет.
Иными словами, до тех пор пока существуют процессоры, ассемблер будет необходим.
В этом отношении совершенно не важно, какую конкретно архитектуру и язык какого конкретного ассемблера изучать. Зная один язык ассемблера, ты с успехом можешь начать писать на любом другом, потратив лишь некоторое время на изучение справочной информации
Но самое главное в том, что, умея мыслить языком процессора, ты всегда будешь знать, что, для чего, почему и зачем происходит. А это уже не просто уровень программирования мышкой, а путь к созданию программного обеспечения, несущего печать великого мастерства.
Пример чистой реализации конвенции PASCAL.
Исходный код GBLPROC1.ASM
;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
pMessage2 equ
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
1 |
;goblproc1.asm .modeltiny; for СОМ .code; code segment start org100h; offset in memory = 100h (for COM) start mainproc begin movah,09h movdx,offsetprompt int21h inpt movah,01h int21h cmpal,’m’ jemode_man cmpal,’w’ jemode_woman pushoffsetmes_gobl pushoffsetmes_wow callmy_prnt_func ;call my_prnt_func PASCAL,offset mes_gobl, offset mes_wow jmpbegin mode_man pushoffsetmes_man pushoffsetmes_wow callmy_prnt_func ;call my_prnt_func PASCAL, offset mes_man, offset mes_wow; addr mes_man,addr mes_man jmpcont mode_woman pushoffsetmes_womn pushoffsetmes_wow callmy_prnt_func ;call my_prnt_func PASCAL, offset mes_womn, offset mes_wow cont ;call my_prnt_func movax,4c00h int21h mainendp my_prnt_funcprocnear pushbp movbp,sp pMessage1equbp+6 pMessage2equbp+4 movdx,pMessage2 movah,09h int21h movah,09h movdx,pMessage1 int21h popbp ret4; Чистим стек my_prnt_funcendp promptdb’Are you Man or Woman [m/w]? : $’ mes_wowdb0Dh,0Ah,»Wow!»,0Dh,0Ah,24h; строка для вывода. 24h = ‘$’ . mes_mandb0Dh,0Ah,»Hello, Strong Man!»,0Dh,0Ah,’$’; строка для вывода. Вместо ASCII смвола ‘$’ можно написать машинный код 24h mes_womndb0Dh,0Ah,»Hello, Beautyful Woman!»,0Dh,0Ah,’$’; строка для вывода mes_gobldb0Dh,0Ah,»Hello, Strong and Beautyful GOBLIN!»,0Dh,0Ah,24h; строка для вывода. 24h = ‘$’ . len=$-mes_gobl;len equ $ — mes_gobl endstart |
Можете прогнать наш GBLPROC1.COM через IDA и посмотреть, как сработал компилятор.
Макрос — макрокоманда, макроопределение.
У большинства популярных ассемблеров (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)
;Ассемблерный код:
push a; Первый параметр (самый левый) — сверху
push b; Второй параметр
push c;
push d;
push e; Пятый параметр — снизу
call myFunc
…
;———————————————————————-
;Функция содержит пять параметров:
myFunc:
push bp
mov bp,sp; Создаём стековый кадр. В bp — указатель на стековый кадр, регистр bp использовать нельзя!
a equ ; Первый параметр — сверху ()
b equ
c equ
d equ
e equ
…
(команды, которые могут использовать стек):
mov ax,e ; считать параметр 5 — . Можно и так, но это менее понятно: mov ax,
; Его адрес в сегменте стека ВР + 4, потому что при выполнении
; команды CALL при вызове функции, в стек поместили адрес возврата — 2 байта для процедуры
; типа NEAR (или 4 — для FAR), а потом еще и ВР — 2 байта (push bp — в начале нашей функции)
mov bx,с ; считать параметр 3 — . Можно и так, но это менее понятно: mov bx,
(ещё команды)
…
pop bp
ret 10 ;Из стека дополнительно извлекается 10 байт — стек освобождает вызываемая функция
1 |
;Ассемблерный код: pusha; Первый параметр (самый левый) — сверху pushb; Второй параметр pushc; pushd; pushe; Пятый параметр — снизу callmyFunc … ;———————————————————————- myFunc pushbp movbp,sp; Создаём стековый кадр. В bp — указатель на стековый кадр, регистр bp использовать нельзя! aequbp+12; Первый параметр — сверху () bequbp+10 cequbp+8 dequbp+6 eequbp+4 … (команды,которыемогутиспользоватьстек) movax,e; считать параметр 5 — . Можно и так, но это менее понятно: mov ax, ; Его адрес в сегменте стека ВР + 4, потому что при выполнении movbx,с; считать параметр 3 — . Можно и так, но это менее понятно: mov bx, (ещёкоманды) … popbp ret10;Из стека дополнительно извлекается 10 байт — стек освобождает вызываемая функция |
Ниже — исходный код несколько модернизированной программы goblin.asm. Конвенция PASCAL реализована по всем правилам, «ручками» и без всяких макросов.