Язык ассемблера. Особенности, макросы, формат операторов, псевдокоманды. Часть 1.

Пример HTML-страницы

Язык ассемблераВведение.

Язык, на котором написана исходная программа, называется вход­ным языком, а язык, на который она переводится для исполнения процессо­ром, — выходным языком. Процесс преобразования входного языка в выходной язык называется трансляцией. Так как процессоры способны выполнять програм­мы на машинном языке двоичных кодов, который не используется для програм­мирования, то необходима трансляция всех исходных программ. Известны два способа трансляции: компиляция и интерпретация.

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

При интерпретации каждая строка текста исходной программы анализируется (интерпретируется) и указанная в ней команда сразу выполняется. Реализация такого способа возлагается на программу–интерпретатор. Интерпретация занима­ет длительное время. Для повышения ее эффективности вместо обработки каждой строки интерпретатор предварительно осуществляет преобразование всех ко­мандных строк в символы (

token

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

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

Особенности языка.

Основные особенности ассемблера:

● вместо двоичных кодов в языке используются символьные имена — мнемо­ника. Например, для команды сложения (

add

) используется мнемоника

ADD

, вычитания (

subtract

) —

SUB,

умножения (

multiply

) —

MUL

, деления (

divide

) —

DIV

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

каждое высказывание соответствует одной машинной команде (коду), т. е. су­ществует взаимно однозначное соответствие между машинными командами и операторами в программе на языке ассемблера;

● язык обеспечивает доступ ко всем объектам и командам. Языки высокого уровня такой способностью не обладают. Например, язык ассемблера позво­ляет выполнить проверку бита регистра флагов, а язык высокого уровня (на­пример,

Java

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

● язык ассемблера не является универсальным языком. Для каждой определен­ной группы микропроцессоров имеется свой ассемблер. Языки высокого уровня лишены этого недостатка.

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

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

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

В большинстве программ лишь небольшой процент всего кода отвечает за большой процент времени выполнения программы. Обычно 1% программы отве­чает за 50% времени выполнения, а 10% программы отвечает за 90% времени выполнения. Поэтому для написания конкретной программы в реальных ус­ловиях используется как ассемблер, так и один из языков высокого уровня.

Формат оператора в языке ассемблера.

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

Поле метки.

Для поля метки отводится колонка 1. Метка является символи­ческим именем, или идентификатором, адреса памяти. Она необходима для того, чтобы можно было:

● совершить условный или безусловный переход к команде;

● получить доступ к месту, где хранятся данные.

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

В некоторых версиях языка ассемблера двоеточия ставятся только после ме­ток команд, но не после меток данных, а длина метки может быть ограничена 6 или 8 символами.

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

Поле кода операции.

Это поле содержит мнемокод команды или псевдо­команды (см. ниже). Мнемокод команд выбирается разработчиками языка. В язы­ке ассемблера

SPARC

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

LD

(

Load

), а для сохранения содержимого регистра в памяти — мнемоника

ST

(

Storage

). В языках ассемблера

Intel

и

Motorola

для обеих операций можно ис­пользовать одно имя, соответственно

MOV

,

MOVE

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

SPARC

.

Мнемоника регистров также зависит от версии ассемблера (табл. 5.2.1). табл. 5.2.1

Поле операнда.

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

INTEL

:

● числовые данные,

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

Q

, Н,

D

— соответственно двоичная, восьмеричная, шестнадцатеричная, десятичная системы счисления (

D

можно не записывать). Если первой цифрой шестнадцатеричного числа являются А, В, С,

D

, Е,

F

, то впереди добавляется незначащий 0 (нуль);

● коды внутренних регистров микропроцессора и ячейки памяти

М (источников или приемников информации) в виде букв А, В, С,

D

, Е, Н,

L

, М или их адреса в любой системе счисления (например, 10В — адрес регистра

D

в двоичной системе);

● идентификаторы,

для регистровых пар ВС,

DE

,

HL

— первые буквы В,

D

, Н; для пары аккумулятора и регистра признаков —

PSW

; для счетчика команд —

PC

;для указателя стека —

SP

;

● метки, указывающие адреса операндов или следующих команд в условных

(при выполнении условия) и безусловных переходах. Например, операнд М1 в команде

JMP

Ml

означает необходимость безусловного перехода к коман­де, адрес которой в поле метки отмечен идентификатором М1;

● выражения,

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

Intel

выбрали

DW

(

Define

Word

— определить слово), а позднее ввели альтернативный вариант .

WORD

,

который с самого начала был в языке для процессоров

SPARC

. В версии языка

Motorola

используется

DC

(

Define

Constant

— определить константу).

Процессоры обрабатывают операнды разной длины. Для ее определения раз­работчики ассемблера приняли разные решения, например:

● в

Pentium

II регистры разной длины имеют разные названия: ЕАХ — для раз­мещения 32–битных операндов (тип

long

); АХ — для 16–битных (тип

word

), а

AL

и АН — для 8–битных (тип

byte);

● для процессоров

Motorola

к каждому коду операции прибавляются суффиксы: суффикс

«.L»

для типа

long;

суффикс

«.W»

— для типа

word

; суффикс «.В» для типа

byte;

● в

SPARC

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

halfword

) и слова в 64–битный ре­гистр используются коды операций

LDSB

,

LDSH

и

LDSW

соответственно.

Поле комментариев.

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

● точка с запятой (;) в языках для процессоров фирмы

INTEL

;

● восклицательный знак (!) в языках для

SPARC

.

Каждая отдельная строка, отведенная под комментарий, предваряется начальным символом.

Псевдокоманды (директивы).

В языке ассемблера можно выделить два ос­новных вида команд:

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

псевдокоманды, или директивы, предназначенные для обслуживания процес­са трансляции программы на язык кодовых комбинаций. В качестве примера в табл. 5.2.2 приведены некоторые псевдокоманды из ас­семблера

MASM

для семейства

Intel

. табл. 5.2.2

Макросы.

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

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

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

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

Макрос, или макрокоманда, характеризуется тремя аспектами: макроопреде­лением, макрообращением и макрорасширением.

Макроопределение

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

Макроопределение имеет следующую структуру:

NAME

MACRO

Список выражений ; Макроопределение

… …

ENDM

В приведенной структуре макроопределения можно выделить три части:

● заголовок

макроса, включающий в себя имя

NAME

, псевдокоманду

MACRO

и набор параметров;

● отмеченное точками тело макроса;

● команда

ENDM

окончания

макроопределения.

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

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

NAME

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

Когда в процессе компиляции ассемблер встречает макроопределение, он со­храняет его в таблице макроопределений. При последующих появлениях в про­грамме имени (

NAME

) макроса ассемблер замещает его телом макроса.

Использование имени макроса в качестве кода операции называется макро–обращением (макровызовом), а его замещение телом макроса — макрорасши­рением.

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

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

Процесс ассемблирования осуществляется в два прохода:

● на первом проходе сохраняются все макроопределения, а макровызовы расширяются. При этом исходная программа считывается и преобразуется в программу, в которой удалены все макроопределения, а каждый макро­вызов замещен телом макроса;

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

Макросы с параметрами.

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

● с фактическими параметрами, которые помещаются в поле операндов макро-обращения;

● с формальными параметрами. В процессе расширения макроса каждый фор­мальный параметр, появляющийся в теле макроса, замещается соответству­ющим фактическим параметром.

Пример

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

В программе 1 приведено две похожих последовательности команд, отличающихся тем, что пер­вая из них меняет местами Р и

Q

, а вторая

R

и

S

.

В программе 2 включен макрос с двумя формальными параметрами Р1 и Р2. Во время расшире­ния макроса каждый символ Р1 внутри тела макроса замещается первым фактическим параметром (Р,

R

), а символ Р2 замещается вторым фактическим параметром (

Q

,

S

) из программы № 1. В макровызо­ве

CHANGE

P

,

Q

и

CHANGE

R

,

S

программы 2 обозначено: Р,

R

— первый фактический параметр,

a

Q

,

S

— второй фактический параметр.

Программа 1

Программа 2

MOV

EAX,P

CHANGE

MACRO

P1,

P2

MOV EBX,Q MOV EAX,Pl

MOV Q,EAX MOV EBX,P2

MOV P,EBX MOV P2,EAX

MOV

P1,EBX

ENDM

MOV EAX,R

MOV

EBX,S

CHANGE

P,Q

MOV S,EAX

MOV R,EBX

CHANGE

R,S

Расширенные возможности.

Рассмотрим некоторые расширенные возмож­ности языка

MASM

.

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

MASM

метка объявляется локальной (

LOCAL

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

Язык

MASM

позволяет определять макросы внутри других макросов. Такая расширенная возможность весьма полезна в сочетании с условной компоновкой программы. Рассмотрим

пример

:

Ml MACRO

IF WORDSIZE GT 16 M2 MACRO

ENDM

ELSE

M

2

MACRO

ENDM

END

IF

ENDM

Макрос М2 может быть определен в обеих частях оператора

IF

. Однако опре­деление зависит от того, на каком процессоре ассемблируется программа: на 16–битном или на 32–битном. Если М1 не вызывается, то макрос М2 вообще не будет определен.

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

Об использовании макросредств в ассемблере.

При использовании мак­росов ассемблер должен уметь выполнять две функции: сохранять макроопреде­ления и расширять макровызовы.

Сохранение макроопределений.

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

При встрече с макросом в процессе ассемблирования создается:

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

● список формальных параметров.

Затем считывается и сохраняется в таблице макроопределений тело макроса, представляющее собой просто цепочку символов. Формальные параметры, встречающиеся в теле цикла, помечаются специальным символом.

Внутреннее представление макроса

CHANGE

из приведенного выше примера для программы 2 (стр. 244) имеет вид:

MOV EAX,&P1; MOV EBX,&P2; MOV &P2EAX;MOV &

Р

1,

ЕВХ

;

где в качестве символа возврата каретки используется точка с запятой, а в каче­стве символа формального параметра — амперсант &.

Расширение макровызовов.

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

Несмотря на то, что существует много версий ассемблера, процессы ассемб­лирования имеют общие черты и во многом сходны. Ниже рассматривается рабо­та двухпроходного ассемблера.

Двухпроходной ассемблер.

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

● считать оператор;

● транслировать его на машинный язык;

● перенести полученный машинный код в файл, а соответствующую часть лис­тинга — в другой файл;

● повторять перечисленные процедуры до тех пор, пока вся программа не бу­дет оттранслирована.

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

на первом проходе следует собрать и сохранить в таблице все определения символов (в том числе меток), а на втором проходе — выполнить чтение и ас­семблирование каждого оператора. Такой способ относительно прост, однако второй проход по исходной программе требует дополнительных временных затрат на операции ввода–вывода;

● на первом проходе следует преобразовать программу в промежуточную фор­му и сохранить ее в таблице, а второй проход выполнить не по исходной про­грамме, а по таблице. Такой способ ассемблирования позволяет сэкономить время, так как на втором проходе не выполняются операции ввода–вывода.

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

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

Первый проход.

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

МЕТКА:

;Метка

BUFSIZE

EQU

8192

;Значение — размер буфера табл. 5.2.3

Придавая значения символьным именам в поле метки команд, ассемблер по сути дела задает адреса, которые будет иметь каждая команда во время выпол­нения программы. Для этого ассемблер во время процесса ассемблирования со­храняет счетчик адреса команд (

Instruction

Location

Counter

ILC

) как специаль­ную переменную. В начале первого прохода значение специальной переменной устанавливается на 0 и увеличивается после каждой обработанной команды на длину этой команды. В качестве примера в табл. 5.2.3 приведен фрагмент про­граммы с указанием длины команд и значений счетчика. При первом проходе формируются таблицы символьных имен, директив и кодов операций, а при необ­ходимости литеральная таблица. Литерал — это константа, для которой ассемб­лер автоматически резервирует память. Сразу же отметим, что современные процессоры содержат команды с непосредственными адресами, поэтому их ас­семблеры не поддерживают литералы. табл. 5.2.4

Таблица символьных имен

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

● длину поля данных, связанного с символом;

● биты перераспределения памяти (которые показывают, изменяется ли зна­чение символа, если программа загружается не в том адресе, в котором предполагал ассемблер);

● сведения о том, можно ли получить доступ к символу извне процедуры.

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

EQU

).

Таблица директив.

В этой таблице приводятся все директивы, или псевдо­команды, которые встречаются при ассемблировании программы.

Таблица кодов операций.

Для каждого кода операции в таблице преду­смотрены отдельные графы: обозначение кода операции, операнд 1, операнд 2, 16–ричное значение кода операции, длина команды и тип команды (табл. 5.2.5). Коды операций делятся на группы в зависимости от числа и вида операндов. Тип команды определяет номер группы и задает процедуру, которая вызывается для обработки всех команд данной группы. рис. 5.2.5

Второй проход.

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

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

Исходная программа может содержать ошибки, например:

приведенный символ не определен или определен более одного раза;

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

● отсутствует оператор

END

и др.

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

Вторая частьстатьи посвященной языку ассемблер.


Понравилась статья? Поделиться с друзьями:
Все об энергетике, электротехнике и электронике
Комментарии: 1
  1. Александр

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

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

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: