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

Узнать больше...

Главная страница Шпаргалки
Решение задач Эксклюзивные фото по химии
Сочинения (более 4000) Юмор из жизни учащихся
Вернуться в раздел "Учебные материалы"

Информатика и Ассемблер

 

ГЛАВА 2

ОБЩИЕ СВЕДЕНИЯ О ЯЗЫКЕ АССЕМБЛЕРА

2.1 Основные понятия

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

2.1.1. Идентификаторы

Идентификаторы - символьные обозначения объектов программы: переменных, меток, названий операций и т.п. И. - последовательность из латинских букв (больших и малых), цифр и знаков ?.@_$. Ограничения на И. ¾ длина может быть любой, но значащие - лишь первые 31 символ, ¾ не должен начинаться с цифры ¾ точка - только первым символом,¾ одноименные большие и малые буквы - эквивалентны. Русские не допустимы. Делятся на служебные (зарезервированные - reserved) слова и имена. Первые определены заранее и имеют определенный смысл, например обозначают регистры (ах, cs) названия команд (mov, xor), все остальные И. называются именами и ими можно пользоваться по усмотрению.

2.1.2. Запись целых чисел в ассемблере

Могут быть записаны в десятичной, двоичной, щестнадцатиричной и восьмиричной системах счисления. Десятичные числа записываются как обычно, или с добавлением буквы d. После двоичного числа добавляется b, после щестнадцатиричного - h, а после восьмеричного о или q. Если hex начинается с буквы (цифры, обозначаемой буквой), то перед ним обязательно должен стоять ноль.

2.1.2. Символьные данные

Символы заключаются в кавычки или апострофы ²А² или ¢А¢ не ²Ы¢. Строки (последовательности символов) также ¢перспектива¢. Заметим ¾ в качестве символов можно использовать буквы русского алфавита,¾ в строках одноименные большие и малые буквы не отождествляются, ¾ кавычки: ‘don’’t’ «don.

2.1.3. Предложения

Программа на ассемблере состоит из предложений, каждое из которых записывается на отдельной строке. <предложение>... Переносить предложение на следующую строку или записывать два предложения на одной строке нельзя. Если более 131 символа, то 132-й и далее - игнорируются. Правила расстановки пробелов:

¾ пробел обязателен между стоящими рядом идентификаторами и/или числами,

¾ внутри идентификаторов и чисел пробелы недопустимы,

¾ там где допустим один пробел, допустимо и несколько пробелов подряд. Предложения в ассемблере делятся на три группы по смыслу: комментарии, команды, директивы (приказы ассемблеру). Рассмотрим.

комментарии. Не отражаются на ходе выполнения программы, игнорируются компилятором и предназначены для людей. Могут быть любые символы. Комментарием считается любая строка, начинающаяся со знака ; или пустая строка. Возможен многострочный комментарий COMMENT <маркер> <текст>. В качестве комментария берется первый за словом COMMENT символ, отличный то пробела COMMENT * бред* бред.

Команды. Это символьная форма записи машинных команд.

[<метка>:] <мнемокод> [<операнды>] [;<комментарий>]

Метка с двоеточием, а также точка с запятой и комментарий могут отсутствовать.

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

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

Операнды, если они есть отделяются друг от друга запятыми.

Комментарий - после знака ; не влияет на смысл команды, а лишь поясняет ее. Действителен для данной строки.

Регистры указываются своими именами, например:

MOV AX,SI ;оба операнда - регистры

Непосредственные операнды задаются константными выражениями (их значениями являются константы-числа), например:

MOV BH,5 ;5 - непосредственный операнд

MOV DI,SIZE X ;SIZE X (число байтов, занимаемых переменной X) - ;непосредственный операнд

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

MOV X,AH

MOV X[BX][DI],5

MOV [BX],CL

 

2.2. Директивы языка Ассемблер

2.2.1. Общие понятия

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

Синтаксис директив следующий:

[<имя>] <название директивы> [<операнды>] [< ; комментарий>]

Пример: X DB 10, -5, 0FFh ; массив X

Как видно, формат директив в целом совпадает с форматом команд. Единственное отличие - в директиве после имени, если оно есть, не ставится двоеточие. Имя, указываемое в начале директивы, - это, как правило, имя константы или переменной, описываемой данной директивой. В нашем примере X - это имя переменной - массива из трех элементов. Названия директив, как и мнемокоды, - служебные слова. При этом заранее известно, какие служебные слова обозначают директивы, а какие - мнемокоды, и путаницы здесь нет. Названия директив будут указываться по мере знакомства с директивами. Остальные части директивы (операнды и комментарий) записываются также, как и в командах.

2.2.2 Директивы определения данных

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

В простейшем случае в директиве DB, DW или DD описывается одна константа, которой дается имя для последующих ссылок на нее. По этой директиве ассемблер формирует машинное представление константы (в частности, если надо, "переворачивает" ее) и записывает в очередную ячейку памяти. Адрес этой ячейки становится значением имени: все вхождения имени в программу ассемблер будет заменять на этот адрес. Имена, указанные в директивах DB, DW и DD, называются именами переменных (в отличие от меток - имен команд).

В ASM числа записываются в нормальном (неперевернутом) виде в cистемах счисления с основанием 10, 16, 8 или 2. Десятичные числа записываются как обычно, за шестнадцатиричным числом ставится буква h (если число начинается с "цифры" A, B, ..., F, то вначале обязателен 0), за восьмиричным числом - буква q или o, за двоичным числом - буква b.

Примеры:

A DB 162 ;описать константу-байт 162 и дать ей имя A

B DB 0A2h ;такая же константа, но с именем B

С DW -1 ;константа-слово -1 с именем С

D DW 0FFFFh ;такая же константа-слово, но с именем D

E DD -1 ;-1 как двойное слово

Константы-символы описываются в директиве DB двояко: указывается либо код символа (целое от 0 до 255), либо сам символ в кавычках (одинарных или двойных); в последнем случае ассемблер сам заменит символ на его код. Например, следующие директивы эквивалентны (2A - код звездочки в ASCII):

CH DB 02Ah

CH DB '*'

CH DB "*"

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

ADR DW CH

будет отведено слово памяти, которому дается имя ADR и в которое запишется адрес (смещение), соответствующий имени CH. Если такое же имя описать в директиве DD, то ассемблер автоматически добавит к смещению имени его сегмент и запишет смещение в первую половину двойного слова, а сегмент - во вторую половину.

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

F DW ? ;отвести слово и дать ему имя F, ничего в этот байт не записывать

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

G DB 200, -5, 10h, ?, 'F'

Имя, указанное в директиве, считается именующим первую из констант. Для ссылок на остальные в MASM используются выражения вида <имя>+<целое>; например, для доступа к байту с числом -5 надо указать выражение

G+1, для доступа к байту с 10h - выражение G+2 и т.д.

Если в директиве DB перечислены только символы, например:

S DB 'a','+','b'

тогда эту директиву можно записать короче, заключив все эти символы в одни кавычки:

S DB 'a+b'

И, наконец, если в директиве описывается несколько одинаковых констант (переменных), то можно воспользоваться конструкцией повторения

k DUP(a,b,...,c)

которая эквивалентна повторенной k раз последовательности a,b,...,c.

Например, директивы

V1 DB 0,0,0,0,0

V2 DW ?,?,?,?,?,?,?,?,?,'a',1,2,1,2,1,2,1,2

можно записать более коротко таким образом:

V1 DB 5 DUP(0)

V2 DW 9 DUP(?), 'a', 4 DUP(1,2)

2.2.3. Директивы присваивания и эквивалентности

Директива эквивалентности - EQU (Equal, равно), имеет синтаксис

<имя> equ <операнд> - обязательно имя и один операнд. Директива означает, что указанному операнду присваивается указанное имя и при вхождении этого имени в текст программы ассемблер заменяет его на этот операнд, например если есть

name equpersто ассемблер будет рассматривать предложение

nm db name как nm dbpers’.

Директива носит чисто информационный характер и при ее появлении ассемблер ничего не заносит в машинную программу. Можно указывать имя a dw 55 b equ a c dw b ; то же что c dw a. Можно указывать имена регистров rez1 equ bx. Если справа - константное выражение, то слева - имя константы. N equ 10 k equ 3*n-1. Далее директива xx db k dup (?) то же что xx db 29 dup (?) можно также wp equ word ptr.

Директива присваивания. <имя> = <константное выражение> определяет константу с именем, указанным в левой части и с числовым значением, равным выражению справа. В отличие от констант, определенных с помощью директивы equ данная константа может менять свое значение, обозначая в разных частях программы разные числа. Так, k=10 a db k ; same as a db 10 k=k+5 s db k ; same as s db 15. Нельзя q equ 1 q equ 2 нельзя w equ 1 w=2 нельзя e=1 e equ 2

