Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции / Лекция 6 / Лекция 6.doc
Скачиваний:
24
Добавлен:
10.06.2015
Размер:
226.3 Кб
Скачать

Глава 10

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

управления

Программирование нелинейных алгоритмов

Классификация команд передачи управления

Команды безусловной передачи управления

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

Команды условной передачи управления

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

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

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

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

Как вы помните, программа представляет собой последовательность команд и данных, занимающих определенное пространство оперативной памяти. Это пространство памяти может либо быть непрерывным, либо состоять из нескольких фрагментов. В главе 5 нами были рассмотрены средства сегментации кода программы и ее данных. То, какая команда программы должна выполняться следующей, процессор узнает по содержимому пары регистров CS:(E)IP, в которой:

- CS — регистр сегмента кода, в котором находится физический (базовый) адрес текущего сегмента кода;

-EIP/IP — регистр указателя команды, в котором находится значение, представляющее собой смещение в памяти следующей выполняемой команды относительно начала текущего сегмента кода.

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

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

Команды безусловной передачи управления:

-безусловного перехода;

-вызова процедуры и возврата из процедуры;

-вызова программных прерываний и возврата из программных прерываний.

Команды условной передачи управления:

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

- перехода по состоянию определенного флага;

-перехода по содержимому регистра ЕСХ/СХ.

Команды управления циклом:

-организации цикла со счетчиком ЕСХ/СХ;

-организации цикла со счетчиком ЕСХ/СХ с возможностью досрочного выхода из цикла по дополнительному условию.

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

Подобно переменной, транслятор ассемблера присваивает любой метке три атрибута:

-имя сегмента кода, где эта метка описана;

-смещение — расстояние в байтах от начала сегмента кода, в котором описана метка;

-тип, или атрибут расстояния, метки.

Последний атрибут может принимать два значения:

-near — переход на метку возможен только в пределах сегмента кода, где эта метка описана, то есть для перехода на метку физически достаточно изменить только содержимое регистра EIP/IP;

-far — переход на метку возможен только в результате межсегментной передачи управления, для осуществления которой требуется изменение содержимого как регистра EIP/IP, так и регистра CS.

Метку можно определить двумя способами:

-оператором: (двоеточие);

-директивой LABEL

Первый способ. С помощью оператора: можно определить метку только ближнего типа near. Определенную таким образом метку можно использовать в качестве операнда в командах условных переходов JCC и безусловного перехода JMP, CALL Эти команды, естественно, должны быть в том же сегменте кода, в котором определена метка. Команда ассемблера может находиться как на одной строке с меткой, так и на следующей.

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

LABEL. Тип метки может иметь значение near или far. Обычно директиву LABEL используют для определения идентификатора заданного типа. Например, следующие описания меток ближнего типа эквивалентны:

ml:

mov ax,pole_l

ml label near

mov ax,pole_l

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

.

,…

public m_far ;сделать метку m_far видимой для внешних программ

m_far label far ; определение метки дальнего типа m_far

m_near: ;определение метки ближнего типа n_far

mov ax,pole_l

Определив для команды mov ax,pole_l две метки, можно организовывать переход на эту команду как из данного сегмента команд, так и из других сегментов команд, в том числе принадлежащих другим модулям. Для того чтобы сделать видимым извне имя метки m_far, применяется директива PUBLIC. Другой часто встречающийся случай применения директивы LABEL — это организация доступа к области памяти, содержащей данные разных типов, например:

mas_b label byte

mas_w dw 15 dup (?)

;в этом фрагменте оба идентификатора относятся к одной области

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

;имена, либо как с байтовым массивом, либо как с массивом слов

mov mas_b+10,al ;запись из al в массив байтов (в 11-й байт)

mov mas_w,ax ;запись из ах в первое слово области mas_w

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

резервирования и инициализации данных в сегменте данных).

Транслятор ассемблера обеспечивает нам две возможности работы с этим счет-

