Asembler — Составить программу (на асемблере), которая…


Содержание

Разработка программы на Ассемблере (стр. 1 из 11)

Кыргызский Государственный Национальный Университет

Институт Интеграции Международных Образовательных программ

Кыргызско-Американский Факультет Компьютерных Информационных Систем и ИНТЕРНЕТ (КАФ-ИНТЕРНЕТ)

Разработка программы на Ассемблере.

Выполнили: студенты группы КИС 2 – 98

Вершинин АА Исманов АА

Проверил: преподаватель Кочетов ОП

АССЕМБЛЕР. ЭТАПЫ РАЗРАБОТКИ ПРОГРАММЫ………………. 4

ФОРМАТ КОМАНД И ИХ КЛАССИФИКАЦИЯ ………………………….5

НЕКОТОРЫЕ ОПЕРАТОРЫ, ПРЕДОПРЕДЕЛЁННЫЕ ИМЕНА, ДИРЕКТИВЫ И КОМАНДЫ АССЕМБЛЕРА 80X86(8088) ……………..7

КОМАНДЫ ПЕРЕСЫЛКИ …………………………………………………11

АРИФМЕТИЧЕСКИЕ КОМАНДЫ ………………………………………..12

ЛОГИЧЕСКИЕ КОМАНДЫ И КОМАНДЫ СДВИГА …………………. 14

КОМАНДЫ УПРАВЛЕНИЯ МИКРОПРОЦЕССОРОМ …………………18

ПРИМЕЧАНИЯ………………………………………………………………20

1.1. Технико-математическое описание задачи……….………………….21

1.2. Требования к функциональным характеристикам…………………..23

1.3. Требования к техническим и программным средствам………….… 25

1.3.1. Обоснования выбора языка программирования……………………. 26

2.2. Описание структуры программы……………………………….…..… 30

2.3. Описание алгоритма решения задачи………………………….……….32

2.4. Отладка и тестирование……………………………………….………. 34

2.5. Инструкция к пользователю………………………………….……….. 35

2.6. Заключение о результатах проектируемой задачи………….……….. 36

ПРИЛОЖЕНИЕ 1 (Системы счисления)………………………………….. 37

ПРИЛОЖЕНИЕ 2 (Структурная схема микропроцессора)..…………….. 44

ПРИЛОЖЕНИЕ 3 (Основные положения алгебры логики)…….……….. 59

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

В связи с этим также стремительно развивался и рынок программных тестирующих средств. На рынке существует огромное количество отличных диагностических программ, написанных большими корпорациями: такими как Symantec inc., APS (Advanced Personal Systems), Microsoft и т.д., но все существующие диагностирующие программы написаны на языках высокого уровня, а значит не достаточно быстры и надёжны.

Автор проекта не берётся конкурировать с огромными гигантами по количеству выполняемых этими программами тестов в силу того, что это бессмысленно. Была предпринята попытка написать более надежную, быструю диагностическую программу с использованием машинно-ориентированного языка программирования – Ассемблер.

АССЕМБЛЕР. ЭТАПЫ РАЗРАБОТКИ ПРОГРАММЫ.

Язык программирования наиболее полно учитывающий особенности «родного» микропроцессора и содержащий мнемонические обозначения машинных команд называется Ассемблером. Программа, написанная на Ассемблере называется исходной программой. Далее остановимся на версии, называемой Турбо Ассемблер.

Разработка программы на Ассемблере состоит из следующих этапов:

  • 1) Составление алгоритма в виде блок-схемы или структурного описания,
  • 2) Ввод в ЭВМ текста исходной программы PROG.ASM с помощью редактора текстов. Имя PROG может быть произвольным, а расширение ASM — обязательно,
  • 3) Перевод (трансляция или ассемблирование) исходной программы в машинные коды с помощью транслятора TASM.EXE. На этом этапе получается промежуточный продукт PROG.OBJ (объектный код). Выявленные при этом синтаксические и орфографические ошибки исправляются повтором пп.2 и 3,
  • 4) Преобразование с помощью программы TLINK.EXE объектного кода PROG.OBJ в выполнимый код PROG.EXE или PROG.COM.
  • 5) Выполнение программы и ее отладка начиная с п.1, если встретились логические ошибки.

Текст программы на Ассемблере содержит следующие операции:

  • а) команды или инструкции,
  • б) директивы или псевдооператоры,
  • в) операторы,
  • г) предопределенные имена.

Действия обусловленные операциями перечисленными в пп.б,в,г выполняются на этапе трансляции, т.е. являются командами Ассемблеру. Операции, называемые командами или инструкциями выполняются во время выполнения программы, т.е. являются командами микропроцессору.

ФОРМАТ КОМАНД И ИХ КЛАССИФИКАЦИЯ

Инструкция записывается на отдельной строке и включает до четырех полей, необязательные из которых выделены [ ]:

[метка:] мнемоника_команды [операнд(ы)] [;комментарий]

Метка или символический адрес содержит до 31 символа из букв цифр и знаков ? @ . _ $. Причем цифра не должна стоять первой, а точка, если есть должна быть первой.

Мнемоника — сокращенное обозначение кода операции (КОП) команды, например мнемоника ADD обозначает сложение (addition).

Операндами могут быть явно или неявно задаваемые двоичные наборы, над которыми производятся операции.Операнды приводятся в одной из четырех систем счисления и должны оканчиваться символом b(B), o(O), d(D), h(H) для 2, 8, 10 или 16-ной СС. К шестнадцатиричному числу добавляется слева ноль, если оно начинается с буквы.

Система команд может быть классифицирована по трем основным признакам —

  • длина команды или число занимаемых ею байтов,
  • функциональное назначение и
  • способ адресации.

Для МП 1810ВМ86 (8086) команда занимает от одного до шести байтов. Первым байтом команды всегда является код операции, например код команды INT XXh равен CD(HEX).

По функциональному признаку инструкции можно разбить на пять больших групп:

  • 1) команды пересылки данных,
  • 2) арифметические команды,
  • 3) логические команды,
  • 4) команды переходов и
  • 5) команды управления.

Существует пять основных способов адресации:

  • регистровая,
  • непосредственная,
  • прямая,
  • косвенная и
  • стековая.

Большинство остальных способов адресации являются комбинациями или видоизменениями перечисленнных.

В первом случае операнд(ы) располагаются в регистрах микропроцессора (МП), например по команде MOV AX,CX пересылается содержимое CX в AX.

При непосредственной адресации операнд располагается в памяти непосредственно за КОП, инструкция MOV AL,0f5h записывает число 245(f5) в регистр AL.

В случае прямой адресации за КОП следует не сам операнд, а адрес ячейки памяти или внешнего устройства, например команда IN AL,40h вводит байт данных из внешнего устройства с адресом 40h.

Косвенная адресация отличается от регистровой тем, что в регистре хранится адрес операнда, т.е. по команде MOV AL,[BX] в аккумулятор al будет записано число из ячейки памяти с адресом, хранящимся в регистре BX.


Стековая адресация производится к операндам расположенным в области памяти, называемой стек.

НЕКОТОРЫЕ ОПЕРАТОРЫ, ПРЕДОПРЕДЕЛЁННЫЕ ИМЕНА, ДИРЕКТИВЫ И КОМАНДЫ АССЕМБЛЕРА 80X86(8088)

ПРЕДОПРЕДЕЛЕННЫЕ ИМЕНА

1. $ — программный счетчик. Этот символ отмечает текущий адрес в текущем сегменте. Полезен при определении длины цепочек байтов или строк.

text DB ‘This string has NN letters’

NN = $ — text; NN = длине строки text (количеству байтов

в этой строке). Не путать часть строки ‘..NN..’ и константу NN!

2. @data — адрес начала сегмента данных.

в сегментном регистре DS теперь адрес сегмента данных.

3. ??date, ??time, ??filename — эти имена во время трансляции заменяются, соответственно на текущие дату, время и имя файла в формате ASCII.

ОПЕРАТОРЫ

1. () — скобки, определяют порядок вычислений

2. [] — например [BX] означает содержимое ячейки памяти с адресом в регистре bx. Признак косвенной адресации.

3. +, -, *, / — операторы сложения, вычитания, умножения и деления.

mov ax, (2 * 3 + 8 / 2) — 2; в регистр ax будет помещено число 8.

4. MOD — деление по модулю. Даёт остаток.

5. SHL,SHR — сдвиг операнда влево, вправо.

mov si, 01010101b SHR 3; в регистр SI будет загружено число 0Ah (00001010).

6. NOT — побитовая инверсия.

7. AND,OR,XOR — операции «И»,»ИЛИ»,»ИСКЛ.ИЛИ».

mov dl, (10d OR 5d) XOR 7d; (dl) будет равно 8.

8. : — переназначение сегмента.

mov dl,[es:bx]; поместить в dl байт данных из сегмента es и отстоящий от его начала на (bx) байтов (смещение).

Структура программы на ассемблере

Программирование на уровне машинных команд — это тот минимальный уровень, на котором возможно составление программ. Система машинных команд должна быть достаточной для того, чтобы реализовать требуемые действия, выдавая указания аппаратуре вычислительной машины.

Каждая машинная команда состоит из двух частей:

  • операционной — определяющей, «что делать»;
  • операндной — определяющей объекты обработки, «с чем делать».

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

метка команда/директива операнд(ы) ;комментарии

При этом обязательным полем в строке является команда или директива.

Метка, команда/директива и операнды (если имеются) разделяются по крайней мере одним символом пробела или табуляции.

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

По умолчанию язык ассемблера не различает заглавные и строчные буквы в написании команд или директив.

Примеры строк кода:

Метки

Метка в языке ассемблера может содержать следующие символы:

  • все буквы латинского алфавита;
  • цифры от 0 до 9;
  • спецсимволы: _, @, $, ?.

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

Первым символом в метке должна быть буква или спецсимвол (но не цифра). Максимальная длина метки – 31 символ. Все метки, которые записываются в строке, не содержащей директиву ассемблера, должны заканчиваться двоеточием : .

Команды

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

Директивы

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

Операнды

Операнд – объект, над которым выполняется машинная команда или оператор языка программирования.
Команда может иметь один или два операнда, или вообще не иметь операндов. Число операндов неявно задается кодом команды.
Примеры:

  • Нет операндов ret ;Вернуться
  • Один операнд inc ecx ;Увеличить ecx
  • Два операнда add eax,12 ;Прибавить 12 к eax

Метка, команда (директива) и операнд не обязательно должны начинаться с какой-либо определенной позиции в строке. Однако рекомендуется записывать их в колонку для большего удобства чтения программы.

В качестве операндов могут выступать

  • идентификаторы;
  • цепочки символов, заключенных в одинарные или двойные кавычки;
  • целые числа в двоичной, восьмеричной, десятичной или шестнадцатеричной системе счисления.
Идентификаторы

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

Правила записи идентификаторов.

  • Идентификатор может состоять из одного или нескольких символов.
  • В качестве символов можно использовать буквы латинского алфавита, цифры и некоторые специальные знаки: _, ?, $, @.
  • Идентификатор не может начинаться символом цифры.
  • Длина идентификатора может быть до 255 символов.
  • Транслятор воспринимает первые 32 символа идентификатора, а остальные игнорирует.