K=1

n equ k

a db n ;a=k

k=2

b db n ;b=2

k=1

n equ k+10

c dw n ; c=11

k=2

s dw n ;d=11

 

2.3. Ссылки назад и вперед.

Теперь сделаем несколько замечаний о метках и именах. Во-первых, метки команд и имена (констант, переменных и т.п.), указываемые в директивах, - это, вообще говоря, разные вещи как по смыслу, так и по ряду формальных признаков. Однако, если не вдаваться в детали, то метки можно рассматривать как имена команд. Поэтому в дальнейшем под термином “имя” мы будем обычно понимать как имена переменных, так и метки. Во-вторых, появление имени в начале команды или директивы считается описанием данного имени. В ЯА действует общее правило: каждое имя должно быть описано только раз, т.е. в программе не должно быть двух предложений с одним и тем же именем вначале. (Из этого правила есть исключения, они будут оговариваться явно.) В-третьих, если в языках высокого уровня действует правило «сначала опиши и лишь затем используй», то в ЯА такого правила нет и именем можно пользоваться (ссылаться на него) как до его описания, так и после описания. Поэтому допустимы оба случая:

A db 1

NEG A

...............

ссылка назад

..............

ссылка вперед

neg A

A DB 1

 

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

Операнд в директиве db - константное выражение с допустимым значением -128¸255. Возможны следующие варианты записи директивы:

q db 41h = q dba

qq dbas magic’ - операнд-строка

mass db 1,2,3,55h,-1,’power

Операнд - конструкция повторения DUP

zer db 0,0,0,0,0,0,0,0,0 = zer db 9 dup (0)

va db 32h, 3 dup (‘qw’,1,?) va db 32h,‘qw’,1,?, ‘qw’,1,?, ‘qw’,1,?

Adb 20 dup( 30 dup (?))

2.4. Оператор указания типа (PTR)

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

MOV AH,5 ;пересылка байта, т.к. AH - байтовый регистр

MOV AX,5 ;пересылка слова, т.к. AX - 16-битовый регистр

;(операнд 5 может быть байтом и словом, по нему

;нельзя определить размер пересылаемой величины)

MOV [BX],300 ;пересылка слова, т.к. число 300 не может быть

;байтом

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

MOV DS,AX ;оба операнда имеют размер слова

MOV CX,BH ;ошибка: регистры CX и BH имеют разные размеры

MOV DL,300 ;ошибка: DL - байтовый регистр, а число 300 не

;может быть байтом

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

MOV [BX],5

Здесь число 5 может быть и байтом, и словом, а адрес из регистра BX может указывать и на байт памяти, и на слово.

Отметим, что если некоторый адрес А необходимо модифицировать, скажем по регистру SI, то в АЯ это записывается так: A[SI]. Исполнительный адрес в этом случае вычисляется по формуле Аисп=А+[SI]. В частности, при А=0 эта запись имеет вид [SI] (0 не указывается) и задает исполнительный адрес, равный содержимому регистра SI: Аисп=[SI]. С учетом этого рассмотрим такую задачу. Пусть в регистре SI находится адрес некоторой ячейки памяти и требуется записать 0 в эту ячейку. Тогда, казалось бы, такое обнуление можно сделать с помощью команды MOV [SI] , 0.

Однако это не так. Дело в том, что по этой команде нельзя понять, какого размера ноль пересылается, поскольку второй операнд может обозначать ноль как размером в байт (00h), так и размером в слово (0000h), а что касается первого операнда, то адрес из регистра SI может быть адресом ячейки как размером в байт, так и размером в слово (с одного и того же адреса могут начинаться ячейки разных размеров). Итак, в этой команде ни по первому, ни по второму операнду нельзя определить размер пересылаемой величины, поэтому компилятор ассемблера не сможет определить, на какую конкретную машинную команду заменять эту символьную команду. В подобных ситуациях ассемблер фиксирует ошибку, сообщая, что типы операндов неизвестны.

Чтобы не было этой ошибки, автор программы должен явно указать тип хотя бы одного из операндов команды. Для этого в АЯ введен оператор указания типа PTR (от pointer, указатель), который записывается следующим образом:

