Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

epd627

.pdf
Скачиваний:
23
Добавлен:
02.05.2015
Размер:
816.97 Кб
Скачать

2) из стандартных (полных) директив определения сегментов (более сложно использовать, но он предусматривает полное управление сегментами, необходимое в некоторых прикладных программах ассемблера; см. уч. пос., ч.2, 10 и 11).

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

впрограмме, различают следующие модели памяти.

Сверхмалая (tiny) – код программы и ее данные должны размещаться внутри одного и того же сегмента размером 64 Кб. Код и данные имеют ближний тип (см. уч. пос., ч.3 и 7).

Малая (small) – код программы должен размещаться внутри одного сегмента данных размером 64 Кб, а данные – в отдельном сегменте данных (размером 64 Кб). И код, и данные должны быть ближнего типа (см. уч.

пос., ч.4 и 5).

Средняя (medium) – код программы может превышать 64 Кб, но данные должны помещаться в один сегмент размером 64 Кб. Код имеет дальний тип, а данные – ближний (см. уч. пос., ч.2 и 6).

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

ч.14).

Большая (large) – код и данные программы могут превышать 64 Кб, но один массив данных не может превышать 64 Кб. И код и данные имеют дальний тип (см. уч. пос., ч.14).

Сверхбольшая (huge) – код и данные могут превышать по размеру 64 Кб. Массивы данных также могут превышать 64 Кб. Код и данные имеют дальний тип. Указатели на элементы массива также дальнего типа

(см.п.4.58; уч. пос., ч.14; 1, с. 112).

4.24. Стандартные директивы определения сегментов

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

stacksg

segment para stack 'stack'

db 200h dup (?)

stacksg

ends

datasg

segment para 'data'

HelloMessage db 'Привет!',13,10,'$'

datasg

ends

codesg

segment para 'code'

assume cs:codesg, ds:datasg, ss:stacksg ProgramStart:

mov ax,datasg

31

mov ds,ax

; установить ds в значение сегмента данных

mov dx,offset HelloMessage;ds:dx указывает на сообщение 'Привет!'

mov ah,9

; функция DOS вывода строки

int 21h

; вывести строку на экран

mov ah,4ch

; функция DOS завершения программы

int 21h

; завершить программу

codesg ends

end ProgramStart

4.25. Директива segment

Директива segment определяет начало сегмента. Метка, которая указывается в данной директиве, определяет начало сегмента. Например, директива:

codesg segment

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

4.26. Директива ends

Директива ends определяет конец сегмента. Например: codesg ends

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

4.27. Директива assume

Директива assume указывает, в значение какого сегмента установлен данный сегментный регистр. Директиву assume cs: требуется указывать в каждой программе, в которой используются стандартные сегментные директивы, так как ассемблеру необходимо знать о сегменте кода для того, чтобы установить выполняемую программу. Кроме того, обычно используются директивы assume ds: и assume es:, благодаря которым ассемблер знает, к каким ячейкам памяти можно адресоваться в данный момент.

Директива assume позволяет ассемблеру проверить допустимость каждого обращения к именованной ячейке памяти с учетом значения текущего сегментного регистра. Рассмотрим следующий пример:

Data1 segment para 'Data' Var1 dw 0

32

Data1 ends

Data2 segment para 'Data' Var2 dw 0

Data2 ends

Codesg segment para 'Code' assume cs:Codesg

ProgramStart: mov ax,Data1

mov ds,ax ; установить ds в Data1 assume ds:Data1

mov ax,[Var2] ; попытаться загрузить Var2 в ax (это приведет к

;

ошибке, так как Var2 недоступна в сегменте Data1)

mov ah,4ch

; номер функции dos для завершения программы

int 21h

; завершить программу

Codesg ends

end ProgramStart

Ассемблер отмечает в данной программе ошибку, так как в ней делается попытка получить доступ к переменной памяти Var2, когда регистр ds установлен в значение сегмента Data1 Var2 нельзя адресоваться, пока ds не будет установлен в значение сегмента Data2).

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

Например: mov ax,0b800h mov ds,ax

assume ds:nothing

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

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

Data1 segment para 'Data' Var1 dw 0

Data1 ends

Data2 segment para 'Data' Var2 dw 0

Data2 ends

Codesg segment para 'Code'

33

assume cs:Codesg ProgramStart:

mov ax,Data1

mov ds,ax ; установить ds в Data1 assume ds:Data1

mov ax,Data2

mov es,ax ; установить es в Data2 assume es:Data2

mov ax,[Var2] ; загрузить Var2 в ax

;ассемблер укажет процессору 8086, что загрузку нужно выполнять

;относительно es, т.к. к Var2 нельзя получить доступ относительно ds mov ah,4ch ; функция dos завершения работы программы

int

21h

; завершить программу

Codesg

ends

 

end ProgramStart

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

mov ax,[Var2]

для доступа к Var2 относительно сегментного регистра es, а не ds.