Комментарии

Комментарии отделяются от исполняемой строки символом ; . При этом все, что записано после символа точка с запятой и до конца строки, является комментарием. Использование комментариев в программе улучшает ее ясность, особенно там, где назначение набора команд непонятно. Комментарий может содержать любые печатные символы, включая пробел. Комментарий может занимать всю строку или следовать за командой на той же строке.


Структура программы на ассемблере

Программа, написанная на языке ассемблера, может состоять из нескольких частей, называемых модулями . В каждом модуле могут быть определены один или несколько сегментов данных, стека и кода. Любая законченная программа на ассемблере должна включать один главный, или основной, модуль, с которого начинается ее выполнение. Модуль может содержать сегменты кода, сегменты данных и стека, объявленные при помощи соответствующих директив. Перед объявлением сегментов нужно указать модель памяти при помощи директивы .MODEL.

Пример «ничего не делающей» программы на языке ассемблера:

В данной программе представлена всего одна команда микропроцессора. Эта команда RET . Она обеспечивает правильное окончание работы программы. В общем случае эта команда используется для выхода из процедуры.
Остальная часть программы относится к работе транслятора.
.686P — разрешены команды защищенного режима Pentium 6 (Pentium II). Данная директива выбирает поддерживаемый набор команд ассемблера, указывая модель процессора. Буква P, указанная в конце директивы, сообщает транслятору о работе процессора в защищенном режиме.
.MODEL FLAT, stdcall — плоская модель памяти. Эта модель памяти используется в операционной системе Windows. stdcall
.DATA — сегмент программы, содержащий данные.
.CODE — блок программы, содержащей код.
START — метка. В ассемблере метки играют большую роль, что не скажешь о современных языках высокого уровня.
END START — конец программы и сообщение транслятору, что начинать выполнение программы надо с метки START .
Каждый модуль должен содержать директиву END , отмечающую конец исходного кода программы. Все строки, которые следуют за директивой END , игнорируются. Если опустить директиву END , то генерируется ошибка.
Метка, указанная после директивы END , сообщает транслятору имя главного модуля, с которого начинается выполнение программы. Если программа содержит один модуль, метку после директивы END можно не указывать.

Perl, Python — блог программиста

Просто примеры работающих программ на masm32, без подробного разбора и т.п. Использование MessageBox, ExitProcess. Вызов WinAPI-функции с помощью invoke. Подключение файлов и библиотек с помощью includelib и include. Инструкции call, push, test, add, jz, jmp, jne, mov, inc, dec, cmp. Реализация цикла через .REPEAT и .UNTIL, .WHILE и .ENDW. Использование .IF, .ENDIF, .CONTINUE и .BREAK.

Программа 1. Получение данных из командной строки

Программа получает данные из командной строки и выводит их в небольшом windows-окне.

Код с вызовом функций можно было бы заменить кодом:

invoke — это встроенный макрос для упрощения кода, и при компиляции всё это преобразуется в ассемблерные команды.

Стек — это удобное место для хранения информации. Чаще всего он используется при вызове функций.

Все WinAPI-функции созданы по соглашению stdcall , то есть, передача аргументов в них производится через стек в обратном порядке. Возвращают эти функции значение в регистре eax.

Программа 2. Сложение двух чисел

Программа складывает два числа, и проверяет результат. Если сумма равна 0 — выводится одно сообщение, если нет — другое.

В данной задаче самое интересное — это работа с метками, командами jz , jmp и test .

test — это операция логического сравнения двух операндов, размерностью байт, слово или двойное слово. В процессе выполняется операцию логического умножения: бит результата равен 1, если соответствующие биты операндов равны 1, в остальных случаях бит результата равен 0. Затем устанавливаются флаги, в том числе флаг ZF (zero flag), который равен 1, если результат логического умножения равен нулю.

Флаг ZF в дальнейшем используется для анализа результата.

jnz — выполняет переход по указанной метке, если не установлен флаг ZF. Данная команда обычно используется с операциями сравнения, которые влияют на состояние флага ZF. Например, test и cmp .

jz — выполняет переход по указанной метке, если установлен флаг ZF. Данная команда обычно используется с операциями сравнения, которые влияют на состояние флага ZF. Например, test и cmp .

jmp — выполняет безусловный переход по указанной метке.

Программа 3. Организация цикла

Программа использует repeat для организации цикла.

Простые примеры программ на ассемблере для новичков

17 ответов

.model tiny
.code
org 100h
begin:
jmp start

_mul proc
push bp
mov bp,sp
mov ax,[bp+4]
mul word ptr [bp+6]
mov [bp+4],ax
mov [bp+6],dx
pop bp
ret
_mul endp

start:
mov dx,130h
push dx
push 100h
call _mul
pop ax
pop bx
ret
end begin

:D У меня есть инфа по этому поводу, могу скинуть тебе на мыло если заинтересовался этим ;) . Есть даж код загруза в память вируса Satana. и ещё кое что интересное)))))) :D

Кое-что есть))) Есть описания самого вируса по «шагам» и вырезки кода. ;) Есть и документуха по написанию виря, кот. так сказать перехватывает MBR)))))) Ну раньше же баловался я этим, осталось кое чего. :D

Ого-го)) Ну ты дал))) Для начала его надо где-то словить, потом ещё определить, затем как то поймать исходный файл откуда он начал распространение, ну а потом уже дизасемблировать)) А ты хоть раз хотя бы Softice в глаза то видел? Думаешь это так просто? :)

Наивный. так я тебе и сказал))) :rolleyes:

В ТРЕТЬЕЙ И ДЕСЯТОЙ СТРОЧКЕ ОШИБКИ
error A2008: syntax error: begin
error A2006: undefined symbol :begin

Создание программы на ассемблере

Цель работы — знакомство со специальными программными средствами, предназначенными для преобразования исходных текстов на ассемблере к виду, приемлемому для выполнения на компьютере. Процесс разработки программы состоит из нескольких этапов:

постановка и формулировка задачи,

отладка и тестирование,

эксплуатация и сопровождение:

Количество этапов и их содержание определяется особенностями конкретной задачи, ее назначением, объемом кода и обрабатываемых данных, другими характеристиками задачи. Некоторые из этих этапов могут либо выполняться одновременно с другими этапами, либо вовсе отсутствовать.

У существующих реализаций ассемблера нет интегрированной среды, подобно интегрированным средам Turbo Pascal, Turbo C или Visual C++. Поэтому для выполнения всех функций по вводу кода программы, ее трансляции, компоновке, редактированию и отладке необходимо использовать отдельные служебные программы.

На рис.1 приведена общая схема процесса разработки программы на ассемблере. На схеме выделено четыре шага этого процесса.

На первом шаге, когда вводится код программы, можно использовать любой текстовый редактор. Основным требованием к нему является то, чтобы он не вставлял посторонних символов (спецсимволов редактирования). Файл должен иметь расширение .asm.

Программы, реализующие остальные шаги схемы, входят в состав программного пакета ассемблера. На рынке ассемблеров для микропроцессоров фирмы Intel имеется два пакета:

»Макроассемблер» (MASM) фирмы Microsoft.

Turbo Assembler (TASM) фирмы Borland.

Пакет TASM интересен тем, что имеет два режима работы. Один из этих режимов, называемый MASM, поддерживает все основные возможности макроассемблера MASM. Другой режим, называемый IDEAL, представляет более удобный синтаксис написания программ, более эффективное использование памяти при трансляции программы и другие новшества, приближающие компилятор ассемблера к компиляторам языков высокого уровня.

В эти пакеты входят трансляторы, компоновщики, отладчики и другие утилиты для повышения эффективности процесса разработки программ на ассемблере. Транслятор TASM, работая в режиме MASM, поддерживает почти все возможности транслятора MASM.

п рограммы PRG.LST

T ASM.EXE PRG.CRF

Рис.1. Процесс разработки программы на ассемблере

Ввод исходного текста программы

Смотри порядок выполнения работы!

2. Создание объектного модуля (трансляция программы)

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

Для получения объектного модуля исходный файл программы на ассемблере, который должен быть предварительно подготовлен и записан на диск, необходимо подвергнуть трансляции при помощи программы tasm.exe из пакета TASM. Формат командной строки для запуска tasm.exe следующий:

TASM [опции] имя_исходного_файла [,имя_объектного_файла] [,имя_файла_листинга] [,имя_файла_перекрестных_ссылок]

Если вы забыли формат командной строки и возможные значения параметров, то получить быструю справку на экране монитора можно запустив файл tasm.exe без задания каких-либо аргументов. Обратите внимание, что большинство параметров заключено в квадратные скобки. Это общепринятое соглашение по обозначению параметров, которые могут отсутствовать.

Таким образом, обязательным аргументом командной строки является имя_исходного_файла. Этот файл должен находиться на диске и обязательно иметь расширение .asm. За именем исходного файла через запятую могут следовать необязательные аргументы, обозначающие имена объектного файла, файла листинга и файла перекрестных ссылок.

Если имена объектного файла , файла листинга и файла перекрестных ссылокдолжны совпадать с именем исходного файла (наиболее типичный случай), то нужнопростопоставитьзапятыевместо имен этих файлов:

Применение опции /zi разрешает транслятору сохранить связь символических имен в программе и их смещений в системе кода, что позволит отладчику производить отладку,используя оригинальные имена. В результате будут созданы файлы, как показано на рис. 1 для шага 2.

Если имена объектного файла, файла листинга и файла перекрестных ссылок не должны совпадать с именем исходного файла, то нужно в соответствующем порядке в командной строке указать имена соответствующих файлов, к примеру:

В результате на диске будут созданы файлы:


Если требуется выборочное создание файлов, то вместо ненужных файлов необходимо подставить параметр nul. например:

В результате на диске будут созданы файлы:

Логика работы программы для транслятора не имеет никакого значения. Вы можете написать абсолютную чушь, но если она будет синтаксически правильна, транслятор поспешит вас обрадовать, сообщив, что всё хорошо. Наличие строки Warning означает, что конструкция синтаксически правильна, но не соответствует некоторым соглашениям языка и это может послужить источником последующих ошибок.

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

Для устранения ошибок нужно определить место их возникновения и проанализировать ситуацию. Место ошибки легко определяется по значению в скобках в сообщении об ошибке. Это значение является номером ошибочной строки. Запомнив его, вы переходите в файл с исходной программой и по номеру строки находите место ошибки.

Этот способ локализации ошибок имеет недостатки. Во-первых, он не нагляден. Во- вторых, не всегда номер строк сообщений соответствует действительным номерам ошибочных строк в исходном файле. Такая ситуация будет наблюдаться, например, в том случае, если вы используете макрокоманды. При их использовании транслятор вставляет файл дополнительные строки в соответствии с описанием применяемой макрокоманды, в результате чего получается отличие в нумерации.

Для локализации ошибок лучше использовать информацию из специального, создаваемого транслятором файла листинга. Этот файл имеет расширение .lst; его имя определяется в соответствии с теми соглашениями, которые мы разобрали выше.

Ниже приведен полный формат листинга для программы, содержащей некоторые ошибки. Листинг- очень важный документ, и ему нужно уделить должное внимание.

Turbo Assembler Version 3.1 13/09/09 15:10:55 Page 1

Ассемблер. Базовый синтаксис

Обновл. 29 Сен 2020 |

Программы на ассемблере могут быть разделены на три секции:

Секция data

Секция bss

Секция text

Секции ассемблера

Секция data используется для объявления инициализированных данных или констант. Данные в этой секции не могут быть изменены во время выполнения. Вы можете хранить константные значения и названия файлов в этой секции. Синтаксис объявления:

Секция bss используется для объявления переменных. Синтаксис объявления:

Секция text используется для хранения кода программы. Данная секция должна начинаться с объявления global_start , которое сообщает ядру, откуда нужно начинать выполнение программы. Синтаксис объявления:

Комментарии

Комментарии в ассемблере должны начинаться с точки с запятой ( ; ). Они могут содержать любой печатный символ, включая пробел. Комментарий может находиться как на отдельной строке:

Так и на строке со стейтментом:

Стейтменты

В ассемблере есть три вида стейтментов:

Выполняемые инструкции (или просто «инструкции»), которые сообщают процессору, что нужно делать. Каждая инструкция хранит в себе код операции (или ещё «опкод») и генерирует одну инструкцию на машинном языке.

Цукерберг рекомендует:  3D-принтер что это и как он работает

Директивы ассемблера, которые сообщают программе об аспектах компиляции. Они не генерируют инструкции на машинном языке.

Макросы, которые являются простым механизмом вставки кода.

В ассемблере на одну строку приходится один стейтмент, который должен соответствовать следующему формату:

Базовая инструкция состоит из названия инструкции ( mnemonic ) и операндов (они же «параметры»). Вот примеры типичных стейтментов ассемблера:

Первая программа

Следующая программа на языке ассемблера выведет строку Hello, world! на экран:

Результат выполнения программы выше:

Сборка программ

Убедитесь, что у вас установлен NASM. Запишите вашу программу в текстовом редакторе и сохраните её как hello.asm . Затем:

убедитесь, что вы находитесь в той же директории, в которой вы сохранили hello.asm;

чтобы собрать программу, введите команду nasm -f elf hello.asm ;

если не было ошибок, то создастся объектный файл вашей программы под названием hello.o;

чтобы ваш объектный файл прошёл линкинг и создался исполняемый файл под названием hello, введите команду ld -m elf_i386 -s -o hello hello.o ;

запустите программу, написав команду ./hello .

Если всё прошло успешно, то вам выведется Hello, world! .

Если у вас нет возможности скомпилировать программу, например, у вас нет Linux и вы пока не хотите на него переходить, то можете использовать одну из следующих онлайн-IDE:

Примечание: Запоминать две команды выше для сборки программы на ассемблере для некоторых может быть несколько затруднительно, поэтому вы можете написать скрипт для сборки программ на ассемблере. Для этого создайте файл под названием Makefile со следующим содержанием:

Программирование на языке Assembler в FASM

В начале было слово… Если точнее то было просто предложение от Kinder-а написать статью посвящённую макросам в FASM. Я согласился попробовать, но рассматривать макросы отдельно от синтаксиса как-то не очень правильно, а синтаксис без примеров разобрать сложно и в результате получилось сочинение о том как писать программы в FASM.

В этом сочинении будут рассмотрены основные моменты отличающие FASM от sдругих компиляторов, правила по которым пишутся макросы и форматы выходных файлов создаваемые FASM-ом. Так же в рамках данного сочинения мы создадим несколько контролов в виде макросов .

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

Данная статья посвящена использованию макросов в компиляторе FASM. Для начала вопрос: «Что такое макрос?» (поскольку я не могу услышать Ваш ответ прямо сейчас, когда пишу эту статью, отправляйте свои ответы мне по электронной почте, если Ваше мнение отличается от моего мнения).

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

Далее предлагаю Вам ознакомиться с основными директивами, используемыми внутри определений макросов.

Внутри определений макросов можно использовать инструкции if и else при этом каждая инструкция if должна закрываться инструкцией end if. Пример из руководства, поставляемого вместе с компилятором:

Этот макрос является расширением инструкции процессора mov. В данном случае если оба операнда макроса являются сегментными регистрами используется связка «push — pop», в любом другом случае используется стандартная инструкция «mov op1,op2». Оператор in позволяет проверить соответствие операнда нескольким значениям в угловых скобках.

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

В данном случае нас интересует инструкция eq, которая позволяет проверить эквивалентен ли операнд какому либо значению, в нашем случае мы проверяем его отсутствие, и если он отсутствует, то подставляется ветвь if иначе подставляется ветвь else.

Директива purge позволяет отменить последнее определение макроса.

Кроме уже изложенного в определение макроса можно передавать заранее неизвестное количество операндов (пар операндов и т.д.), для этого необходимо заключить операнды, количество которых заранее неизвестно, в квадратные скобки. Например для вызова процедур написан макрос STDCALL:

Операндами этого макроса служат один обязательный (proc) и несколько необязательных параметров (arg). Директива reverse сообщает препроцессору, что следующие строки необходимо повторить столько раз, сколько параметров arg передано макросу начиная с последнего параметра. Директива common сообщает препроцессору, что следующие строки необходимо повторить только один раз. Директива forward сообщает препроцессору, что следующие строки необходимо повторить столько раз, сколько параметров arg передано макросу начиная с первого параметра. Действие директив common, forward и reverse заканчивается другой директивой common, forward или re-verse соответственно или закрывающейся фигурной скобкой. Если ни одна из этих директив не встречается в определении макроса, то макрос развернётся для всех параметров начиная с первого. Неизвестное количество параметров можно передать и другому макросу:

В этом примере при выполнении ветви if в макрос stdcall кроме параметра [proc] передаются все полученные параметры arg.

Внутри макроопределения может использоваться оператор #, который может использоваться для составления операторов ассемблера, например условного перехода:

При выполнении макроса jif ax,ae,10h,exit макрос будет развёрнут в следующую конструкцию:

Так же этот оператор можно использовать и для составления имён переменных или макросов внутри макроопределений:


Теперь при вызове данного макроса в секции данных

Мы будем иметь два массива из 10 байт каждый, с именами Chif.mas и Rab.mas.

Аналогично можно создать и переменные и макросы внутри определения макроса. Однако определить макрос внутри макроса обычным путём:

препроцессор не позволит использовать данную конструкцию, выход в использовании директивы fix она эквивалентна директиве equ, но препроцессор обрабатывает fix позже чем equ, что и позволяет использовать подобную конструкцию:

Синтаксис компилятора ФАСМ несколько отличается от синтаксиса других компиляторов, но отличия незначительны и в основном касаются макроязыка.

Средства макроязыка мы рассмотрим ниже, здесь же я хотел бы отметить только то, что передача адреса переменной производится указанием имени переменной без каких либо скобок или префиксов, а её содержимого написанием имени переменной в квадратных скобках.

mov eax,var; записываем в регистр еах адрес переменной var mov eax,[var]; записываем в регистр еах значение переменной var

Структура каркасного приложения на ФАСМ-е

В этой части мы рассмотрим, основы
синтаксиса данного компилятора и
рассмотрим каркасные приложения для
основных форматов исполняемых файлов.

Итак, что необходимо для работающего приложения?

Во-первых мы должны указать компилятору какого формата должен быть создаваемый файл. Это делается директивой format после, которой следует указание форматов:

  • PE формат исполняемых файлов Windows. Далее должно следовать уточнение GUI (графический интерфейс), console (консольное приложение) и native (если Вы знаете, что это такое сообщите мне). Если выбран графический интерфейс, то следует также указать версию графического интерфейса 4.0, а так же зарезервированное слово DLL, если собирается динамическая библиотека.
  • MZ формат исполняемых файлов MS DOS.
  • COFF или MS COFF (для линковки продуктами Microsoft)для создания объектного файла, который в дальнейшем будет линковаться к другому проекту или к ресурсам.
  • ELF формат для создания исполняемых файлов UNIX подобных систем (Linux).

Как Вы наверно поняли, я не знаком с файлами ELF типа и в данной статье мы не будем его рассматривать. Если директива format не указана, компилятор соберёт файл в формате com.

После указания формата выходного файла мы должны указать компилятору точку начала программы. Это делается с помощью директивы entry после которой указывается метка, с которой начинается код программы. Например entry start.

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

Форматы PE исполняемых файлов Windows.
Консольные приложения
Консольное приложение может использовать все функции API предоставляемые Windows, но работать с такой программой пользователь может только через командную строку.

Вот простой пример консольного приложения:

В данном примере создаётся три секции: данных, кода и секция импортируемых функций. Секцию импорта я создаю при помощи макроса _library, в стандартной поставке такого макроса нет, но если написать без подчёркивания перед library то всё скомпилируется правильно. Причина проста, я пользуюсь FASM-ом с версии 1.35, а тогда такого макроса еще не было (а сам я ещё не смог бы его написать) и для совместимости с ранее написанными программами я включил два варианта этого макроса. В ранней версии, необходимо было записывать все используемые в приложении функции вручную, теперь же работу по подключению только используемых функций берёт на себя компилятор, хотя за такое удобство приходится платить некоторым увеличением времени компиляции. Более подробно о принципе работы этого и других макросов будет написано в соответствующем разделе ниже.

Название секции импорта «.idata», хотя название можно изменить и это не повлияет на работоспособность программы, т.к. мы указали назначение этой секции import data, далее следует указание атрибутов, в данном случае readable, что означает для чтения и этого достаточно, если Вы не собираетесь менять содержимое этой секции.

Следующей рассмотрим секцию данных. У нас секция данных имеет название «.data», далее следует указание назначения секции data, что означает секцию данных, атрибуты указаны readable writeable, т.е. эта область памяти будет доступна для чтения и записи. Если указать только первый атрибут (readable) при выполнении программы произойдёт ошибка «Программа выполнила недопустимую операцию».

Последней рассмотрим секцию кода, она имеет название «.code» назначение секции code, т.е. исполняемая часть программы, атрибуты readable executable, что означает, что из секции кода мы можем читать данные (под данными может подразумеваться и коды инструкций и определённые в этой секции данные) и можем выполнять содержимое этой секции.

Кроме рассмотренных секций, консольное приложение может иметь секции экспортируемых функций и секцию ресурсов.

Это пожалуй всё, что необходимо знать для начала о консольных приложениях. Да ещё я считаю лишний раз напомнить, что хоть консольное приложение и напоминает внешне программы DOS-а, использование прерываний в нём не допускается т.к. это всё же приложение Windows, естественно выполняться такое приложение может только в Windows.

Графическое приложение Графические приложения Windows это оконные приложения, такие как Word, Excel, Notepad и т.д.

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

Так же, как и в примере с консольным приложением, в первой строке указана директива format PE, но вместо console, как в предыдущем примере, дано указание GUI 4.0 компилятору собирать приложение с графическим интерфейсом версии 4.0.