<тип> PTR <выражение>

где <тип> - это BYTE, WORD или DWORD (есть и другие варианты, но мы их пока не рассматриваем), а выражение может быть константным или адресным. Если указано константное выражение, то оператор указывает на то, что значение этого выражения (число) должно рассматриваться ассемблером как величина указанного типа (размера); например, BYTE PTR 0 - это ноль как байт, а WORD PTR 0 - это ноль как слово (запись BYTE PTR 300 ошибочна, т.к. число 300 не может быть байтом). Отметим, что в этом случае оператор PTR относится к константным выражениям. Если же в PTR указано адресное выражение, то оператор «говорит», что адрес, являющийся значением выражения, должен восприниматься ассемблером как адрес ячейки указанного типа (размера); например: WORD PTR A - адрес А обозначает слово (байты с адресами А и А+1). В данном случае оператор PTR относится к адресным выражениям. С использованием оператора PTR наша задача решается так: если мы имеем в виду обнуление байта по адресу из регистра SI, то для этого необходимо использовать команду:

MOV BYTE PTR [SI], 0 или MOV [SI], BYTE PTR 0,

а если надо переслать нулевое слово, то команду:

MOV WORD PTR [SI], 0 или MOV [SI], WORD PTR 0.

Отметим, что обычно принято уточнять тип операнда-адреса, а не тип непосредственного операнда. Оператор PTR полезен еще в одной ситуации - когда надо не уточнить тип операнда, а изменить его. Пусть, к примеру, Z - переменная размером в слово: Z DW 1234h ; Z: 34h, Z+1: 12h

и надо записать ноль не во все это слово, а только в его первый байт - в тот, где находится величина 34h. Так вот, сделать это командой MOV Z, 0 нельзя, т.к. по ней 0 запишется в оба байта, потому, что имя Z описано в директиве DW и потому, как мы уже знаем, получает тип WORD: TYPE Z=WORD. Когда ассемблер определяет размер операнда команды, в качестве которого указано имя переменной, он учитывает тип, полученный именем при описании. Поэтому в нашем случае ассемблер и считает, что пересылается нулевое слово. Обычно это так и должно быть, но сейчас нас это не устраивает, нам сейчас нужно, чтобы ассемблер рассматривал Z как имя байта. Вот такое изменение типа имени и позволяет сделать оператор PTR:

MOV BYTE PTR Z,0 ; Z: 00h, Z+1: 12h

Здесь мы сказали ассемблеру, чтобы он игнорировал тот тип имени Z, который был приписан ему при описании, и считал, что имя Z обозначает байт. (Отметим, что такое изменение типа локально, оно действует только в данной команде.) Аналогичная ситуация возникает, если мы хотим получить доступ ко второму байту переменной Z. Например, для записи 15 в этот байт нельзя использовать команду MOV Z+1,15 , т.к. считается, что адрес Z+1 обозначает слово. Это общее правило в ЯА: адрес вида <имя>±<целое> имеет тот же тип, что и <имя>. Поэтому число 15 запишется в два байта - с адресами Z+1 и Z+2. Если же мы хотим изменить только байт по адресу Z+1, тогда надо воспользоваться оператором PTR: MOV BYTE PTR (Z+1),15

Здесь конструкция BYTE PTR (Z+1) «говорит», что Z+1 надо рассматривать как адрес байта, а не слова. Отметим, что в ЯА оператор PTR по старшинству выполняется до оператора сложения, поэтому запись BYTE PTR Z+1 трактуется как (BYTE PTR Z)+1. Однако в данном конкретном случае старшинство операторов не играет никакой роли, т.к. обе записи - BYTE PTR (Z+1) и BYTE PTR Z+1 - эквивалентны по смыслу: в первом случае мы сначала увеличиваем адрес Z на 1 и только затем сообщаем, что Z+1 надо рассматривать как адрес байта, а во втором случае мы сначала сообщаем, что Z - это адрес байта, и лишь затем увеличиваем его на 1 (при этом «байтовость» адреса сохраняется). Итак, оператор PTR используется в следующих ситуациях: когда типы операндов команд неизвестны и потому надо указать явно тип одного из операндов, и когда нас не устраивает тип, приписанный имени при его описании, и поэтому мы должны указать нужный нам тип.

2.5. Команда XCHG

В машинных программах приходится довольно часто переставлять местами какие-то две величины, и хотя такую перестановку можно реализовать только с помощью команды MOV, в ПК введена специальная команда для этого:

Перестановка (exchange): XCHG op1,op2

Эта команда меняем местами значения своих операндов (они должны быть либо байтами, либо словами): op1 ó op2. Флаги при этом не меняются. Пример:

MOV AX,62 ;AX=62

MOV SI,135 ;SI=135

XCHG AX,SI ;AX=135, SI=62

Допустимые типы операндов команды XCHG:

op1

op2

r8

r8, m8

перестановка байтов

m8

r8

r16

r16, m16

перестановка слов

m16

r16

Как видно, не допускается перестановка содержимого двух ячеек памяти. Если все-таки надо сделать такую перестановку, то это реализуется через какой-нибудь регистр. Например, поменять местами значения байтовых переменных X и Y можно так: MOV AL,X ;AL=X

XCHG AL,Y ;AL=Y, Y=X

MOV X,AL ;X=Y (исходное значение).

2.6. Сегментирование

2.6.1 Сегменты памяти. Сегментные регистры

Первые модели ПК имели оперативную память объемом 216 байтов (64Кб) и потому использовали 16-битовые адреса. В последующих моделяхпамять была увеличена до 220 байтов (1Мб=1000Кб), для чего уже необходимы 20-битовые адреса. Однако в этих ПК ради сохранения преемственности были сохранены 16-битовые адреса: именно такие адреса хранятся в регистрах и указываются в командах, именно такие адреса получаются в результате модмфикации по базовым и индексным регистрам. Как же удается 16-битовыми адресами ссылаться на 1Мб памяти?

Эта проблема решается с помощью сегментирования адресов (неявного базирования адресов). В ПК вводится понятие "сегмент памяти". Так называется любой участок памяти размером до 64Кб и с начальным адресом, кратным 16. Абсолютный (20-битовый) адрес A любой ячейки памяти можно представить как сумму 20-битового начального адреса (базы) B сегмента, которому принадлежит ячейка, и 16-битового смещения D - адреса этой ячейки, отсчитанного от начала сегмента: A=B+D. (Неоднозначность выбора сегмента не играет существенной роли, главное - чтобы сумма B и D давала нужный адрес.) Адрес B заносится в некоторый регистр S, а в команде, где должен быть указан адрес A, вместо него записывается пара из регистра S и смещения DMASM такая пара, называемая адресной парой или указателем, записывается как S:D). Процессор же устроен так, что при выполнении команды он прежде всего по паре S:D вычисляет абсолютный адрес A как сумму содержимого регистра S и смещения D и только затем обращается к памяти по этому адресу A. Вот так, заменяя в командах абсолютные адреса на адресные пары, и удается адресовать всю память 16-битовыми адресами (смещениями). В качестве регистра S разрешается использовать не любой регистр, а только один из 4 регистров, называемых сегментными: CS, DS, SS и ES. В связи с этим одновременно можно работать с 4 сегментами памяти: начало одного из них загружается в регистр CS и все ссылки на ячейки этогосегмента указываются в виде пар CS:D, начало другого заносится в DS и все ссылки на его ячейки задаются в виде пар DS:D и т.д. Если одновременно надо работать с большим числом сегментов, тогда нужно своевременно спасать содержимое сегментных регистров и записывать в них начальные адреса пятого, шестого и т.д. сегментов.

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

Как и все регистры ПК, сегментные регистры имеют размер слова. Поэтому возникает вопрос: как удается разместить в них 20-битовые начальные адреса сегментов памяти? Ответ такой. Поскольку все эти адресакратны 16 (см. выше), то в них младшие 4 бита (последняя 16-ричная цифра) всегда нулевые, а потому эти биты можно не хранить явно, а лишь подразумевать. Именно так и делается: в сегментном регистре всегда хранятся только первые 16 битов (первые четыре 16-ричные цифры) начального адреса сегмента (эта величина называется номером сегмента или просто сегментом). При вычислении же абсолютного адреса A по паре S:D процессор сначала приписывает справа к содержимому регистра S четыре нулевых бита (другими словами, умножает на 16) и лишь затем прибавляет смещение D, причем суммирование ведется по модулю 220:

Aабс = 16×[S]+D (mod 220)

Если, например, в регистре CS хранится величина 1234h, тогда адресная пара 1234h:507h определяет абсолютный адрес, равный 16*1234h+507h =12340h+507h = 12847h.

2.6.2 Сегментные регистры по умолчанию

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

Что это за договоренность? Считается, что регистр CS всегда указывает на начало области памяти, в которой размещены команды программы (эта область называется сегментом команд или сегментом кодов), и потому при ссылках на ячейки этой области регистр CS можно не указывать явно, он подразумевается по умолчанию. (Отметим попутно, что абсолютный адрес очередной команды, подлежащей выполнению, всегда задается парой CS:IP: в счетчике команд IP всегда находится смещение этой команды относительно адреса из регистра CS.) Аналогично предполагается, что регистр DS указывает на сегмент данных (область памяти с константами, переменными и другими величинами программы), и потому во всех ссылках на этот сегмент регистр DS можно явно не указывать, т.к. он подразумевается по умолчанию. Регистр SS, считается, указывает на стек - область памяти, доступ к которой осуществляется по принципу "последним записан - первым считан" (см. далее), и потому все ссылки на стек, в которых явно не указан сегментный регистр, по умолчанию сегментируются по регистру SS. Регистр ES считается свободным, он не привязан ни к какому сегменту памяти и его можно использовать по своему усмотрению; чаще всего он применяется для доступа к данным, которые не поместились или сознательно не были размещены в сегменте данных.

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

Здесь, правда, возникает такой вопрос: как по смещению определить, на какой сегмент памяти оно указывает? Точный ответ приведен ниже , а в общих чертах он такой: ссылки на сегмент команд могут быть только в командах перехода, а ссылки практически во всех других командах (кроме строковых и стековых) - это ссылки на сегмент данных. Например, в команде пересылки

MOV AX,X

имя X воспринимается как ссылка на данное, а потому автоматически восстанавливается до адресной пары DS:X. В команде же безусловного перехода по адресу, находящемуся в регистре BX,

JMP BX

абсолютный адрес перехода определяется парой CS:[BX].

Итак, если в ссылке на какую-то ячейку памяти не указан явно сегментный регистр, то этот регистр берется по умолчанию. Явно же сегментные регистры надо указывать, только если по каким-то причинам регистр по умолчанию не подходит. Если, например, в команде пересылки нам надо сослаться на стек (скажем, надо записать в регистр AH байт стека, помеченный именем X), тогда нас уже не будет устраивать договоренность о том, что по умолчанию операнд команды MOV сегментируется по регистру DS, и потому мы обязаны явно указать иной регистр - в нашем случае регистр SS, т.к. именно он указывает на стек:

MOV AH,SS:X

Однако такие случаи встречаются редко и потому в командах, как правило, указываются только смещения. Отметим, что в MASM сегментный регистр записывается в самой команде непосредственно перед смещением (именем переменной, меткой и т.п.), однако на уровне машинного языка ситуация несколько иная. Имеется 4 специальные однобайтовые команды, называемые префиксами замены сегмента (обозначаемые как CS:, DS:, SS: и ES:). Они ставятся перед командой, операнд-адрес которой должен быть просегментирован по регистру, отличному от регистра, подразумеваемому по умолчанию. Например, приведенная выше символическая команда пересылки - это на самом деле две машинные команды:

SS:

MOV AH,X

2.6.3 Сегментирование, базирование и индексирование адресов

Поскольку сегментирование адресов - это разновидность модификации адресов, то в ПК адрес, указываемый в команде, в общем случае модифицируется по трех регистрам - сегментному, базовому и индексному. В целом, модификация адреса производится в два этапа. Сначала учитываются только базовый и индексный регистры (если они, конечно, указаны в команде), причем вычисление здесь происходит в области 16-битовых адресов; полученный в результате 16-битовый адрес называется исполнительным (эффективным) адресом. Если в команде не предусмотрено обращение к памяти (например, она загружает адрес в регистр), то на этом модификация адреса заканчивается и используется именно исполнительный адрес (он загружается в регистр). Если же нужен доступ к памяти, тогда на втором этапе исполнительный адрес рассматривается как смещение и к нему прибавляется (умноженное на 16) содержимое сегментного регистра, указанного явно или взятого по умолчанию, в результате чего получается абсолютный (физический) 20-битовый адрес, по которому реально и происходит обращение к памяти.

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

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

1) В командах перехода адрес перехода сегментируется по регистру CS и только по нему, т.к. абсолютный адрес команды, которая должна быть выполнена следующей, всегда определяется парой CS:IP (попытка изменить в таких командах сегментный регистр будет безуспешной).

Отметим, что сегментиорвание по регистру CS касается именно адреса перехода, а не адреса той ячейки, где он может находиться. Например, в команде безусловного перехода по адресу, находящемуся в ячейке X:

JMP X

имя X сегментируется по регистру DS, а вот адрес перехода, взятый из ячейки X, уже сегментируется по регистру CS.

2) Адреса во всех других командах, кроме строковых (STOS, MOVS,

SCAS и CMPS), по умолчанию сегментируются:

- по регистру DS, если среди указанных регистров-модификаторов нет регистра BP;

- по регистру SS, если один из модификаторов - регистр BP.

Таким образом, адреса вида A, A[BX], A[SI], A[DI], A[BX][SI] и A[BX][DI] сегментируются по регистру DS, а адреса A[BP], A[BP][SI] и A[BP][DI] - по регистру SS, т.е. адреса трех последних видов используются для доступа к ячейкам стека.

3) В строковых командах STOS, MOVS, SCAS и CMPS, имеющих два операнда-адреса, на которые указывают индексные регистры SI и DI, один из операндов (на который указывает SI) сегментируется по регистру DS, адругой (на него указывает DI) - по регистру ES.

2.6.4 Программные сегменты. Директива ASSUME

Рассмотрим, как сегментирование проявляется в программах на MASM.

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

DT1 SEGMENT ;программный сегмент с именем DT1

A DB 0

B DW ?

DT1 ENDS

;

DT2 SEGMENT ;программный сегмент DT2

C DB 'hello'

DT2 ENDS

;

CODE SEGMENT ;программный сегмент CODE

ASSUME CS:CODE, DS:DT1, ES:DT2

BEG: MOV AX,DT2

MOV DS,AX

MOV BH,C

...

CODE ENDS

END BEG ;конец текста программы

Предложения программного сегмента ассемблер размещает в одном сегменте памяти (в совокупности они не должны занимать более 64Кб) начиная с ближайшего свободного адреса, кратного 16. Номер (первые 16 битов начального адреса) этого сегмента становится значением имени сегмента. В MASM это имя относится к константным выражениям, а не адресным, в связи с чем в команде MOV AX,DT2

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

Имена же переменных (A, B, C) и метки (BEG) относятся к адресным выражениям, и им ставится в соответствие адрес их ячейки относительно "своего" сегмента: имени A соответствует адрес 0, имени B - адрес 1,

имени C - адрес 0, а метке BEG - адрес 0.

Все ссылки на предложения одного программного сегмента ассемблер сегментирует по умолчанию по одному и тому же сегментному регистру. По какому именно - устанавливается специальной директивой ASSUME. В нашем примере эта директива определяет, что все ссылки на сегмент CODE должны, если явно не указан сегментный регистр, сегментироваться по регистру CS, все ссылки на DT1 - по регистру DS, а все ссылки на DT2 - по регистру ES.

Встретив в тексте программы ссылку на какое-либо имя (например, на имя C в команде MOV AX,C), ассемблер определяет, в каком программном сегменте оно описано (у нас - в DT2), затем по информации из директивы ASSUME узнает, какой сегментный регистр поставлен в соответствие этому сегменту (у нас - это ES), и далее образует адресную пару иэ данного

регистра и смещения имени (у нас - ES:0), которую и записывает в формируемую машинную команду. При этом ассемблер учитывает используемое в ПК соглашение о сегментных регистрах по умолчанию: если в адресной паре, построенной им самим или явно заданной в программе, сегментный регистр совпадает с регистром по умолчанию, то в машинную команду заносится лишь смещение. Если, скажем, в нашем примере встретится команда MOV CX,B, тогда по имени В ассемблер построит пару DS:1, но раз операнд-адрес команды MOV по умолчанию сегментируется по регистру DS, то записывать этот регистр в машинную команду излишне и ассемблер записывает в нее только смещение 1.

Таким образом, директива ASSUME избавляет программистов от необходимости выписывать полные адресные пары не только тогда, когда используются сегментные регистры по умолчанию (как в случае с именем B), но тогда, когда в машинной команде нужно было бы явно указать сегментный регистр (как в случае с именем C). В MASM сегментный регистр в ссылке на имя требуется указывать лишь тогда, когда имя должно по каким-либо причинам сегментироваться по регистру, отличному от того, что постав-

лен в соответствие всему сегменту, в котором это имя описано.

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

Во-вторых, в директиве ASSUME следует каждому сегменту ставить в соответствие сегментный регистр: если ассемблеру встретится ссылка на имя из сегмента, которому не соответствует никакой сегментный регистр, то он зафиксирует ошибку. Правда, в обоих случаях можно избежать ошибки, но для этого в ссылках необходимо явно указывать сегментный регистр.

2.6.5 Начальная загрузка сегментных регистров

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

Поскольку в ПК нет команды пересылки непосредственного операнда в сегментный регистр (а имя, т.е. начало, сегмента - это непосредственный операнд), то такую загрузку приходится делать через какой-то другой, несегментный, регистр (например, AX):

MOV AX,DT1 ;AX:=начало сегмента DT1

MOV DS,AX ;DS:=AX

Аналогично загружается и регистр ES.

Загружать регистр CS в начале программы не надо: он, как и счетчик команд IP, загружается операционной системой перед тем, как начинается выполнение программы (иначе нельзя было бы начать ее выполнение). Что же касается регистра SS, используемого для работы со стеком, то он может быть загружен так же, как и регистры DS и ES, однако в MASM предусмотрена возможность загрузки этого регистра еще до выполнения программы (см. 1.7).

2.6.6 Ссылки вперед

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

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

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

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

не всегда удается: если правильная машинная команда должна занимать больше места, чем машинная команда, построенная на основе предположения (например, перед командой надо на самом деле вставить префикс замены сегмента), тогда ассемблер фиксирует ошибку (как правило, это ошибка номер 6: Phase error between passes.)

Какие же предположения делает ассемблер, встречая ссылку вперед? Во всех командах, кроме команд перехода (о них см. 1.5), ассемблер предполагает, что имя будет описано в сегменте данных и потому сегментируется по регистру DS. Это следует учитывать при составлении программы: если в команде встречается ссылка вперед на имя, которое описа-

но в сегменте, на начало которого указывает сегментный регистр, отлич-

ный от DS, то перед таким именем автор программы должен написать соот-

вествующмй префикс. Пример:

code segment

assume cs:code

x dw ?

beg: mov ax,x ;здесь вместо cs:x можно записать просто x

mov cs:y,ax ;здесь обязательно надо записать cs:y

...

y dw ?

code ends

2.7. Операции ввода-вывода.

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

Прерывания BIOS.

Int 10h video services (обслуживание видео)

Регистр АН используется для занесения в него номера выполняемой функции.

00 - (mov ah,0) - установка режима вывода (текстовой, графический)

AL - номер режима (03 - обычный текстовый - 80х25, 13 - графика 640х480 256 цветов)

05h - выбор страницы на которую выводятся данные. AL - номер страницы. Начальная - 00, текстовых - 8, графических -2, для адаптеров VGA.

02h - установка позиции курсора

bh - номер видеостраницы;

dh - номер ряда, начиная с 00;

dl - номер колонки, начиная с 00;

09h - вывод символа в позицию курсора

al - порядковый номер символа в соответствии с ASCII.

bh - номер видеостраницы;

bl - атрибуты символа: цвет, цвет фона, мигание.

cx - количество выводимых символов.

0ah - то же что 09, но без задания атрибутов символа bh - игнорируется.

0ch - вывод графического пикселя

al - номер цвета;

bh - номер видеостраницы;

cx - графическая колонка;

dx - графический ряд.

1300h - вывод строки на экран ах=1300Н

bh - номер видеостраницы;

bl - атрибуты символа;

cx - длина строки;

dh,dl - ряд, колонка начала вывода.

es:bp - адрес первого байта строки.

1301h - вывод строки на экран. После вывода производится изменение позиции курсора на следующую после последнего символа выведенной строки. Регистры - как в 1300Н.

1302h - то же что 1300h, но без учета атрибутов символа.

1303h - то же что 1301h, но без учета атрибутов символа.

 

 

Вы находитесь на сайте Xenoid v2.0:
если вам нужно быстро, подробно и недорого
решить контрольную - обращайтесь. Возможны консультации
онлайн. См. раздел "Решение задач".

 

 

 

Copyright © 2005-2013 Xenoid v2.0

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