чиком:

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

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

Классический пример:

.data

;вычисление длины строки в сегменте данных

Str_Mes db "Займись делом - учи ассемблер ..."

Len_Msg=$-Str_Mes

После ассемблирования значение Len_Msg будет равно длине строки, так как значение символа $ в месте его появления отличается от Str_Mes ровно на длину строки.

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

cmp dl,9h ;сравнить (dl) с 9h

jle $+5 ;перейти на команду mov cl.,4h, если dl<9h или dl=9h

sub dl,7h ;вычитание: (dl)=(dl)47h

;M1:

mov cl,4h ;пересылка 4h в регистр cl

cmp al,9h ;сравнить (al) с 9h 28

jle $+4 ;перейти на команду add dl.al, если al<9h или al=9h

sub al,7h ;вычитание: (al)=(al)47h

;M2:

add dl.al

Как узнать правильные значения длины команд? Во-первых, по опыту, то.есть просто догадаться. Во-вторых, из файла листинга, что не всегда дает результат, так как конечные машинные команды (колонка файла листинга с объектным кодом) в нем не всегда до конца сформированы. В-третьих, узнать длины команд можно, загрузив программу в отладчик и активировав окно CPU. Последний способ самый точный. Не будет ничего страшного, если для первого прогона исполняемого модуля программы в нем будут неверные значения для подобных относительных переходов. И еще одно замечание: при переходе вперед необходимо учитывать длину текущей команды перехода, при переходе назад этого делать не нужно.

Кроме возможности получения значения счетчика адреса компилятор позволяет при необходимости установить счетчик адреса в нужное абсолютное значение. Это делается с помощью директивы ORG:

ORG выражение

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

К примеру, эту директиву всегда используют при создании исполняемого файла с расширением .com. В контексте нашего обсуждения поясним, в чем здесь суть. Программа в формате СОМ состоит из одного сегмента величиной не более 64 Кбайт. Сегментные регистры CS и DS содержат одно и то же значение физического адреса, а регистр SS указывает на конец этого единственного сегмента. Программа-загрузчик операционной системы, считывая с диска исполняемые файлы с расширениями .ехе и .com, производит определенные действия. В частности, настраивает перемещаемые адреса программ на их конкретные физические значения. Кроме того, к началу каждой исполняемой программы в памяти добавляется специальная область величиной 256 байт (100h) — префикс программного сегмента (PSP). Он предназначен для хранения различной информации о загруженном исполняемом модуле. Для программ формата СОМ блок PSP находится в начале сегмента размером в 64 Кбайт. В исходной программе, для исполняемого файла которой планируется формат СОМ, мы должны предусмотреть место для блока Р5Р,-что и делается директивой org 100h.

Чтобы закончить разговор о файлах этого типа, разберемся с тем, как получить исполняемый модуль формата СОМ. Трансляция программы выполняется как обычно. Далее возможны два варианта действий.

Во-первых, возможно использование утилиту tlink с ключом /t:

tlink /t имя_объектного_файла

Этот вариант подходит только в том случае, если вы правильно оформили исходный текст программы для формата СОМ. Кроме того, наличие директивы org l00h предполагает:

-отсутствие разделения сегментов данных и стека, то есть данные необходимо описать в сегменте кода, а для их обхода использовать команду безусловного перехода JMP;

-применение директивы ASSUME для указания транслятору на необходимость связать содержимое регистров DS и SS с сегментом кода:

codeseg segment para "code"

assume cs: codeseg,ds: codeseg,ss: codeseg

org l00h

jmp ml

;здесь описываем данные

ml:

;далее идут команды программы

Во-вторых, можно использовать специальную утилиту ехе2bin. Эта утилита

позволяет преобразовать уже полученный ранее исполняемый модуль в формате ЕХЕ в формат СОМ:

exe2bin имя_файла_ехе имя_файла_сом.сот

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

Соседние файлы в папке Лекция 6