Это происходит потому что регистр ds установлен в значение сегмента Data1, а es установлен в значение сегмента Data2. Ассемблер совершенно правильно заключает, что к Var2 нельзя получить доступ относительно регистра ds, однако Var2 доступно относительно сегментного регистра es. В итоге ассемблер включает перед инструкцией mov специальный код (префикс переопределения сегмента), чтобы указать процессору 8086, что вместо сегментного регистра ds нужно использовать регистр es.

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

4.28. Директива end

Директива end отмечает конец исходного кода программы. Все строки, которые следуют за нею, ассемблером игнорируются. Например: stacksg segment para ‘Stack’

db 200h ; стек объемом 512 байтов stacksg ends

datasg segment para ‘Data’ ; начало сегмента данных datasg ends

codesg segment para ‘Code’ ; начало сегмента кода

34

main proc far

assume cs:codesg, ds:datasg, ss:stacksg

mov ax,datasg

 

mov ds,ax

; ds указывает на сегмент данных datasg

mov

ah,4ch

 

int

21h

 

main endp

 

codesg ends

 

end

main

 

Это простейшая программа на ассемблере. Она ничего не делает, просто немедленно возвращает управление DOS.

Кроме завершения программы, директива end может указывать, где должно начинаться выполнение при запуске программы (с метки main). Иногда требуется начать выполнение программы в файле .exe не с первой инструкции. Директива end предусматривает такие случаи. Например:

stacksg segment para ‘Stack’

 

 

db

200h

 

; стек объемом 512 байтов

stacksg ends

 

 

 

datasg segment para ‘Data’

; начало сегмента данных

datasg ends

 

 

 

codesg segment para ‘Code’

; начало сегмента кода

Delay proc

 

 

 

mov

cx,60000

 

 

DelayLoop:

 

 

 

loop DelayLoop

 

 

ret

 

 

 

 

 

Delay endp

 

 

 

mainproc far

 

 

 

 

assume cs:codesg, ds:datasg, ss:stacksg

ProgramStart:

 

 

 

mov ax,datasg

 

 

mov ds,ax

; ds указывает на сегмент данных datasg

call Delay

; пауза на время, необходимое для выполнения 64

циклов

 

 

 

 

 

mov

ah,4ch

 

 

 

int

21h

 

 

 

main endp

 

 

 

codesg ends

 

 

 

end

ProgramStart

 

 

Выполнение здесь начинается не на первой инструкции исходного кода (mov cx,60000) по метке Delay. Вместо этого выполнение начинается

35

с инструкции mov ax,datasg по метке ProgramStart, как определено в директиве end. И уже в процессе выполнения этой процедуры вызывается процедура, обеспечивающая временну'ю задержку call Delay.

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

4.29. Основные элементы программы на языке ассемблера

Теперь можно назвать детали программирования:

–основные компоненты программы;

–минимальные требования к программе;

–способы, с помощью которых программа может получить доступ к памяти;

–определение и использование сегментов;

–выделение и использование переменных в памяти;

–наиболее общеупотребительные инструкции.

4.30. Элементы и структура программы на языке ассемблера

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

stacksg segment para ‘Stack’

db 200h

; стек объемом 512 байтов (200h)

stacksg ends

 

 

datasg segment para ‘Data’

; начало сегмента данных

DisplayString db

13,10

; символы "возврат каретки/перевод строки"

ThreeChars db 3 dup (?)

; память для трех введенных символов

db

'$'

; символ $, указывающий DOS, где

datasg ends

; завершить вывод строки DisplayString

 

 

codesg segment para ‘Code’

; начало сегмента кода

mainproc far

assume cs:codesg, ds:datasg, ss:stacksg

mov ax,datasg

 

mov ds,ax

; ds указывает на сегмент данных datasg

mov bx,offset ThreeChars ; указывает на ячейку с первым символом

mov ah,1

; функция DOS ввода с клавиатуры

int 21h

; получить следующую нажатую клавишу

 

36

dec al

; вычесть из символа 1

mov [bx],al

; сохранить измененный символ

inc bx ; указать на ячейку памяти, где содержится следующий символ

inc 21h

; получить следующую нажатую клавишу

dec al

; вычесть из символа 1

mov [bx],al

; сохранить измененный символ

inc bx

; ссылка на ячейку памяти со следующим символом

int 21h

; получить следующую нажатую клавишу

dec al

; вычесть из символа 1

mov [bx],al

; сохранить измененный символ

mov dx,offset DisplayString ; ссылка на строку измененных символов

mov ah,9

; функция DOS вывода строки

int 21h

; вывести измененные символы

mov ah,4ch

; функция DOS завершения

int 21h

; программы

ret

 

main endp

 

codesg ends

 

end main

; конец кода и указание на входную точку

Данная программа содержит стандартные директивы определения сегментов стека, данных и кода, а также директиву end. В каждой программе на ассемблере, чтобы обеспечить определение сегментов и управление ими, необходимы директивы определения сегментов (стандартные, как в приведенном выше примере, или упрощенные – см. уч. пос., ч.4), а завершать программу на ассемблере всегда должна директива end. Директивы представляют собой только "рамки" программы на ассемблере. В самой программе необходимы также строки исходного кода, выполняющие какие-либо действия. Что делает программа, приведенная в примере? Введите ее, получите объектный файл и выполняемую программу, запустите, наберите “IBM”, после чего программа ответит Вам: “hal”

4.31. Формат строки

Строки исходного кода на языке ассемблера имеют следующий формат:

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

где <метка> представляет собой необязательное имя идентификатора; <инструкция/директива> – мнемоника инструкции или директивы;

37

<операнды> – содержат сочетание 0, 1 или 2 (иногда более) констант, ссылок на регистры, или на память, или на текстовые строки, как это требуется в каждой конкретной инструкции или директиве;

<;комментарий> – необязательный комментарий. Рассмотрим каждый из этих элементов более подробно.

4.32. Метки

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

A Z a z _ @ $ ? 0 9

Цифры 0 9 не могут использоваться в качестве первых символов метки. Символы $ и ? имеют специальное значение, поэтому их не следует использовать в идентификаторах.

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

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

фрагментах программы 1) и 2):

 

1)

jmp

formula1

2) jmp formula1

 

. . .

. . .

formula1:

 

formula1: add ax, dx

 

add

ax,dx

. . .

следующей инструкцией, выполняемой после инструкции jmp, которая выполняет переход на метку formula1, является инструкция add ax,dx.

Преимущества размещения метки на своей собственной строке:

1)удается использовать длинные метки без нарушения формата программы;

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

встроенных символов, включая имена регистров (ax, bx и т.д.) и операторы, используемые в выражениях (ptr, byte, word и т.д.). В качестве меток нельзя также использовать любую из директив .ifxxx или .errxxx. Несколько других зарезервированных в ассемблере идентификаторов могут использоваться только в определенных контекстах. Эти идентификаторы включают в себя name, include и comment.

38

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

cmp

al,'a'

jb

NotALowerCaseLetter ; не строчная буква

cmp

al,'z'

ja

NotALowerCaseLetter

sub

al,20h ; преобразовать в верхний регистр (прописную букву)

NotALowerCaseLetter:

. . .

метка NotALowerCaseLetter определена с двоеточием, но в ссылках на эту метку двоеточие не указывается.

Могут быть метки, которые не должны завершаться двоеточием. В примерах программы (пп.4.30, 4.45 и 4.56) содержатся несколько меток без двоеточий (DisplayString и ThreeChars в п.4.30, vvod_b в п.4.45 и KeyBuffer в п.4.56).

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

cmpal,'a' jb P1 cmpal,'z' ja P1

sub al,20h ; преобразовать в верхний регистр

P1:

. . .

Вариант с описательной меткой гораздо более понятен. Метки могут содержать символы подчеркивания. Можно использовать метки типа not_a_lower_case_letter, или Not_A_Lower_Case_Letter, или NotA_LowerCaseLetter.

4.33. Мнемоники инструкций и директивы

Основным полем в строке программы на ассемблере является поле <инструкция/директива>. Это поле может содержать мнемонику инструкции или директиву.

Мнемоники инструкций представляют собой удобные для чтения имена инструкций, непосредственно выполняемых процессором 8086. Например, mov, add, mul и jmp – это мнемоники инструкций, соответствующие инструкциям процессора 8086 перемещения данных, сложения, умножения и перехода соответственно. Ассемблер преобразует

39

каждую мнемонику инструкции непосредственно в соответствующую инструкцию на машинном языке.

В отличие от мнемоник инструкций, директивы совсем не генерируют выполняемого кода. Вместо этого они управляют различными аспектами работы ассемблера – от типа ассемблируемого кода (процессоры 8086, 80286, 80386 и т.д.) до используемых сегментов и формата создаваемых файлов листингов. Имеется несколько директив, которые необходимы в любой программе на ассемблере (segment, ends, assume и end; см.пп.4.25– 4.28).

4.34. Операнды

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

Для инструкций требуются 0, 1, 2 или более операндов. Возможные операнды включают в себя регистры, константы, метки, переменные в памяти и текстовые строки. Инструкция с одним операндом выполняет операции с этим операндом. Например:

push ax

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

mov ax,bx

то содержимое регистра bx помещается (копируется) в регистр ax.

4.35. Регистровые операнды

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

1)mov bx,ax ;bx:=ax

2)

push

ds

; занести в стек содержимое регистра ds

3)

xchg

al,dl

;al:=dl и dl:=al

4)mov cl,5

 

ror dx,cl

; циклический сдвиг регистра dx на 5 битов вправо

5)

in al,dx

; из порта с номером в dx ввод байта в регистр al

6)

inc si

;si:=si+1

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

40

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]