Приложение из нашего примера ничего полезного не делает, оно просто создаёт окно, которое реагирует только на нажатие двух системных кнопок в верхнем правом углу окна «Свернуть» и «Закрыть». Приложение содержит секции данных, кода, импорта и ресурсов. После указания точки входа в программу entry start подключаются файлы содержащие макросы и определения констант и структур Windows. Затем следует секция данных, определение которой дано в предыдущем разделе, и секция кода. В секции кода мы получаем хендл нашего модуля (GetModuleHandle), загружаем иконку (LoadIcon), определённую в секции ресурсов, загружаем стандартный курсор Windows (LoadCursor) и заполняем структуру wc для дальнейшей регистрации класса нашего окна (RegisterClass). После регистрации класса окна мы создаём окно зарегистрированного класса (CreateWindowEx). В структуре wc имеется член .lpfnWndProc, в который мы записываем адрес начала процедуры обработки событий окна WindowProc. Далее следует цикл обработки сообщений, посылаемых окну системой Windows.

Процедура обработки сообщений получает от Windows четыре параметра:

proc WindowProc, hwnd, wmsg, wparam, lparam

hwnd — хендл нашего окна
wmsg — код сообщения, которое Windows послало нашему окну
wparam и lparam — уточняющие параметры посланного сообщения.

proc это не директива компилятора, а макрос содержащий, часть пролога процедуры. Он определён следующим образом:

Таким образом следующая строка proc Win,param будет развёрнута в следующую конструкцию:

Более подробно это означает, что мы определяем метку Win, которая означает начало процедуры. В следующей строке записана директива virtual at ebp+8, которая должна заканчиваться директивой end virtual. Внутри этого определения помещаются переменные, которые будут размещаться с адреса указанного после at, в нашем примере это ebp+8. Это означает, что в нашем примере запись «mov param,eax» будет заменена на «mov [ebp+8],eax«. Если бы в определении процедуры было две или более переменных, то адрес каждой последующей переменной увеличивался бы на размер предыдущей переменной. Таким образом:

определит три переменные с адресами

У некоторых читателей может возникнуть вопрос: «А зачем все эти пляски?». Ответ прост: регистр ebp указывает на вершину стека и все переменные, передаваемые в процедуру, находятся выше (в смысле в памяти с адресами больше чем вершина стека т.к. вершина стека сдвигается вниз когда в стек помещаются данные и вверх, когда данные из стека изымаются) текущей вершины стека.

Первой, в определении переменных внутри virtual — end virtual записана директива local, которая указывает компилятору, что следующие за ней через запятую метки, локальны в данном макросе.

Последней, внутри описания виртуальных переменных, следует определение макропеременной ..ret = $ — (ebp+8), которая фактически содержит количество переданных процедуре байт параметров. Она используется при написании эпилога процедуры для очистки стека при возврате управления инструкции, следующей за вызовом данной процедуры.

После определения виртуальных переменных, передаваемых процедуре в качестве параметров, определяются локальные макропеременные, которые используются во второй части пролога процедуры. Я уже несколько раз использовал термин — макропеременная. Макропеременная отличается от переменных, используемых в программе, тем, что она существует только во время компиляции и используется как константа, которая может иметь разные значения в разных частях программы.

После определения макропеременных начинается блок определения виртуальных переменных, однако переменных в этом макросе нет. После макроса proc необходимо ставить макрос enter, который содержит заключительную часть пролога, а между ними могут располагаться локальные переменные. Для того, что бы имена переменных были локальными перед ними ставится точка. Например .param. Макрос enter определён следующим образом:

Таким образом dynamic_size — это размер памяти отведённой под локальные переменные. Последняя инструкция, в прологе процедуры, enter dynamic_size,0. Это единственная строка, которая попадёт в исполняемый код программы. Завершаться процедура должна макросом return, который определён следующим образом:

Полагаю здесь не должно быть сложностей с пониманием макроса.

Процедура должна правильно обрабатывать поступившие сообщения. Те сообщения, которые мы хотим обработать сами, выделяются в первых строках процедуры и управление передаётся на те участки кода, которые должны производить необходимые действия, а те сообщения, которые нам нет необходимости обрабатывать, мы доверяем обработать системе Windows (Def-WindowProc).

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

Теперь пришло время посмотреть на секцию импорта. Она отличается от рассмотренной в предыдущем разделе здесь мы используем макрос library, который определён следующим образом:

Для того, чтобы было более понятно вспомним, как в нашем примере используется этот макрос:

Таким образом, макрос library создаёт заголовок таблицы импорта, вначале записывая смещение названия библиотеки DLL, а затем смещение, с которого начинаются имена импортируемых функций.

Далее следуют макросы import, определённые следующим образом:

Он создаёт тело таблицы импорта для каждой из импортируемых библиотек.

Вначале создавая таблицу смещений имён импортируемых функций, а затем записывая имена импортируемых функций. Более подробно о структуре PE-файла Вы должны прочитать в соответствующей литературе (например в туториалах, которые написал Iczelion).

После таблицы импорта создаётся таблица ресурсов:

В нашем примере мы используем только одну иконку.

Динамические библиотеки (DLL) Последним в данном разделе рассмотрим простую динамическую библиотеку.

Динамическая библиотека — это приложение GUI, который может содержать как исполняемый код, так и ресурсы. Это приложение не предназначено для самостоятельного запуска, оно используется другими приложениями для хранения часто используемых процедур, шрифтов и многого другого.

Мы рассмотрим создание динамической библиотеки на примере процедуры, возвращающей строку, содержащую текущую дату. Мы создадим два модуля один файл динамической библиотеки (dll2.dll) и файл использующий созданную нами библиотеку (usedll.exe).

Вот файл динамической библиотеки:

Первые строки format PE GUI 4.0 DLL и entry DllEntryPoint указывают компилятору создать РЕ-файл динамической библиотеки с точкой входа на метке DllEntryPoint.

Как Вы могли заметить динамическая библиотека практически ни чем не отличается от обычной программы Windows, кроме того, что входная процедура вызывается системой в четырёх случаях:

  • Когда динамическая библиотека загружается в адресное пространство процесса (параметр fdwReason = DLL_PROCESS_ATTACH = 1);
  • Когда динамическая библиотека выгружается из адресного пространства процесса (параметр fdwReason = DLL_PROCESS_ DETACH = 0);
  • Когда создается новый поток в рамках динамической библиотеки (параметр fdwReason = DLL_ THREAD_ATTACН = 2);
  • Когда разрушается (прекращается) поток в рамках динамической библиотеки (параметр fdwReason = DLL_ THREAD_DETACH = 3).


В обработке стартовой процедуры мы определяем значение параметра fdwReason и если динамическая библиотека была загружена или выгружена выдаём соответствующее сообщение.

В конце файла мы создаём секцию экспорта нашей библиотеки:

В таблице мы задаём имя файла динамической библиотеки и имена экспортируемых процедур.

Макрос export определён следующим образом:

Сперва, объявляются локальные метки и подсчитывается количество экспортируемых процедур:

Затем заносится смещение RVA имени библиотеки:

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

Далее объявляется метка начала массива смещений экспортируемых функций и создаётся массив их смещения:

Следом объявляется метка начала массива смещений имён экспортируемых функций и создаётся сам массив:

Затем объявляется метка начала массива смещений ординалов функций и создаётся массив ординалов экспортируемых функций:

Ну и в конце макроса записывается имя динамической библиотеки и создаётся массив имен экспортируемых функций:

Для более подробного ознакомления со структурой секции экспорта необходимо читать специальную литературу по структуре РЕ — файлов.

Теперь давайте посмотрим, на программу вызывающую нашу динамическую библиотеку:

Обычное графическое приложение не имеющее своего окна и ресурсов размером 2 кБ. Импортирует только три функции две предоставляемые системой Windows и одна написанной нами динамической библиотекой. Как видите подключение созданной нами динамической библиотеки, ничем не отличается от подключения стандартных библиотек Windows. В этом и заключается вся прелесть использования ФАСМ-а, он не берёт на себя слишком много, что в купе с гибким макроязыком позволяет сделать очень многое максимально просто.

Формат исполняемых файлов MS COFF (obj — файлы) Использование obj-файлов в ФАСМ, лично мне, не кажется жизненно необходимым, однако использование этого формата возможно понадобится при создании проектов на С/С++. Именно на таком примере мы и рассмотрим формат файлов MS COFF.

Для примера напишем программу, выводящую МессаджБокс с текущей датой. Модуль на ассемблере будет составлять строку символов, а модуль на С++ будет выводить эту строку в МессаджБокс.

Вот модуль на ассемблере:

Обратите внимание на строку 2, где закомментировано определение точки входа в программу (_WinMainCRTStartup)если эту строку раскомментировать, то точка входа в программу будет именно в модуле на ассемблере. Результат работы функции записывается в переменную _outs и адрес (указатель на строку) возвращаем в регистре еах. В этом модуле используются макросы копирования строки в строку (StringCopyHTML ebx,day). Этот макрос не входит в поставку компилятора и содержится в файлах strings.mac и strings.asm он просто копирует строку (не включая нулевой символ), указатель на которую передаётся вторым параметром, в строку, указатель которой передаётся вторым параметром, после его выполнения в регистре еах количество скопированных символов. Эта процедура выдаёт строку типа: «День: 28 Месяц: 10 Год:2003». Для использования объектного модуля созданного на ассемблере необходимо в проект на С++ добавить созданный нами объектный файл (Project->Add to project->Files. . . в появившемся окне необходимо выбрать тип файлов obj и указать используемый файл). Теперь рассмотрим текст программы на MS Visual C++ 5.0:

Здесь ничего сложного: подключаем файл «windows.h», затем объявляем внешнюю процедуру, возвращающую указатель на строку символов с нулём в конце, объявляем переменную dat, как указатель на строку символов, и наконец функция WinMain в которой вызываем нашу функцию (Wind) на ассемблере и выводим результат в мессаджбокс. Внимательный читатель скажет: «Стоп, но в модуле на ассемблере функцию мы объявили как ?Wind@@YAPADXZ, а в модуле на С++ вызываем Wind». Всё правильно. Просто MS Visual C++ 5.0, к объявленным внешним (находящимся в скомпилированных модулях) процедурам добавляет дополнительные символы, для контроля соответствия типов передаваемых результатов из процедуры и переменных в процедуру. Вот краткая структура имени функций формируемых MS Visual C++:

Префикс Объявленное имя переменной Разделитель Тип вызова процедуры Тип результата Тип параметров Постфикс
? Wind @@Y A PAD X Z

Причём постфикс изменяется в зависимости от наличия передаваемых в функцию параметров. Если параметры передаются, то постфикс «@Z», а если параметры не передаются то просто «Z».

Типы вызова процедуры следующие:

__cdecl A
__fastcall I
__stdcall G

Типы результата и передаваемых переменных имеют одинаковые обозначения:

Тип результата Размер Обозначение
char 1 D
unsigned char 1 E
short 2 F
unsigned short 2 G
enum 2 ?AW4__unnamed@@
long 4 J
unsigned long 4 K
int 4 H
unsigned int 4 I
float 4 M
double 8 N
long double 10 O
bool 1 _N

Причём если передаётся (получается) не параметр, а его адрес (например char *) то перед обозначением переменной добавляется PA. Если параметры, получаемые из процедуры или передаваемые в процедуру, отсутствуют, вместо них ставится X.

Читатель может сказать: «Это как же нужно мозг морщить, чтобы всё запомнить и не перепутать ничего при составлении имени процедуры?». Такая работа имеет основное свойство: она не особо интеллектуальна, но муторна и действия при её решении вполне укладываются в рамках алгоритма. Отсюда вывод: компьютер на то и создан, что бы выполнять подобные задачи так пусть сам, и добавляет все эти префиксы, постфиксы и т.п. Напишем макрос, который будет объявлять процедуру составляя её имя из имени которое мы ему передаём и добавляя к нему все необходимые префиксы.

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

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

Далее следует определение макроса:

В первой строке мы очищаем временную символьную константу, далее следует указание препроцессору обработать строку «dr fix dr#type» для всех значений параметров type начиная с первого. Эта строчка, просто создаёт константу содержащую все символы соответствующие всем получаемым процедурой параметрам. Например если в процедуру передаётся два параметра типа int, то dr будет эквивалентно HH. Далее следует указание препроцессору следующие строки обработать только один раз (common) в этих строках мы делаем видимой из других модулей программы метку (название) нашей процедуры и собственно следует содержимое макроса proc, рассмотренного ранее.

Цукерберг рекомендует:  Telegram_bot - Телеграм бот на python

Полагаю, этот макрос не должен вызвать затруднений. Сложности в понимании может вызвать только строка «?#name#@@Y#calltype#returns#dr#@Z», объясню её более подробно. Сперва стоит знак вопроса (посмотрите табличку со структурой имени процедуры), после которого, значок номера (#), он говорит препроцессору, что далее следует символьная константа, которую следует склеить в одну метку с предыдущим текстом. Если константы с указанным именем не существует, то она включается, как есть. Рассмотрим последовательность действий препроцессора. Метка состоит из вопросительного знака, затем (без разделителей) добавляется, передаваемая макросу переменная name, после неё добавляется текст @@Y,после текста добавляется передаваемая макросу переменная calltype, затем переменная returns, потом составленная нами константа dr, и наконец постфикс @Z (или просто Z, если в процедуру не передаются параметры).

Всё. Макрос создан. Однако это ещё не всё. С таким определением процедуры не всегда можно использовать стандартные макросы enter и return т.к. тип вызова __cdecl предполагает, что стек после завершения работы процедуры будет очищаться вызывающей процедурой т.е. модулем написанным на С++, а стандартный макрос return сам очищает стек. Вот макрос, содержащий эпилог процедуры для типа вызова __cdecl:

Но не только эпилог нужно изменить, но и пролог тоже:

Полагаю эти два коротеньких макроса не вызовут затруднений.

Ну и пример использования данных макросов:

Функция IntToString преобразует 32-х битное без знаковое число (второй параметр) в строку (адрес строки передаётся первым параметром).

Модуль на С++ будет выглядеть следующим образом:

Думаю больше пояснений не нужно.

Формат исполняемых файлов MS DOS
Формат исполняемых файлов MZ
Программирование под ДОС в ФАСМ в принципе почти ничем не отличается от программирования под ДОС в любом другом компиляторе.

Вот простейший пример программы:

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

Формат исполняемых файлов СОМ Если директива format не указана создаются файлы формата *.сом. Вот пример:

Погружение в assembler. Полный курс по программированию на асме от ][

Содержание статьи

Это первая (вступительная) статья курса. Курс рассчитан на тех, кто в целом знаком с высокоуровневым программированием и только приступает к изучению ассемблера.

Но что такое программирование само по себе по своей сути, вне зависимости от какого-либо языка? Разнообразие ответов поражает. Наиболее часто можно услышать такое определение: программирование — это составление инструкций или команд для последовательного исполнения их машиной с целью решить ту или иную задачу. Такой ответ вполне справедлив, но, на мой взгляд, не отражает всей полноты, как если бы мы назвали литературу составлением из слов предложений для последовательного прочтения их читателем. Я склонен полагать, что программирование ближе к творчеству, к искусству. Как любой вид искусства — выражение творческой мысли, идеи, программирование представляет собой отражение человеческой мысли. Мысль же бывает и гениальная, и совершенно посредственная.

Но, каким бы видом программирования мы ни занимались, успех зависит от практических навыков вкупе со знанием фундаментальных основ и теории. Теория и практика, изучение и труд — вот краеугольные камни, на которых основывается успех.

В последнее время ассемблер незаслуженно находится в тени других языков. Обусловлено это глобальной коммерциализацией, направленной на то, чтобы в максимально короткие сроки получить как можно большую прибыль от продукта. Иными словами, массовость взяла верх над элитарностью. А ассемблер, по моему мнению, ближе к последнему. Гораздо выгоднее в сравнительно небольшие сроки поднатаскать ученика в таких, например, языках, как С++, С#, PHP, Java, JavaScript, Python, чтобы он был более-менее способен создавать ширпотребный софт, не задаваясь вопросами, зачем и почему он так делает, чем выпустить хорошего специалиста по ассемблеру. Примером тому служит обширнейший рынок всевозможных курсов по программированию на любом языке, за исключением ассемблера. Та же тенденция прослеживается как в преподавании в вузах, так и в учебной литературе. В обоих случаях вплоть до сегодняшнего дня большая часть материала базируется на ранних процессорах серии 8086, на так называемом «реальном» 16-битном режиме работы, операционной среде MS-DOS! Возможно, что одна из причин в том, что, с одной стороны, с появлением компьютеров IBM PC преподавателям пришлось перейти именно на эту платформу из-за недоступности других. А с другой стороны, по мере развития линейки 80х86 возможность запуска программ в режиме DOS сохранялась, что позволяло сэкономить деньги на приобретение новых учебных компьютеров и составление учебников для изучения архитектуры новых процессоров. Однако сейчас такой выбор платформы для изучения совершенно неприемлем. MS-DOS как среда выполнения программ безнадежно устарела уже к середине девяностых годов, а с переходом к 32-битным процессорам, начиная с процессора 80386, сама система команд стала намного более логичной. Так что бессмысленно тратить время на изучение и объяснение странностей архитектуры реального режима, которые заведомо никогда уже не появятся ни на одном процессоре.

Что касается выбора операционной среды для изучения ассемблера, то, если говорить о 32-битной системе команд, выбор сравнительно невелик. Это либо операционные системы Windows, либо представители семейства UNIX.

Также следует сказать несколько слов о том, какой именно ассемблер выбрать для той или другой операционной среды. Как известно, для работы с процессорами х86 используются два типа синтаксиса ассемблера — это синтаксис AT&T и синтаксис Intel. Эти синтаксисы представляют одни и те же команды совершенно по-разному. Например, команда в синтаксисе Intel выглядит так:

В синтаксисе же AT&T уже будет иной вид:

В среде ОС UNIX более популярен синтаксис типа AT&T, однако учебных пособий по нему нет, он описывается исключительно в справочной и технической литературе. Поэтому логично выбрать ассемблер на основе синтаксиса Intel. Для UNIX-систем есть два основных ассемблера — это NASM (Netwide Assembler) и FASM (Flat Assembler). Для линейки Windows популярностью пользуются FASM и MASM (Macro Assembler) от фирмы Microsoft, и также существовал еще TASM (Turbo Assembler) фирмы Borland, которая уже довольно давно отказалась от поддержки собственного детища.

В данном цикле статей изучение будем вести в среде Windows на основе языка ассемблера MASM (просто потому, что он мне нравится больше). Многие авторы на начальном этапе изучения ассемблера вписывают его в оболочку языка си, исходя из тех соображений, что перейти к практическим примерам в операционной среде якобы довольно трудно: нужно знать и основы программирования в ней, и команды процессора. Однако и такой подход требует хоть мало-мальских начатков знаний в языке си. Данный же цикл статей от самого своего начала будет сосредоточен только на самом ассемблере, не смущая читателя ничем иным, ему непонятным, хотя в дальнейшем и будет прослеживаться связь с другими языками.

Следует отметить, что при изучении основ программирования, и это касается не только программирования на ассемблере, крайне полезно иметь представление о культуре консольных приложений. И совершенно нежелательно начинать обучение сразу же с создания окошечек, кнопочек, то есть с оконных приложений. Бытует мнение, что консоль — архаичный пережиток прошлого. Однако это не так. Консольное приложение почти лишено всякой внешней зависимости от оконной оболочки и сосредоточено главным образом на выполнении конкретно поставленной задачи, что дает прекрасную возможность, не отвлекаясь ни на что другое, концентрировать внимание на изучении базовых основ как программирования, так и самого ассемблера, включая знакомство с алгоритмами и их разработку для решения практических задач. И к тому моменту, когда настанет время перейти к знакомству с оконными приложениями, за плечами уже будет внушительный запас знаний, ясное представление о работе процессора и, самое главное, осознание своих действий: как и что работает, зачем и почему.

Что такое ассемблер?

Само слово ассемблер (assembler) переводится с английского как «сборщик». На самом деле так называется программа-транслятор, принимающая на входе текст, содержащий условные обозначения машинных команд, удобные для человека, и переводящая эти обозначения в последовательность соответствующих кодов машинных команд, понятных процессору. В отличие от машинных команд, их условные обозначения, называемые также мнемониками, запомнить сравнительно легко, так как они представляют собой сокращения от английских слов. В дальнейшем мы будем для простоты именовать мнемоники ассемблерными командами. Язык условных обозначений и называется языком ассемблера.

На заре компьютерной эры первые ЭВМ занимали целые комнаты и весили не одну тонну, имея объем памяти с воробьиный мозг, а то и того меньше. Единственным способом программирования в те времена было вбивать программу в память компьютера непосредственно в цифровом виде, переключая тумблеры, проводки и кнопочки. Число таких переключений могло достигать нескольких сотен и росло по мере усложнения программ. Встал вопрос об экономии времени и денег. Поэтому следующим шагом в развитии стало появление в конце сороковых годов прошлого века первого транслятора-ассемблера, позволяющего удобно и просто писать машинные команды на человеческом языке и в результате автоматизировать весь процесс программирования, упростить, ускорить разработку программ и их отладку. Затем появились языки высокого уровня и компиляторы (более интеллектуальные генераторы кода с более понятного человеку языка) и интерпретаторы (исполнители написанной человеком программы на лету). Они совершенствовались, совершенствовались — и, наконец, дошло до того, что можно просто программировать мышкой.

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

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

Синтаксис


Общепринятого стандарта для синтаксиса языков ассемблера не существует. Однако большинство разработчиков языков ассемблера придерживаются общих традиционных подходов. Основные такие стандарты — Intel-синтаксис и AT&T-синтаксис.

Общий формат записи инструкций одинаков для обоих стандартов:

Опкод — это и есть собственно ассемблерная команда, мнемоника инструкции процессору. К ней могут быть добавлены префиксы (например, повторения, изменения типа адресации). В качестве операндов могут выступать константы, названия регистров, адреса в оперативной памяти и так далее. Различия между стандартами Intel и AT&T касаются в основном порядка перечисления операндов и их синтаксиса при разных методах адресации.

Используемые команды обычно одинаковы для всех процессоров одной архитектуры или семейства архитектур (среди широко известных — команды процессоров и контроллеров Motorola, ARM, x86). Они описываются в спецификации процессоров.

Например, процессор Zilog Z80 наследовал систему команд Intel i8080, расширил ее и поменял некоторые команды (и обозначения регистров) на свой лад. Например, сменил Intel-команду mov на ld. Процессоры Motorola Fireball наследовали систему команд Z80, несколько ее урезав. Вместе с тем Motorola официально вернулась к Intel-командам, и в данный момент половина ассемблеров для Fireball работает с Intel-командами, а половина — с командами Zilog.

Директивы

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

  • определение данных (констант и переменных);
  • управление организацией программы в памяти и параметрами выходного файла;
  • задание режима работы компилятора;
  • всевозможные абстракции (то есть элементы языков высокого уровня) — от оформления процедур и функций (для упрощения реализации передачи параметров) до условных конструкций и циклов;
  • макросы.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Урок 1 . Основы ассемблера.

Приветствую всех на нашем сайте по языку ассемблера. Предлагаю вам серию уроков по ассемблеру. Все примеры к урокам приведены к компилятору TASM.

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

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

Сначала рассмотрим регистры общего назначения. Они называются АХ, ВХ, СХ и DX (Аккумулятор, База, Счетчик и Данные). Кроме названий, они больше ничем другим не отличаются друг от друга, поэтому рассмотрим только первый регистр — АХ . В свою очередь, регистр АХ может быть разделен на два 8-битных регистра — АН и AL. Если мы заносим в регистр АХ значение 1234h, (12h в АН и 34h в AL), а значение других регистров общего назначения называются по такому же принципу: ВХ который, в свою очередь, содержит ВН и BL и т.д.

Сегментные регистры

Эту группу регистров можно отнести к регистрам состояния. Регистры из этой группы используются при вычислении реального адреса (адреса, который будет передан на шину адреса, с помощью которой процессор обращается к оперативной памяти). Названия этих регистров соответствуют выполняемым функциям: CS (Code Segment, сегмент кода) вместе с IP определяют адрес памяти, откуда нужно прочитать следующую инструкцию; аналогично регистр SS (Stack Segment, сегмент стека) в паре с SP (SS:SP) указывают на вершину стека. Сегментные регистры DS, ES, FS, и GS (Data, Extra, F и G сегменты) используются для адресации данных в памяти.

Первая программа на ассемблере

И так приступим сразу к делу, напишем простую программу на ассемблере. Для этого мы будем использовать команду MOV которая заносит значения в регистр, или с регистра в память. Наша программа будет выводить на экран лишь один символ. который будут помешен в младшую часть регистра DX , то есть в DL . К примеру мы хотим внесли символ # в DL , для этого нужно прописать MOV DL,’#’ . Чтоб вывести на экран символ для мы воспользуемся прерыванием dos под номером 21h. Про прерывания поговорим подробно чуть позже, а сейчас лишь скажу что в этот момент наша программа передает управление операционной системе в нашем случае это DOS. прерывание делается с помощью команды INT . Далее нам нужно создать файл с расширением asm, к примеру first.asm с таким содержимым:

К примеру если у Вас TASM размещен в корне диска С: (то есть прям на диске С:) и файл first.asm находиться в папке по адресу c:tasm\bin\first.asm тогда вызовете консоль (нажмите wint и R одновременно, в появившейся строке напишите CMD или нажмите Пуск а затем выполнить, и также наберите CMD ). Затем в консоли введите cd C:\tasm\bin\ таким образом мы окажемся в папке C:\tasm\bin\ как видно на рисунке ниже.

Далее с помощью TASM собираем наш код. tasm first.asm , если все сделали верно то должно вывести сообщение что ошибок и предупреждений нет и файл успешно собран :

Затем у нас получился объектный файл который нужно еще будет собрать с помощью линкера в исполняемый файл exe . В пакет Turbo assembler входит также и линкер TLINK. Чтоб собрать нам нужно линковщику лишь указать имя объектного файла( tlink first.obj)

Мои поздравления! Теперь Вы собрали программу на ассемблере за пару простых шагов. Для того чтоб запустить ее просто введите в консоле имя программы и результат ее действия вы увидите на экране. Вот как это сделал я:

Собрать ассемблер программу онлайн

Так же возможно собрать код языка ассемблер онлайн , прямо в вашем браузере. Для этого нужно перейти по ссылке на виртуальную машину VM-DOS которая работает прям в браузере. Подождать когда она погрузиться и можно приступать к работе с ней.

1. С начало нужно набрать код. Для этого мы воспользуемся редактором EDITV, и укажем ему имя файла который мы хотим создать таким образом: editv first.asm

Затем мы откроется редактор.

И затем в редакторе набираем код программы который мы набирали раньше.

Далее сохраняем клавишей F2 и выходим с редактора Alt+X. Затем с помощью TASM собираем наш кода ассемблера. Так как делали раннее (tasm first.asm). И если все верно то должен получится объектный файл.

Далее собираем объектный файл в исходный с помощью программы LINK. Для это го наберем link first.obj . На заданные вопросы ничего не отвечаем просто жмем энтер. Затем у нас получиться исходный файл который вы уже знаете как выполнить.

Желаю успехов Вам. По все вопросом пишите на почту или на форум.

Asembler — Составить программу (на асемблере), которая…

Справочная система по языку Assembler

Структура программы на ассемблере


  • Синтаксис ассемблера
  • Стандартные директивы сегментации
  • Упрощенные директивы сегментации

Программа на ассемблере представляет собой совокупность блоков памяти, называемых сегментами памяти. Программа может состоять из одного или нескольких таких блоков-сегментов. Каждый сегмент содержит совокупность предложений языка, каждое из которых занимает отдельную строку кода программы.

Предложения ассемблера бывают четырех типов:

  • команды или инструкции, представляющие собой символические аналоги машинных команд.
    В процессе трансляции инструкции ассемблера преобразуются в соответствующие команды системы команд микропроцессора;
  • макрокоманды — оформляемые определенным образом предложения текста программы, замещаемые во время трансляции другими предложениями;
  • директивы, являющиеся указанием транслятору ассемблера на выполнение некоторых действий. У директив нет аналогов в машинном представлении;
  • строки комментариев, содержащие любые символы, в том числе и буквы русского алфавита. Комментарии игнорируются транслятором.

Синтаксис ассемблера

Рис. 1. Формат предложения ассемблера

Рис. 2. Формат директив

Рис. 3. Формат команд и макрокоманд

На этих рисунках:

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

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

Допустимыми символами при написании текста программ являются:

  1. все латинские буквы: A—Z , a—z . При этом заглавные и строчные буквы считаются эквивалентными;
  2. цифры от 0 до 9 ;
  3. знаки ? , @ , $ , _ , & ;
  4. разделители , . [ ] ( ) < >+ / * % ! ‘ » ? \ = # ^ .

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

Лексемами являются:

  • идентификаторы — последовательности допустимых символов, использующиеся для обозначения таких объектов программы, как коды операций, имена переменных и названия меток. Правило записи идентификаторов заключается в следующем: идентификатор может состоять из одного или нескольких символов. В качестве символов можно использовать буквы латинского алфавита, цифры и некоторые специальные знаки — _, ?, $, @. Идентификатор не может начинаться символом цифры. Длина идентификатора может быть до 255 символов, хотя транслятор воспринимает лишь первые 32, а остальные игнорирует. Регулировать длину возможных идентификаторов можно с использованием опции командной строки mv . Кроме этого существует возможность указать транслятору на то, чтобы он различал прописные и строчные буквы либо игнорировал их различие (что и делается по умолчанию). Для этого применяются опции командной строки /mu, /ml, /mx ;
  • цепочки символов — последовательности символов, заключенные в одинарные или двойные кавычки;
  • целые числа в одной из следующих систем счисления: двоичной, десятичной, шестнадцатеричной. Отождествление чисел при записи их в программах на ассемблере производится по определенным правилам:
    • Десятичные числа не требуют для своего отождествления указания каких-либо дополнительных символов, например 25 или 139.
    • Для отождествления в исходном тексте программы двоичных чисел необходимо после записи нулей и единиц, входящих в их состав, поставить латинское “ b ”, например 10010101 b .
    • Шестнадцатеричные числа имеют больше условностей при своей записи:
      • Во-первых, они состоят из цифр 0. 9 , строчных и прописных букв латинского алфавита a , b , c , d , e , f или A , B , C , D , E , F .
      • Во-вторых, у транслятора могут возникнуть трудности с распознаванием шестнадцатеричных чисел из-за того, что они могут состоять как из одних цифр 0. 9 (например 190845), так и начинаться с буквы латинского алфавита (например ef15 ). Для того чтобы «объяснить» транслятору, что данная лексема не является десятичным числом или идентификатором, программист должен специальным образом выделять шестнадцатеричное число. Для этого на конце последовательности шестнадцатеричных цифр, составляющих шестнадцатеричное число, записывают латинскую букву “ h ”. Это обязательное условие. Если шестнадцатеричное число начинается с буквы, то перед ним записывается ведущий ноль: 0 ef15 h .

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

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

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


Возможно провести следующую классификацию операндов:

  • постоянные, или непосредственные, операнды
  • адресные операнды
  • перемещаемые операнды
  • счетчик адреса
  • регистровый операнд
  • базовый и индексный операнды
  • структурные операнды
  • Записи

Рассмотрим подробнее характеристику операндов из приведенной классификации:

  • Постоянные или непосредственные операнды — число, строка, имя или выражение, имеющие некоторое фиксированное значение. Имя не должно быть перемещаемым, то есть зависеть от адреса загрузки программы в память. К примеру, оно может быть определено операторами equ или = . В данном фрагменте определяются две константы, которые затем используются в качестве непосредственных операндов в командах пересылки mov и сложения add.
  • Адресные операнды — задают физическое расположение операнда в памяти с помощью указания двух составляющих адреса: сегмента и смещения (рис. 4).

    Рис. 4. Синтаксис описания адресных операндов

    К примеру: Здесь третья команда mov имеет адресный операнд.
    Перемещаемые операнды — любые символьные имена, представляющие некоторые адреса памяти. Эти адреса могут обозначать местоположение в памяти некоторых инструкции (если операнд — метка) или данных (если операнд — имя области памяти в сегменте данных).
    Перемещаемые операнды отличаются от адресных тем, что они не привязаны к конкретному адресу физической памяти. Сегментная составляющая адреса перемещаемого операнда неизвестна и будет определена после загрузки программы в память для выполнения.

    К примеру: В этом фрагменте mas_w — символьное имя, значением которого является начальный адрес области памяти размером 25 слов. Полный физический адрес этой области памяти будет известен только после загрузки программы в память для выполнения.

  • Счетчик адреса — специфический вид операнда. Он обозначается знаком $ .
    Специфика этого операнда в том, что когда транслятор ассемблера встречает в исходной программе этот символ, то он подставляет вместо него текущее значение счетчика адреса. Значение счетчика адреса, или, как его иногда называют, счетчика размещения, представляет собой смещение текущей машинной команды относительно начала сегмента кода.
    В формате листинга счетчику адреса соответствует вторая или третья колонка (в зависимости от того, присутствует или нет в листинге колонка с уровнем вложенности). Если взять в качестве пример любой листинг, то видно, что при обработке транслятором очередной команды ассемблера счетчик адреса увеличивается на длину сформированной машинной команды. Важно правильно понимать этот момент.
    К примеру, обработка директив ассемблера не влечет за собой изменения счетчика. Директивы, в отличие от команд ассемблера, — это лишь указания транслятору на выполнение определенных действий по формированию машинного представления программы, и для них транслятором не генерируется никаких конструкций в памяти. В качестве примера использования в команде значения счетчика адреса можно привести следующий: При использовании подобного выражения для перехода не забывайте о длине самой команды, в которой это выражение используется, так как значение счетчика адреса соответствует смещению в сегменте команд данной, а не следующей за ней команды. В нашем примере команда jmp занимает 2 байта. Но будьте осторожны, длина команды зависит от того, какие в ней используются операнды. Команда с регистровыми операндами будет короче команды, один из операндов которой расположен в памяти. В большинстве случаев эту информацию можно получить, зная формат машинной команды и анализируя колонку листинга с объектным кодом команды.
  • Регистровый операнд — это просто имя регистра. В программе на ассемблере можно использовать имена всех регистров общего назначения и большинства системных регистров.
  • Базовый и индексный операнды. Этот тип операндов используется для реализации косвенной базовой, косвенной индексной адресации или их комбинаций и расширений.
  • Структурные операнды используются для доступа к конкретному элементу сложного типа данных, называемого структурой.
  • Записи (аналогично структурному типу) используются для доступа к битовому полю некоторой записи.

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

Результатом вычисления выражения может быть адрес некоторой ячейки памяти или некоторое константное (абсолютное) значение.

В табл. 2 приведены поддерживаемые языком ассемблера операторы и перечислены их приоритеты. Дадим краткую характеристику операторов:

  • Арифметические операторы. К ним относятся:
    • унарные “ + ” и “ – ”;
    • бинарные “ + ” и “ – ”;
    • умножения “ * ”;
    • целочисленного деления “ / ”;
    • получения остатка от деления “ mod ”.

    Эти операторы расположены на уровнях приоритета 6, 7, 8 в табл. 2. Например,

    Рис. 5. Синтаксис арифметических операций
    Операторы сдвига выполняют сдвиг выражения на указанное количество разрядов (рис. 6). Например,

    Рис. 6. Синтаксис операторов сдвига
    Операторы сравнения (возвращают значение “истина” или “ложь”) предназначены для формирования логических выражений (см. рис. 7 и табл. 1). Логическое значение “истина” соответствует цифровой единице, а “ложь” — нулю. Например, В этом примере если значение tab_size больше или равно 50, то результат в al равен 0ffh, а если tab_size меньше 50, то al равно 00h. Команда cmp сравнивает значение al с нулем и устанавливает соответствующие флаги в flags/eflags. Команда je на основе анализа этих флагов передает или не передает управление на метку m1.

    Рис. 7. Синтаксис операторов сравнения

    Таблица 1. Операторы сравнения

    ОператорЗначение
    eqИСТИНА, если выражение_1 равно выражение_2
    neИСТИНА, если выражение_1 не равно выражение_2
    ltИСТИНА, если выражение_1 меньше выражение_2>ИСТИНА, если выражение_1 не равно выражение_2
    leИСТИНА, если выражение_1 меньше или равно выражение_2
    gtИСТИНА, если выражение_1 больше выражение_2
    geИСТИНА, если выражение_1 больше или равно выражение_2

    Логические операторы выполняют над выражениями побитовые операции (рис. 8). Выражения должны быть абсолютными, то есть такими, численное значение которых может быть вычислено транслятором. Например:

    Рис. 8. Синтаксис логических операторов
    Индексный оператор [ ] . Не удивляйтесь, но скобки тоже являются оператором, и транслятор их наличие воспринимает как указание сложить значение выражение_1 за этими скобками с выражение_2 , заключенным в скобки (рис. 9). Например,

    Рис. 9. Синтаксис индексного оператора

    Заметим, что в литературе по ассемблеру принято следующее обозначение: когда в тексте речь идет о содержимом регистра, то его название берут в круглые скобки. Мы также будем придерживаться этого обозначения.
    К примеру, в нашем случае запись в комментариях последнего фрагмента программы mas + (si) означает вычисление следующего выражения: значение смещения символического имени mas плюс содержимое регистра si.

    Оператор переопределения типа ptr применяется для переопределения или уточнения типа метки или переменной, определяемых выражением (рис. 10).
    Тип может принимать одно из следующих значений: byte, word, dword, qword, tbyte, near, far . Например, Поясним этот фрагмент программы. Переменная d_wrd имеет тип двойного слова. Что делать, если возникнет необходимость обращения не ко всей переменной, а только к одному из входящих в нее байтов (например, ко второму)? Если попытаться сделать это командой
    mov al,d_wrd+1, то транслятор выдаст сообщение о несовпадении типов операндов. Оператор ptr позволяет непосредственно в команде переопределить тип и выполнить команду.

    Рис. 10. Синтаксис оператора переопределения типа
    Оператор переопределения сегмента : (двоеточие) заставляет вычислять физический адрес относительно конкретно задаваемой сегментной составляющей: “имя сегментного регистра”, “имя сегмента” из соответствующей директивы SEGMENT или “имя группы” (рис. 11). Этот момент очень важен, поэтому поясню его подробнее. При обсуждении сегментации мы говорили о том, что микропроцессор на аппаратном уровне поддерживает три типа сегментов — кода, стека и данных. В чем заключается такая аппаратная поддержка? К примеру, для выборки на выполнение очередной команды микропроцессор должен обязательно посмотреть содержимое сегментного регистра cs и только его. А в этом регистре, как мы знаем, содержится (пока еще не сдвинутый) физический адрес начала сегмента команд. Для получения адреса конкретной команды микропроцессору остается умножить содержимое cs на 16 (что означает сдвиг на четыре разряда) и сложить полученное 20-битное значение с 16-битным содержимым регистра ip. Примерно то же самое происходит и тогда, когда микропроцессор обрабатывает операнды в машинной команде. Если он видит, что операнд — это адрес (эффективный адрес, который является только частью физического адреса), то он знает, в каком сегменте его искать — по умолчанию это сегмент, адрес начала которого записан в сегментном регистре ds.
    А что же с сегментом стека? Посмотрите раздел «Программная модель микропроцессора», там, где мы описывали назначение регистров общего назначения.
    В контексте нашего рассмотрения нас интересуют регистры sp и bp. Если микропроцессор видит в качестве операнда (или его части, если операнд — выражение) один из этих регистров, то по умолчанию он формирует физический адрес операнда используя в качестве его сегментной составляющей содержимое регистра ss. Что подразумевает термин “по умолчанию”? Вспомните “рефлексы”, о которых мы говорили на уроке 1. Это набор микропрограмм в блоке микропрограммного управления, каждая из которых выполняет одну из команд в системе машинных команд микропроцессора. Каждая микропрограмма работает по своему алгоритму. Изменить его, конечно же, нельзя, но можно чуть-чуть подкорректировать. Делается это с помощью необязательного поля префикса машинной команды (см. формат машинной команды). Если мы согласны с тем, как работает команда, то это поле отсутствует. Если же мы хотим внести поправку (если, конечно, она допустима для конкретной команды) в алгоритм работы команды, то необходимо сформировать соответствующий префикс.
    Префикс представляет собой однобайтовую величину, численное значение которой определяет ее назначение. Микропроцессор распознает по указанному значению, что этот байт является префиксом, и дальнейшая работа микропрограммы выполняется с учетом поступившего указания на корректировку ее работы. Сейчас нас интересует один из них — префикс замены (переопределения) сегмента. Его назначение состоит в том, чтобы указать микропроцессору (а по сути, микропрограмме) на то, что мы не хотим использовать сегмент по умолчанию. Возможности для подобного переопределения, конечно, ограничены. Сегмент команд переопределить нельзя, адрес очередной исполняемой команды однозначно определяется парой cs:ip. А вот сегменты стека и данных — можно. Для этого и предназначен оператор “ : ”. Транслятор ассемблера, обрабатывая этот оператор, формирует соответствующий однобайтовый префикс замены сегмента. Например,

    Рис. 11. Синтаксис оператора переопределения сегмента

  • Оператор именования типа структуры . (точка) также заставляет транслятор производить определенные вычисления, если он встречается в выражении.
  • Оператор получения сегментной составляющей адреса выражения seg возвращает физический адрес сегмента для выражения (рис. 12), в качестве которого могут выступать метка, переменная, имя сегмента, имя группы или некоторое символическое имя.

    Рис. 12. Синтаксис оператора получения сегментной составляющей
    Оператор получения смещения выражения offset позволяет получить значение смещения выражения (рис. 13) в байтах относительно начала того сегмента, в котором выражение определено.

    Рис. 13. Синтаксис оператора получения смещения

    Как и в языках высокого уровня, выполнение операторов ассемблера при вычислении выражений осуществляется в соответствии с их приоритетами (см. табл. 2). Операции с одинаковыми приоритетами выполняются последовательно слева направо. Изменение порядка выполнения возможно путем расстановки круглых скобок, которые имеют наивысший приоритет.

    Таблица 2. Операторы и их приоритет

    Оператор Приоритет
    length, size, width, mask, (, ), [, ], 1
    . 2
    : 3
    ptr, offset, seg, type, this 4
    high, low 5
    +, — (унарные) 6
    *, /, mod, shl, shr 7
    +, -, (бинарные) 8
    eq, ne, lt, le, gt, ge 9
    not 10
    and 11
    or, xor 12
    short, type 13

    Директивы сегментации

    При рассмотрении архитектуры микропроцессора мы узнали, что он имеет шесть сегментных регистров, посредством которых может одновременно работать:

    • с одним сегментом кода;
    • с одним сегментом стека;
    • с одним сегментом данных;
    • с тремя дополнительными сегментами данных.

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

    Синтаксическое описание сегмента на ассемблере представляет собой конструкцию, изображенную на рис. 14:

    Рис. 14. Синтаксис описания сегмента

    Важно отметить, что функциональное назначение сегмента несколько шире, чем простое разбиение программы на блоки кода, данных и стека. Сегментация является частью более общего механизма, связанного с концепцией модульного программирования. Она предполагает унификацию оформления объектных модулей, создаваемых компилятором, в том числе с разных языков программирования. Это позволяет объединять программы, написанные на разных языках. Именно для реализации различных вариантов такого объединения и предназначены операнды в директиве SEGMENT .
    Рассмотрим их подробнее.

    • Атрибут выравнивания сегмента (тип выравнивания) сообщает компоновщику о том, что нужно обеспечить размещение начала сегмента на заданной границе. Это важно, поскольку при правильном выравнивании доступ к данным в процессорах i80х86 выполняется быстрее. Допустимые значения этого атрибута следующие:
      • BYTE — выравнивание не выполняется. Сегмент может начинаться с любого адреса памяти;
      • WORD — сегмент начинается по адресу, кратному двум, то есть последний (младший) значащий бит физического адреса равен 0 (выравнивание на границу слова);
      • DWORD — сегмент начинается по адресу, кратному четырем, то есть два последних (младших) значащих бита равны 0 (выравнивание на границу двойного слова);
      • PARA — сегмент начинается по адресу, кратному 16, то есть последняя шестнадцатеричная цифра адреса должна быть 0h (выравнивание на границу параграфа);
      • PAGE — сегмент начинается по адресу, кратному 256, то есть две последние шестнадцатеричные цифры должны быть 00h (выравнивание на границу 256-байтной страницы);
      • MEMPAGE — сегмент начинается по адресу, кратному 4 Кбайт, то есть три последние шестнадцатеричные цифры должны быть 000h (адрес следующей 4-Кбайтной страницы памяти).

      По умолчанию тип выравнивания имеет значение PARA .

  • Атрибут комбинирования сегментов (комбинаторный тип) сообщает компоновщику, как нужно комбинировать сегменты различных модулей, имеющие одно и то же имя. Значениями атрибута комбинирования сегмента могут быть:
    • PRIVATE — сегмент не будет объединяться с другими сегментами с тем же именем вне данного модуля;
    • PUBLIC — заставляет компоновщик соединить все сегменты с одинаковыми именами. Новый объединенный сегмент будет целым и непрерывным. Все адреса (смещения) объектов, а это могут быть, в зависимости от типа сегмента, команды и данные, будут вычисляться относительно начала этого нового сегмента;
    • COMMON — располагает все сегменты с одним и тем же именем по одному адресу. Все сегменты с данным именем будут перекрываться и совместно использовать память. Размер полученного в результате сегмента будет равен размеру самого большого сегмента;
    • AT xxxx — располагает сегмент по абсолютному адресу параграфа (параграф — объем памяти, кратный 16; поэтому последняя шестнадцатеричная цифра адреса параграфа равна 0). Абсолютный адрес параграфа задается выражением xxx. Компоновщик располагает сегмент по заданному адресу памяти (это можно использовать, например, для доступа к видеопамяти или области ПЗУ), учитывая атрибут комбинирования. Физически это означает, что сегмент при загрузке в память будет расположен, начиная с этого абсолютного адреса параграфа, но для доступа к нему в соответствующий сегментный регистр должно быть загружено заданное в атрибуте значение. Все метки и адреса в определенном таким образом сегменте отсчитываются относительно заданного абсолютного адреса;
    • STACK — определение сегмента стека. Заставляет компоновщик соединить все одноименные сегменты и вычислять адреса в этих сегментах относительно регистра ss. Комбинированный тип STACK (стек) аналогичен комбинированному типу PUBLIC, за исключением того, что регистр ss является стандартным сегментным регистром для сегментов стека. Регистр sp устанавливается на конец объединенного сегмента стека. Если не указано ни одного сегмента стека, компоновщик выдаст предупреждение, что стековый сегмент не найден. Если сегмент стека создан, а комбинированный тип STACK не используется, программист должен явно загрузить в регистр ss адрес сегмента (подобно тому, как это делается для регистра ds).

      По умолчанию атрибут комбинирования принимает значение PRIVATE .

  • Атрибут класса сегмента (тип класса) — это заключенная в кавычки строка, помогающая компоновщику определить соответствующий порядок следования сегментов при собирании программы из сегментов нескольких модулей. Компоновщик объединяет вместе в памяти все сегменты с одним и тем же именем класса (имя класса, в общем случае, может быть любым, но лучше, если оно будет отражать функциональное назначение сегмента). Типичным примером использования имени класса является объединение в группу всех сегментов кода программы (обычно для этого используется класс “code”). С помощью механизма типизации класса можно группировать также сегменты инициализированных и неинициализированных данных;
  • Атрибут размера сегмента . Для процессоров i80386 и выше сегменты могут быть 16 или 32-разрядными. Это влияет, прежде всего, на размер сегмента и порядок формирования физического адреса внутри него. Атрибут может принимать следующие значения:
    • USE16 — это означает, что сегмент допускает 16-разрядную адресацию. При формировании физического адреса может использоваться только 16-разрядное смещение. Соответственно, такой сегмент может содержать до 64 Кбайт кода или данных;
    • USE32 — сегмент будет 32-разрядным. При формирования физического адреса может использоваться 32-разрядное смещение. Поэтому такой сегмент может содержать до 4 Гбайт кода или данных.

Все сегменты сами по себе равноправны, так как директивы SEGMENT и ENDS не содержат информации о функциональном назначении сегментов. Для того чтобы использовать их как сегменты кода, данных или стека, необходимо предварительно сообщить транслятору об этом, для чего используют специальную директиву ASSUME , имеющую формат, показанный на рис. 15. Эта директива сообщает транслятору о том, какой сегмент к какому сегментному регистру привязан. В свою очередь, это позволит транслятору корректно связывать символические имена, определенные в сегментах. Привязка сегментов к сегментным регистрам осуществляется с помощью операндов этой директивы, в которых имя_сегмента должно быть именем сегмента, определенным в исходном тексте программы директивой SEGMENT или ключевым словом nothing . Если в качестве операнда используется только ключевое слово nothing , то предшествующие назначения сегментных регистров аннулируются, причем сразу для всех шести сегментных регистров. Но ключевое слово nothing можно использовать вместо аргумента имя сегмента; в этом случае будет выборочно разрываться связь между сегментом с именем имя сегмента и соответствующим сегментным регистром (см. рис. 15).

Рис. 15. Директива ASSUME

На уроке 3 мы рассматривали пример программы с директивами сегментации. Эти директивы изначально использовались для оформления программы в трансляторах MASM и TASM. Поэтому их называют стандартными директивами сегментации.

Для простых программ, содержащих по одному сегменту для кода, данных и стека, хотелось бы упростить ее описание. Для этого в трансляторы MASM и TASM ввели возможность использования упрощенных директив сегментации. Но здесь возникла проблема, связанная с тем, что необходимо было как-то компенсировать невозможность напрямую управлять размещением и комбинированием сегментов. Для этого совместно с упрощенными директивами сегментации стали использовать директиву указания модели памяти MODEL , которая частично стала управлять размещением сегментов и выполнять функции директивы ASSUME (поэтому при использовании упрощенных директив сегментации директиву ASSUME можно не использовать). Эта директива связывает сегменты, которые в случае использования упрощенных директив сегментации имеют предопределенные имена, с сегментными регистрами (хотя явно инициализировать ds все равно придется).

В листинге 1 приведен пример программы с использованием упрощенных директив сегментации:
Синтаксис директивы MODEL показан на рис. 16.

Рис. 16. Синтаксис директивы MODEL

Обязательным параметром директивы MODEL является модель памяти. Этот параметр определяет модель сегментации памяти для программного модуля. Предполагается, что программный модуль может иметь только определенные типы сегментов, которые определяются упомянутыми нами ранее упрощенными директивами описания сегментов. Эти директивы приведены в табл. 3.

Таблица 3. Упрощенные директивы определения сегмента

Формат директивы
(режим MASM)
Формат директивы
(режим IDEAL)
Назначение
.CODE [имя] CODESEG[имя] Начало или продолжение сегмента кода
.DATA DATASEG Начало или продолжение сегмента инициализированных данных. Также используется для определения данных типа near
.CONST CONST Начало или продолжение сегмента постоянных данных (констант) модуля
.DATA? UDATASEG Начало или продолжение сегмента неинициализированных данных. Также используется для определения данных типа near
.STACK [размер] STACK [размер] Начало или продолжение сегмента стека модуля. Параметр [размер] задает размер стека
.FARDATA [имя] FARDATA [имя] Начало или продолжение сегмента инициализированных данных типа far
.FARDATA? [имя] UFARDATA [имя] Начало или продолжение сегмента неинициализированных данных типа far
Наличие в некоторых директивах параметра [имя] говорит о том, что возможно определение нескольких сегментов этого типа. С другой стороны, наличие нескольких видов сегментов данных обусловлено требованием обеспечить совместимость с некоторыми компиляторами языков высокого уровня, которые создают разные сегменты данных для инициализированных и неинициализированных данных, а также констант.

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

Таблица 4. Идентификаторы, создаваемые директивой MODEL

Имя идентификатора Значение переменной
@code Физический адрес сегмента кода
@data Физический адрес сегмента данных типа near
@fardata Физический адрес сегмента данных типа far
@fardata? Физический адрес сегмента неинициализированных данных типа far
@curseg Физический адрес сегмента неинициализированных данных типа far
@stack Физический адрес сегмента стека
Если вы посмотрите на текст листинга 1, то увидите пример использования одного из этих идентификаторов. Это @data ; с его помощью мы получили значение физического адреса сегмента данных нашей программы.

Теперь можно закончить обсуждение директивы MODEL . Операнды директивы MODEL используют для задания модели памяти, которая определяет набор сегментов программы, размеры сегментов данных и кода, способ связывания сегментов и сегментных регистров. В табл. 5 приведены некоторые значения параметра модель памяти директивы MODEL .

Таблица 5. Модели памяти

Модель Тип кода Тип данных Назначение модели
TINY near near Код и данные объединены в одну группу с именем DGROUP.
Используется для создания программ формата .com.
SMALL near near Код занимает один сегмент, данные объединены в одну группу с именем DGROUP.
Эту модель обычно используют для большинства программ на ассемблере
MEDIUM far near Код занимает несколько сегментов, по одному на каждый объединяемый программный модуль.
Все ссылки на передачу управления — типа far.
Данные объединены в одной группе; все ссылки на них — типа near
COMPACT near far Код в одном сегменте;
ссылка на данные — типа far
LARGE far far Код в нескольких сегментах, по одному на каждый объединяемый программный модуль
Параметр модификатор директивы MODEL позволяет уточнить некоторые особенности использования выбранной модели памяти (табл. 6).
Таблица 6. Модификаторы модели памяти

Значение модификатора Назначение
use16 Сегменты выбранной модели используются как 16-битные (если соответствующей директивой указан процессор i80386 или i80486)
use32 Сегменты выбранной модели используются как 32-битные (если соответствующей директивой указан процессор i80386 или i80486)
dos Программа будет работать в MS-DOS
Необязательные параметры язык и модификатор языка определяют некоторые особенности вызова процедур. Необходимость в использовании этих параметров появляется при написании и связывании программ на различных языках программирования.

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

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

Цукерберг рекомендует:  Работа c графической библиотекой PHP GD library. Часть первая.
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих