Основные понятия языка ассемблера



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

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

- операционной - определяющей, "что делать";

- операндной - определяющей объекты обработки, "с чем делать".

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

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

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

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

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

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

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

Count     db 1       ; Имя, директива, один операнд

                        mov eax,0 ; Команда, два операнда

                        cbw        ; Команда

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

- все буквы латинского алфавита;

- цифры от 0 до 9;

- спецсимволы: _, @, $, ?.

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

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

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

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

Операнд – объект, над которым выполняется машинная команда или оператор языка программирования.

Команда может иметь один-два операнда, или вообще не иметь операндов. Число операндов неявно задается кодом команды.

Примеры:

Нет операндов: ret          ; Вернуться

Один операнд: inc ecx   ; Увеличить ecx

Два операнда: add eax,12 ; Прибавить 12 к eax

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

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

- идентификаторы;

- цепочки символов, заключенных в одинарные или двойные кавычки;

- целые числа в двоичной, восьмеричной, десятичной или шестнадцатеричной системе счисления.

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

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

- Идентификатор может состоять из одного или нескольких символов.

- В качестве символов можно использовать буквы латинского алфавита, цифры и некоторые специальные знаки: _, ?, $, @.

- Идентификатор не может начинаться символом цифры.

- Длина идентификатора может быть до 255 символов.

- Транслятор воспринимает первые 32 символа идентификатора, а остальные игнорирует.

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

 

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

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

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

Пример:

.686P

.MODEL FLAT, STDCALL

.DATA

.CODE

START:

RET

END START

 

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

Остальная часть программы относится к работе транслятора.

.686P - разрешены команды защищенного режима Pentium 6 (Pentium II).

Данная директива выбирает поддерживаемый набор команд ассемблера, указывая модель процессора. Буква P, указанная в конце директивы, сообщает транслятору о работе процессора в защищенном режиме.

.MODEL FLAT, - плоская модель памяти. Эта модель памяти используется в операционной системе Windows. STDCALL – используемое соглашение о вызовах процедур.

.DATA - блок программы, содержащий данные. В MS-DOS это называлось сегментом. В Windows для прикладной программы сегменты отсутствуют, точнее, есть один большой плоский сегмент. Такие блоки здесь называются секциями.

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

Данная программа не использует стек, поэтому секция .STACK отсутствует.

.CODE - блок программы, содержащей код.

START - метка. В ассемблере метки играют большую роль, что не скажешь о современных языках высокого уровня.

END START - конец программы и сообщение компилятору, что начинать выполнение программы надо с метки START.

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

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

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

Программирование в Windows основывается на использовании функций API (Application Program Interface, т.е. интерфейс программного приложения). Их количество достигает двух тысяч. Программа для Windows в значительной степени состоит из таких вызовов. Все взаимодействие с внешними устройствами и ресурсами операционной системы будет происходить посредством таких функций.

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

Вызов функций API. В файле помощи любая функция API представлена в виде (например):

int MessageBox (HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

Данная функция выводит на экран окно с сообщением и кнопкой (или кнопками) выхода. Смысл параметров:

hWnd -дескриптор окна, в котором будет появляться окно-сообщение,

lpText - текст, который будет появляться в окне,

lpCaption - текст в заголовке окна,

uType - тип окна, в частности можно определить количество кнопок выхода.

Теперь о типах параметров. Практически все параметры API-функций в действительности 32-битные целые числа: 

HWND — 32-битное целое,

LPCTSTR — 32-битный указатель на строку,

UINT — 32-битное целое. К имени функций часто добавляется суффикс "А" для перехода к более новым версиям функций.

Кроме того, при использовании MASM необходимо также в конце имени добавить @16 – количество байт, которое занимают в стеке переданные аргументы. Для функций Win32 API это число можно определить как количество аргументов n, умноженное на 4 (байта в каждом аргументе): 4*n. Для вызова функции используется команда CALL ассемблера. 

При этом все аргументы функции передаются в нее через стек (команда PUSH). Направление передачи аргументов: СЛЕВА НАПРАВО— СНИЗУ ВВЕРХ. В соответствии с этим, первым будет помещаться в стек аргумент uType.

Таким образом, вызов указанной функции будет выглядеть так:

CALL MessageBoxA@16.

Результат выполнения любой API функции — это, как правило, целое число, которое возвращается в регистре EAX.

Пример:

.686P

.MODEL FLAT, STDCALL

;include C:\masm32\include\user32.inc

MessageBoxA PROTO :DWORD, :DWORD, :DWORD,

:DWORD

.STACK 4096

.DATA

MB_OK EQU 0

STR1 DB "Моя первая программа",0

STR2 DB "Привет всем!",0

HW DD ?

.CODE

START:

INVOKE MessageBoxA, HW,

OFFSET STR2, OFFSET STR1, MB_OK

RET

END START

 

Директива OFFSET представляет собой «смещение в секции», или, переводя в понятия языков высокого уровня, «указатель» начала строки.

Директива EQU подобно #define в языке СИ определяет константу.

Транслятор языка MASM позволяет также упростить вызов функций с использованием макросредства – директивы INVOKE:

INVOKE функция, параметр1, параметр2,…

В этом случае нет необходимости добавлять @16 к вызову функции. Кроме того, параметры записываются точно в том порядке, в котором приведены в описании функции. Макросредствами транслятора параметры помещаются в стек. Для использования директивы INVOKE необходимо иметь описание прототипа функции с использованием директивы PROTO в виде:

MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD

Если в программе используется множество функций Win32 API, целесообразно воспользоваться директивой 

include C:\masm32\include\user32.inc

Функция MessageBoxA вызывается из системной библиотеки Windows user32.dll.

include-файлы MASM с соответствующими названиями библиотек содержат описания прототипов всех функций данной библиотеки.

 

Типы данных в ассемблере

Данные – числа и закодированные символы, используемые в качестве операндов команд.

Основные типы данных в ассемблере

 

Тип Директива Количество байт
Байт DB 1
Слово DW 2
Двойное слово DD 4
8 байт DQ 8
10 байт DT 10

 

Данные, обрабатываемые вычислительной машиной, можно разделить на 4 группы:

- целочисленные;

- вещественные.

- символьные;

- логические;

 

Целочисленные данные.

Целые числа в ассемблере могут быть представлены в 1-байтной, 2-байтной, 4-байтной или 8-байтной форме. Целочисленные данные могут представляться в знаковой и беззнаковой форме.

Беззнаковые целые числа представляются в виде последовательности битов в диапазоне от 0 до 2n-1, где n- количество занимаемых битов.

Знаковые целые числа представляются в диапазоне -2n-1 … +2n-1-1. При этом старший бит данного отводится под знак числа (0 соответствует положительному числу, 1 – отрицательному).

 

Вещественные данные.

Вещественные данные могут быть 4, 8 или 10-байтными и обрабатываются математическим сопроцессором.

 

Логические данные представляют собой бит информации и могут записываться в виде последовательности битов. Каждый бит может принимать значение 0 (ЛОЖЬ) или 1 (ИСТИНА). Логические данные могут начинаться с любой позиции в байте.

 

Символьные данные задаются в кодах и имеют длину, как правило, 1 байт (для кодировки ASCII) или 2 байта (для кодировки Unicode) .

Числа в двоично-десятичном формате

В двоично-десятичном коде представляются беззнаковые целые числа, кодирующие цифры от 0 до 9. Числа в двоично-десятичном формате могут использоваться в одном из двух видов:

- упакованном;

- неупакованном.

В неупакованном виде в каждом байте хранится одна цифра, размещенная в младшей половине байта (биты 3…0).

 

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

 

Числовые константы используются для обозначения арифметических операндов и адресов памяти. Для числовых констант в Ассемблере могут использоваться следующие числовые форматы:

Десятичный формат – допускает использование десятичных цифр от 0 до 9 и обозначается последней буквой d, которую можно не указывать, например, 125 или 125d. Ассемблер сам преобразует значения в десятичном формате в объектный шестнадцатеричный код и записывает байты в обратной последовательности для реализации прямой адресации:

a DB 12

Шестнадцатеричный формат – допускает использование шестнадцатеричных цифр от 0 до F и обозначается последней буквой h, например 7Dh. Так как ассемблер полагает, что с буквы начинаются идентификаторы, то первым символом шестнадцатеричной константы должна быть цифра от 0 до 9. Например, 0Eh.

a DB 0Ch

Двоичный формат – допускает использование цифр 0 и 1 и обозначается последней буквой b. Двоичный формат обычно используется для более четкого представления битовых значений в логических командах (AND, OR, XOR) :

a DB 00001100b

Восьмеричный формат – допускает использование цифр от 0 до 7 и обозначается последней буквой q или o, например, 253q:

a DB 14q

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

Цепочка - массив, имеющий фиксированный набор начальных значений. Примеры инициализации цепочек:

M1 DD 0,1,2,3,4,5,6,7,8,9

M2 DD 0,1,2,3

DD 4,5,6,7

DD 8,9

Каждая из записей выделяет десять последовательных 4-байтных ячеек памяти и записывает в них значения 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

Идентификатор M1 определяет смещение начала этой области в сегменте данных .DATA.

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

Идентификатор Тип Размер DUP (Значение)

Идентификатор - имя массива;

Тип - определяет количество байт, занимаемое одним элементом;

Размер - константа, характеризующая количество элементов в массиве;

Значение - начальное значение элементов.

Например:

a DD 20 DUP (0)

описывает массив a из 20 элементов, начальные значения которых равны 0.

Если необходимо выделить память, но не инициализировать ее, в качестве поля Значение используется знак ?. Например:

b DD 20 DUP(?)

Символьные строки представляют собой набор символов для вывода на экран. Содержимое строки отмечается

- одиночными кавычками '', например, 'строка';

- двойными кавычками "", например "строка".

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

Символьная строка, предназначенная для корректного вывода, должна заканчиваться нуль-символом '\0' с кодом, равным 0:

Str DB 'Привет всем!', 0

Для перевода строки могут использоваться символы

- возврат каретки с кодом 13 (0Dh)

- перевод строки с кодом 10 (0Ah):

Stroka DB "Привет", 13, 10, 0

 

Команды языка ассемблера

Базовая система команд микропроцессора

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

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

Поскольку опкод состоит из 8 бит, может существовать 256 разных опкодов. Ограничение в 256 команд было обойдено, так как некоторые опкоды (0FEh, 0FFh и другие) служат шлюзами к следующим таблицам кодов. В приложении представлен полный список команд микропроцессора x86.

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

После того как программа написана на языке ассемблера, стандартная программа, известная также под названием «ассемблер», преобразует мнемокоды и имена в числа. Таким образом, программа ассемблер переводит на объектный язык программы, составленные на входном языке. 

В процессе этого перевода одна команда языка ассемблер может быть преобразована в 1, 2 или более байт объектного языка в зависимости от конкретной команды.

Базовую систему команд микропроцессора можно условно разделить на несколько групп по функциональному назначению:

·- команды передачи данных;

·- команды работы со стеком;

·- команды ввода-вывода;

·- арифметические команды;

·- логические команды;

·- сдвиговые команды;

·- команды управления флагами;

·- команды прерываний;

·- команды передачи управления;

·- команды поддержки языков высокого уровня;

·- команды синхронизации работы процессора;

·- команды обработки цепочки бит;

·- строковые команды;

Кроме базовой системы команд процессора существуют также команды расширений:

X87 – расширение, содержащее команды математического сопроцес-сора (работа с вещественными числами);

MMX – расширение, содержащее команды для кодирования/деко-дирования потоковых аудио/видео данных;

SSE – расширение включает в себя набор инструкций, который производит операции со скалярными и упакованными типами данных;

SSE2 – модификация SSE, содержит инструкции для потоковой обработки целочисленных данных, что делает это расширение более предпочтительным для целочисленных вычислений, нежели использование набора инструкций MMX, появившегося ранее;

SSE3, SSE4 – содержат дополнительные инструкции расширения SSE.

В таблице приняты следующие обозначения:

r – регистр;

m – ячейка памяти;

c – константа;

8, 16, 32 – размер в битах;

W – запись бита в регистре признаков;

U – значение бита в регистре признаков неизвестно после выполнения операции;

- – значение бита в регистре признаков не изменилось;

+ – значение бита в регистре признаков меняется в соответствии с результатом.

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

1. Нельзя в одной команде оперировать двумя областями памяти одновременно.

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

2. Нельзя оперировать сегментным регистром и значением непосредственно из памяти. Поэтому для выполнения такой операции нужно использовать промежуточный объект. Это может быть регистр общего назначения или стек.

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

mov ax,ds

mov es,ax ; es=ds

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

5. Операнды команды, если это не оговаривается дополнительно в описании команды, должны быть одного размера.

 

Машинные коды для всех возможных сочетаний операторов команды

Описание машинного кода производится в шестнадцатеричном виде. При описании машинного кода используются следующие обозначения:

/цифра (от 0 до 7) показывает, что байт mod r/m кода операции использует только операнд r/m.

Поле reg содержит цифры которые обеспечивает расширение опткода;

/r — показывает, что байт mod r/m команды содержит как регистровый операнд, так и операнд r/m;

cb, cw, cd, cp, cq - одно-, двух-, четырех, шести-, восьмибайтное значение, следующее за полем кода операции и используемое для смещения в сегменте кода и возможно задает новое значение для регистра;

ib, iw, id, iq - одно-, двух-, четырех-, восьмибайтное непосредственное значение (число). Следует за опкод, Mod R/M или SIB (если таковые есть), при этом код операции определяет, является ли непосредственный операнд знаковым значением, а все слова, двойные и четверные слова приводятся в порядке «младший байт по младшему адресу»;

+rb, +rw, +rd, +rq, +i - код регистра от 0 до 7. Добавляется к байту слева от знака «+». В результате получается окончательный опкод.

 

Команды передачи данных

Основной командой передачи данных является команда MOV, осуществляющая операцию присваивания:

MOV приемник, источник

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

Команда обеспечивает копирование значения из операнда-источника в операнд- приемник.

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

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

mov eax, ebx

mov result, ax ; result – 16-битная переменная

mov ecx, count ; count - 32-битная переменная

mov ah, 64 ; 64 – непосредственный операнд

mov var1, 100

Бывают случаи, когда не ясен размер операнда. Для такого уточнения можно использовать оператор переопределения типа PTR. Типы могут быть byte, word, dword и другие. Например, для получения младшего байта 32-битной переменной x можно использовать команду

mov al,byte ptr x.

Команда Операнды Пояснение Описание
MOV r(m)8,r8 r(m)16,r16 r(m)32,r32 r8,r(m)8 r16,r(m)16 r32,r(m)32 r(m)8,c8 r(m)16,c16 r(m)32,c32 r(m)8=r8 r(m)16=r16 r(m)32=r32 r8=r(m)8 r16=r(m)16 r32=r(m)32 r(m)8=с8 r(m)16=с16 r(m)32=с32 Пересылка операндов
XCHG r(m)8, r8 r8, r(m)8 r(m)16,r16 r16, r(m)16 r(m)32, r32 r32, r(m)32 r(m)8 ↔r8 r8 ↔r(m)8 r(m)16↔r16 r16 ↔r(m)16 r(m)32↔r32 r32 ↔r(m)32 Обмен операндов
BSWAP r32 TEMP ← r32 r32[7..0]←TEMP[31..24] r32[15..8]←TEMP[23..16] r32[23..16]←TEMP[15..8] r32[31..24]←TEMP[7..0] Перестановка байтов из порядка "младший – старший" в порядок "старший – младший"
MOVSX r16, r(m)8 r32, r(m)8 r32, r(m)16 r16,r(m)8 DW ← DB r32,r(m)8 DD ← DB r32,r(m)16 DD ← DW Пересылка с расширением формата и дублированием знакового бита
MOVZX r16,r(m)8 r32,r/m8 r32,r/m16 r16,r(m)8 DW ← DB r32,r(m)8 DD ← DB r32,r(m)16 DD ← DW Пересылка с расширением формата и дублированием нулевого бита
XLATXLATB m8 AL=DS:[(E)BX+unsigned AL] Загрузить в AL байт из таблицы в сегменте данных, на начало которой указывает EBX (ВХ); начальное значение AL играет роль смещения
LEA r16, m r32, m r16=offset m r32=offset m Загрузка эффективного адреса
LDS r16,m16 r32,m16 DS:r=offset m Загрузить пару регистров из памяти

LSS

SS:r=offset m

LES

ES:r=offset m

LFS

FS:r=offset m

LGS

GS:r=offset m

 


Дата добавления: 2018-06-27; просмотров: 1298; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!