Программная модель микропроцессора. Общая характеристика и структура программы на ассемблере. Описание системы команд микропроцессоров Intel. Опции транслятора и редактора связей. Команды пересылки данных и передачи управления. Цепочечные команды.
Программная модель микропроцессора (1 пара) ..................................................5 Лекция 3. Структура программы на ассемблере (2 пары) ...................................................13 Лекция 4. Типы данных (1 пара) ..........................................................................................109 Лекция 6. Массивы (1 пара)..................................................................................................116 Лекция 7. Двухмерные массивы. Типовые операции с массивами (1 пара)....................122 Лекция 8.Условные директивы компиляции имеют общий синтаксис и применяются в составе следующей синтаксической конструкции: IFXXX логическое_выражение_или_аргументы фрагмент_программы_1 ELSE фрагмент_программы_2 ENDIF Заключение некоторых фрагментов текста программы - фрагмент_программы_1 и фрагмент_программы_2 - между директивами IFXXX, ELSE и ENDIF приводит к их выборочному включению в объектный модуль. Какой именно из этих фрагментов - фрагмент_программы_1 или фрагмент_программы_2 - будет включен в объектный модуль, зависит от конкретного типа условной директивы, задаваемого значением xxx, и значения условия, определяемого операндом (операндами) условной директивы логическое_выражение_или_аргумент(ы). Обработка этих директив макроассемблером заключается в вычислении логического_выражения и включении в объектный модуль фрагмент_программы_1 или фрагмент_программы_2 в зависимости от того, в какой директиве IF или IFE это выражение встретилось: ? если в директиве IF логическое выражение истинно, то в объектный модуль помещается фрагмент_программы_1. Если же директивы ELSE нет (и символическое_имя в программе не описано), то вся часть программы между директивами IF и ENDIF игнорируется и в объектный модуль не включается.
Введение
Всего имеется 10 типов условных директив компиляции. Их логично попарно объединить в четыре группы: 1. Директивы IF и IFE — условная трансляция по результату вычисления логического выражения.
2. Директивы IFDEF и IFNDEF — условная трансляция по факту определения символического имени.
3. Директивы IFB и IFNB — условная трансляция по факту определения фактического аргумента при вызове макрокоманды.
4. Директивы IFIDN, IFIDNI, IFDIF и IFDIFI — условная трансляция по результату сравнения строк символов.
Условные директивы компиляции имеют общий синтаксис и применяются в составе следующей синтаксической конструкции: IFXXX логическое_выражение_или_аргументы фрагмент_программы_1 ELSE фрагмент_программы_2 ENDIF
Заключение некоторых фрагментов текста программы — фрагмент_программы_1 и фрагмент_программы_2 — между директивами IFXXX, ELSE и ENDIF приводит к их выборочному включению в объектный модуль. Какой именно из этих фрагментов — фрагмент_программы_1 или фрагмент_программы_2 — будет включен в объектный модуль, зависит от конкретного типа условной директивы, задаваемого значением xxx, и значения условия, определяемого операндом (операндами) условной директивы логическое_выражение_или_аргумент(ы).
Синтаксические конструкции, соответствующие директивам условной компиляции, могут быть вложенными друг в друга (см. "Вложенность директив условной трансляции")
Директивы IF и IFE
Синтаксис этих директив следующий: IF(E) логическое_выражение фрагмент_программы_1
ELSE фрагмент_программы_2 ENDIF
Обработка этих директив макроассемблером заключается в вычислении логического_выражения и включении в объектный модуль фрагмент_программы_1 или фрагмент_программы_2 в зависимости от того, в какой директиве IF или IFE это выражение встретилось: ? если в директиве IF логическое выражение истинно, то в объектный модуль помещается фрагмент_программы_1.
? Если логическое выражение ложно, то при наличии директивы ELSE в объектный код помещается фрагмент_программы_2. Если же директивы ELSE нет, то вся часть программы между директивами IF и ENDIF игнорируется и в объектный модуль ничего не включается. Кстати сказать, понятие истинности и ложности значения логического_выражения весьма условно. Ложным оно будет считаться, если его значение равно нулю, а истинным — при любом значении, отличном от нуля.
? директива IFE аналогично директиве IF анализирует значение логического_выражения. Но теперь для включения фрагмент_программы_1 в объектный модуль требуется, чтобы логическое_выражение имело значение ?ложь?.
Директивы IF и IFE очень удобно использовать при необходимости изменения текста программы в зависимости от некоторых условий.
К примеру, составим макрос для определения в программе области памяти длиной не более 50 и не менее 10 байт (листинг 5).
Листинг 5. Использование условных директив IF и IFE
;prg_13_4.asm masm model small stack 256 def_tab_50 macro len if len GE 50
GOTO exit endif if len LT 10 :exit
EXITM endif reptlen db 0 endm endm .data def_tab_50 15 def_tab_50 5 .code main: mov ax,@data mov ds,ax exit: mov ax,4c00h int 21h end main
ENDIF
Введите и оттранслируйте листинг 5. При этом не забывайте о том, что условные директивы действуют на шаге трансляции, и поэтому результат их работы можно увидеть только после макрогенерации, то есть в листинге программы.
В нем вы увидите, что в результате трансляции строка 18 листинга 5 развернется в пятнадцать нулевых байт, а строка 19 оставит макрогенератор совершенно равнодушным, так как значение фактического операнда в строках 6 и 9 будет ложным. Обратите внимание, что для обработки реакции на ложный результат анализа в условной директиве мы использовали макродирективы EXITM и GOTO.
Другой интересный и полезный вариант применения директив IF и IFE — отладочная печать. Суть здесь в том, что в процессе отладки программы почти всегда возникает необходимость динамически отслеживать состояние определенных программно- аппаратных объектов, в качестве которых могут выступать переменные, регистры микропроцессора и т. п. После этапа отладки отпадает необходимость в таких диагностических сообщениях. Для их устранения нужно корректировать исходный текст программы, после чего ее следует подвергнуть повторной трансляции. Но есть более изящный выход.
Можно определить в программе некоторую переменную, к примеру debug, и использовать ее совместно с условными директивами IF или IFE. К примеру, ... debug equ 1 ...
.code ... if debug
;любые команды и директивы ассемблера ;(вывод на печать или монитор) endif
На время отладки и тестирования программы вы можете заключить отдельные участки кода в своеобразные операторные скобки в виде директив IF и ENDIF (строки 6-9 последнего фрагмента), которые реагируют на значение логической переменной debug. При значении debug = 0 транслятор полностью проигнорирует текст внутри этих условных операторных скобок; при debug = 1, наоборот, будут выполнены все действия, описанные внутри них.
Директивы IFDEF и IFNDEF
Синтаксис этих директив следующий: IF(N)DEF символическое_имя фрагмент_программы_1
ELSE фрагмент_программы_2 ENDIF
Данные директивы позволяют управлять трансляцией фрагментов программы в зависимости от того, определено или нет в программе некоторое символическое_имя. Директива IFDEF проверяет, описано или нет в программе символическое_имя, и если это так, то в объектный модуль помещается фрагмент_программы_1. В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_2.
Если же директивы ELSE нет (и символическое_имя в программе не описано), то вся часть программы между директивами IF и ENDIF игнорируется и в объектный модуль не включается.
Действие IFNDEF обратно IFDEF. Если символического_имени в программе нет, то транслируется фрагмент_программы_1. Если оно присутствует, то при наличии ELSE транслируется фрагмент_программы_2. Если ELSE отсутствует, а символическое_имя в программе определено, то часть программы, заключенная между IFNDEF и ENDIF, игнорируется.
В качестве примера рассмотрим ситуацию, когда в trialтный модуль программы должен быть включен один из трех фрагментов trial Какой из трех фрагментов будет включен в объектный модуль, зависит от значения некоторого идtrialикатора switch: ? если switch = 0, то сгенерировать фрагмент для вычисления выражения ? y = x*2**n;
? если switch = 1, то сгенерировать фрагмент для вычисления выражения ? y = x/2**n;
? если switch не определен, то ничего не генерировать.
Соответствующий фрагмент исходной программы может выглядеть так: ifndef sw ;если sw не определено, то выйти из макроса
EXITM else ;иначе — на вычисление mov cl,n ife sw sal x,cl ;умножение на степень 2 сдвигом влево else sar x,cl ;деление на степень 2 сдвигом вправо endif endif
Как видим, эти директивы логически связаны с директивами IF и IFE, то есть их можно применять в тех же самых случаях, что и последние.
Есть еще одна интересная возможность использования этих директив. На уроке 4 мы обсуждали формат командной строки и говорили об опциях, которые в ней можно задавать. Вспомните одну из опций командной строки TASM — опцию
/dидентификатор=значение.
Ее использование дает возможность управлять значением идентификатора прямо из командной строки транслятора, не изменяя при этом текста программы.
В качестве примера рассмотрим листинг 6, в котором мы попытаемся с помощью макроса контролировать процесс резервирования и инициализации некоторой области памяти в сегменте данных.
Листинг 6. Инициализация значения идентификатора из командной строки ;prg_13_5.asm masm model small stack 256 def_tab_50 macro len ifndef len display "size_m не определено, задайте значение 10else if len GE 50 GOTO exit endif if len LT 10 :exit EXITM endif reptlen db 0 endm endif endm ;size_m=15 .data def_tab_50 size_m
.code main: mov ax,@data mov ds,ax exit: mov ax,4c00h int 21h end main
Запустив этот пример на трансляцию, вы получите сообщение о том, что забыли определить значение переменной size_m. После этого попробуйте два варианта действий: 1. Определите где-то в начале исходного текста программы значение этой переменной с помощью equ:
size_m equ 15
2. Запустите программу на трансляцию командной строкой вида 3. tasm /dsize_m=15 /zi prg_13_2,,, В листинге 6 мы использовали еще одну возможность транслятора — директиву display, с помощью которой можно формировать пользовательское сообщение в процессе трансляции программы.
Директивы IFB и IFNB
Синтаксис этих директив trialющий: IF(N)B аргумент фрагмент_программы_1
ELSE фрагмент_программы_2 ENDIF
Данные директивы используются для проверки фактических параметров, передаваемых в макрос. При вызове макрокоманды они анализируют значение аргумента, и в зависимости от того, равно оно пробелу или нет, транслируется либо фрагмент_программы_1, либо фрагмент_программы_1. Какой именно фрагмент будет выбран, зависит от кода директивы: ? Директива IFB проверяет равенство аргумента пробелу. В качестве аргумента могут выступать имя или число.
? Если его значение равно пробелу (то есть фактический аргумент при вызове макрокоманды не был задан), то транслируется и помещается в объектный модуль фрагмент_программы_1.
? В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_1. Если же директивы ELSE нет, то при равенстве аргумента пробелу вся часть программы между директивами IFB и ENDIF игнорируется и в объектный модуль не включается.
? Действие IFNB обратно IFB. Если значение аргумента в программе не равно пробелу, то транслируется фрагмент_программы_1.
? В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_1. Если же директивы ELSE нет, то вся часть программы (при неравенстве аргумента пробелу) между директивами IFNB и ENDIF игнорируется и в объектный модуль не включается.
В качестве типичного примера применения этих директив предусмотрим строки в макроопределении, которые будут проверять, указывается ли фактический аргумент при вызове соответствующей макрокоманды:
show ifb display exitm endif ... endm macro reg
"не задан регистр"
Если теперь в сегменте кода вызвать макрос show без аргументов, то будет выведено сообщение о том, что не задан регистр и генерация макрорасширения будет прекращена директивой exitm.
Директивы IFIDN, IFIDNI, IFDIF и IFDIFI
Эти директивы позволяют не просто проверить наличие или значение аргументов макрокоманды, но и выполнить идентификацию аргументов как строк символов.
Синтаксис этих директив: IFIDN(I)аргумент_1,аргумент_2 фрагмент_программы_1
В этих директивах проверяются аргумент_1 и аргумент_2 как строки символов. Какой именно код — фрагмент_программы_1 или фрагмент_программы_1 — будет транслироваться по результатам сравнения, зависит от кода директивы.
Парность этих директив объясняется тем, что они позволяют учитывать либо не учитывать различие строчных и прописных букв. Так, директивы IFIDNI и IFDIFI игнорируют это различие, а IFIDN и IFDIF — учитывают.
Директива IFIDN(I) сравнивает символьные значения аргумент_1 и аргумент_2.
Если результат сравнения положительный, то фрагмент_программы_1 транслируется и помещается в объектный модуль.
В противном случае, при наличии директивы ELSE, в объектный код помещается фрагмент_программы_1.
Если же директивы ELSE нет, то вся часть программы между директивами IFIDN(I) и ENDIF игнорируется и в объектный модуль не включается.
Действие IFDIF(I) обратно IFIDN(I).
Если результат сравнения отрицательный (строки не совпадают), транслируется фрагмент_программы_1.
В противном случае все происходит аналогично рассмотренным ранее директивам.
Как мы уже упоминали, эти директивы удобно применять для проверки фактических аргументов макрокоманд.
К примеру, проверим, какой из регистров — al или ah — передан в макрос в качестве параметра (проверка проводится без учета различия строчных и прописных букв): show macro rg ifdifi , goto M_al else ifdifi , goto M_ah else exitm endif endif :M_al ... :M_ah ... endm ENDIF
Вложенность директив условной трансляции
Как мы неоднократно видели в приведенных выше примерах, TASM допускает вложенность условных директив компиляции. Более того, так как вложенность требуется довольно часто, TASM предоставляет набор дополнительных директив формата ELSEIFXXX, которые заменяют последовательность подряд идущих ELSE и IFXXX в структуре: IFXXX ;
ELSE IFXXX
;... ENDIF ENDIF
Эту последовательность условных директив можно заменить эквивалентной последовательностью дополнительных директив: IFXXX ;...
ELSEIFXXX ;...
ENDIF
Наличие xxx в ELSEXXX говорит о том, что каждая из директив IF, IFB, IFIDN и т. д. имеет аналогичную директиву ELSEIF, ELSEIFB, ELSEIFIDN и т. д.
В конечном итоге это улучшает читаемость кода. В последнем примере фрагмента макроса, проверяющем, имя какого регистра было передано в макрос, наблюдается подобная ситуация. Последовательность ELSE и IFDIFI можно записать так, как в строке 4: showmacro rg ifdifi , goto M_al elseifdifi , goto M_ah else exitm endif :M_al ... :M_ah ... endm
Директивы генерации ошибок
В языке TASM есть ряд директив, называемых директивами генерации пользовательской ошибки. Их можно рассматривать и как самостоятельное средство, и как метод, расширяющий возможности директив условной компиляции. Они предназначены для обнаружения различных ошибок в программе, таких как неопределенные метки или пропуск параметров макроса.
Директивы генерации пользовательской ошибки по принципу работы можно разделить на два типа: ? безусловные директивы, генерирующие ошибку трансляции без проверки каких-либо условий;
? условные директивы, генерирующие ошибку трансляции после проверки определенных условий.
Большинство директив генерации ошибок имеют два обозначения, хотя принцип их работы одинаков. Второе название отражает их сходство с директивами условной компиляции. При дальнейшем обсуждении такие парные директивы будут приводиться в скобках.
Безусловная генерация пользовательской ошибки
К безусловным директивам генерации пользовательской ошибки относится только одна директива — это ERR (.ERR).
Данная директива, будучи вставлена в текст программы, безусловно приводит к генерации ошибки на этапе трансляции и удалению объектного модуля. Она очень эффективна при ее использовании с директивами условной компиляции или в теле макрокоманды с целью отладки.
К примеру, эту директиву можно было бы вставить в ту ветвь программы (в последнем рассмотренном нами макроопределении), которая выполняется, если указанный в качестве аргумента регистр отличен от al и ah: show macro rg ifdifi , goto M_al else ifdifi , goto M_ah else
.Err endif endif ... endm
Если после определенного таким образом макроопределения в сегменте кода вызвать макрокоманду show с фактическим параметром, отличным от имен регистров ah или al, будет сгенерирована ошибка компиляции (с текстом “User error”), сам процесс компиляции прекращен и, естественно, объектный модуль создан не будет.
Остальные директивы являются условными, так как их поведение определяют некоторые условия.
Условная генерация пользовательской ошибки
Набор условий, на которые реагируют директивы условной генерации пользовательской ошибки, такой же, как и у директив условной компиляции. Поэтому и количество этих директив такое же. К их числу относятся следующие директивы: ? .ERRB (ERRIFB) и .ERRNB (ERRIFNB)
? .ERRDEF (ERRIFDEF) и .ERRNDEF (ERRIFNDEF) ? .ERRDIF (ERRIFDIF) и .ERRIDN (ERRIFIDN)
? .ERRE (ERRIFE) и .ERRNZ (ERRIF)
Принцип их работы ясен, поэтому рассматривать их мы будем очень кратко. Заметим только, что как и директивы условной компиляции, использовать большинство директив условной генерации пользовательской ошибки можно как в макроопределениях, так и в любом месте программы.
Директивы .ERRB (ERRIFB) и .ERRNB (ERRIFNB)
Синтаксис директив: .ERRB (ERRIFB) — генерация пользовательской ошибки, если пропущено;
.ERRNB (ERRIFNB) — генерация пользовательской ошибки, если присутствует.
Данные директивы применяются для генерации ошибки трансляции в зависимости от того, задан или нет при вызове макрокоманды фактический аргумент, соответствующий формальному аргументу в заголовке макроопределения с именем .
По принципу действия эти директивы полностью аналогичны соответствующим директивам условной компиляции IFB и IFNB. Их обычно используют для проверки задания параметров при вызове макроса. Строка имя_формального_аргумента должна быть заключена в угловые скобки.
К примеру, определим обязательность задания фактического аргумента, соответствующего формальному аргументу rg, в макросе show: show macro rg
;если rg в макрокоманде не будет задан, ;то завершить компиляцию
.errb
;текст макроопределения ;... endm
Директивы .ERRDEF (ERRIFDEF) и .ERRNDEF (ERRIFNDEF)
Синтаксис директив: .ERRDEF (ERRIFDEF) символическое_имя — если указанное имя определено до выдачи этой директивы в программе, то генерируется пользовательская ошибка.
.ERRNDEF(ERRIFNDEF) символическое_имя — если указанное символическое_имя не определено до момента обработки транслятором данной директивы, то генерируется пользовательская ошибка. Данные директивы генерируют ошибку трансляции в зависимости от того, определено или нет некоторое символическое_имя в программе.
Не забывайте о том, что компилятор TASM по умолчанию формирует объектный модуль за один проход исходного текста программы. Следовательно, директивы .ERRDEF (ERRIFDEF) и .ERRNDEF (ERRIFNDEF) отслеживают факт определения символического_имени только в той части исходного текста, которая находится до этих директив.
Директивы .ERRDIF (ERRIFDIF) и .ERRIDN (ERRIFIDN)
Синтаксис директив: .ERRDIF (ERRIFDIF) , — директива, генерирующая пользовательскую ошибку, если две строки посимвольно не совпадают. Строки могут быть символическими именами, числами или выражениями и должны быть заключены в угловые скобки. Аналогично директиве условной компиляции IFDIF, при сравнении учитывается различие прописных и строчных букв. .ERRIDN (ERRIFIDN) , — директива, генерирующая пользовательскую ошибку, если строки посимвольно идентичны. Строчное и прописное написание одной и той же буквы воспринимается как разные символы.
Для того чтобы игнорировать различия строчных и прописных букв, существуют аналогичные директивы: ERRIFDIFI , — то же, что и ERRIFDIF, но игнорируется различие строчных и прописных букв при сравнении и .
ERRIFIDNI , — то же, что и ERRIFIDN, но игнорируется различие строчных и прописных букв при сравнении и .
Данные директивы, как и соответствующие им директивы условной компиляции, удобно применять для проверки передаваемых в макрос фактических параметров.
Директивы .ERRE (ERRIFE) и .ERRNZ (ERRIF)
Синтаксис директив: .ERRE (ERRIFE) константное_выражение — директива вызывает пользовательскую ошибку, если константное_выражение ложно (равно нулю). Вычисление константного_выражения должно приводить к абсолютному значению, и это выражение не может содержать компонентов, являющихся ссылками вперед.
.ERRNZ(ERRIF) константное_выражение — директива вызывает пользовательскую ошибку, если константное_выражение истинно (не равно нулю). Вычисление константного_выражения должно приводить к абсолютному значению и не может содержать компонентов, являющихся ссылками вперед.
Константные выражения в условных директивах
Как вы успели заметить, во многих условных директивах в формировании условия участвуют выражения. Результат вычисления этого выражения обязательно должен быть константой. Хотя его компонентами могут быть и символические параметры, но их сочетание в выражении должно давать абсолютный результат.
К примеру: .data mas db ... len dd ... ...
.code
...
.erre (len-mas) lt 10 если длина меньше 10 байт ...
;генерация ошибки, ;области mas trial того, выражение не должно содержать компоненты, которые транслятор еще не обработал к тому месту программы, где находится условная директива.
Также мы отметили, что логические результаты ?истина? и ?ложь? являются условными в том смысле, что ноль соответствует логическому результату ?ложь?, а любое ненулевое значение — ?истине?.
Но в языке ассемблера существуют операторы, которые позволяют сформировать и ?чисто логический? результат. Это так называемые операторы отношений, выражающие отношение двух значений или константных выражений.
В контексте условных директив вместе с операторами отношений можно рассматривать и логические операторы. Результатом работы и тех, и других может быть одно из двух значений: ? истина — число, которое содержит двоичные единицы во всех разрядах; ? ложь — число, которое содержит двоичные нули во всех разрядах.
Операторы, которые можно применять в выражениях условных директив и которые формируют логические результаты, приведены в табл. 1 и 2.
Таблица 1. Операторы отношений
Оператор/
EQ (equal) — равно NE (not equal) — не равно LT (less than) — меньше
LE (less or equal) — меньше или равно GT (greater than) — больше
GE (greater or equal) — больше или равно
Синтаксис выражение_1 EQ выражение_2
Выражение_1 NE выражение_2
Выражение_1 LT выражение_2
Выражение_1 LE выражение_2
Выражение_1 GT выражение_2
Выражение_1 GE выражение_2
Результат отношения истина — если выражение_1 равно выражение_2
Истина — если выражение_1 не равно выражение_2
Истина — если выражение_1 меньше выражение_2
Истина — если выражение_1 меньше или равно выражение_2
Истина — если выражение_1 больше выражение_2
Истина — если выражение_1 больше или равно выражение_2
Таблица 2. Логические операторы
Оператор
NOT — логическое отрицание
AND — логическое И
OR — логическое ИЛИ XOR — исключающее ИЛИ Синтаксис
NOT выражение выражение_1 AND выражение_2 выражение_1 OR выражение_2 выражение_1 XOR выражение_2
Вывод
Истина — если выражение ложно; ложь — если выражение истинно
Истина — если выражение_1 и выражение_2 истинны
Истина — если выражение_1 или выражение_2 истинны
Истина — если выражение_1 = (NOT выражение_2)
Дополнительное управление трансляцией
TASM предоставляет средства для вывода текстового сообщения во время трансляции программы — директивы DISPLAY и %OUT. С их помощью можно, при необходимости, следить за ходом трансляции.
К примеру: display недопустимые аргументы макрокоманды
...
%out недопустимое имя регистра
В результате обработки этих директив на экран будут выведены тексты сообщений. Если эти директивы использовать совместно с директивами условной компиляции, то, к примеру, можно отслеживать путь, по которому осуществляется трансляция исходного текста программы.
Самостоятельная работа. Опции транслятора TASM и редактора связей TLINK В данном документе приведены опции командной строки: ? для транслятора Turbo Assembler фирмы Borland (TASM) (версия 3.0 и выше); ? редактора связей TLINK.
Во избежание несовместимости используйте программы TLINK и TASM одной версии.
Опции транслятора TASM
/a, /s
/c
/dимя_идентификатора[=значение]
/e, /r
/h, /?
/іпуть
/jдиректива_TASM
/khn
/l, /la
/a — сегменты в объектном файле должны быть размещены в алфавитном порядке;
/s — сегменты в объектном файле следуют в порядке их описания в программе
Указание на включение в файл листинга с информацией о перекрестных ссылках
Определяет идентификатор. Это эквивалент директивы ассемблера =, как если бы она была записана в начале исходного текста программы
/e — генерация инструкций эмуляции операций с плавающей точкой;
/r — разрешение трансляции действительных инструкций с плавающей точкой, которые должны выполняться реальным арифметическим сопроцессором
Вывод на экран справочной информации. Это эквивалентно запуску TASM без параметров
Задает путь к включаемому по директиве INCLUDE файлу. Синтаксис аргумента ?путь? такой же, как для команды PATH файла autoexec.bat
Определяет директивы, которые будут транслироваться перед началом трансляции исходного файла программы на ассемблере. В директиве не должно быть аргументов
Задает максимальное количество идентификаторов, которое может содержать исходная программа, то есть фактически задается размер таблицы символов транслятора.
По умолчанию программа может содержать до 16384 идентификаторов. Это значение можно увеличить (но не более чем до 32 768) или уменьшить до n. Сигналом к тому, что необходимо использовать данный параметр, служит появление сообщения “Out of hash space” (“Буферное пространство исчерпано”)
/l — указывает на необходимость создания файла листинга, даже если он не ?заказывается? в командной строке;
/la — показать в листинге код, вставляемый транслятором для организации интерфейса с языком высокого уровня по директиве MODEL
/ml, /mx, /mu
/mvn
/mn
/n
/os, /o, /op, /oi
/p
/q
/t
/w0, /w1, /w2
/w-xxx, /w xxx
/x
/ml — различать во всех идентификаторах прописные и строчные буквы; /mx — различать строчные и прописные символы во внешних и общих идентификаторах. Это важно при компоновке с программами на тех языках высокого уровня, в которых строчные и прописные символы в идентификаторах различаются;
/mu — воспринимать все символы идентификаторов как прописные
Определение максимальной длины идентификаторов. Минимальное значение n равно 12
Установка количества (n) проходов транслятора TASM. По умолчанию транслятор выполняет один проход. Максимально при необходимости можно задать выполнение до 5 проходов
Не выдавать в файле листинга таблицы идентификаторов (в таких таблицах содержатся все имена идентификаторов и их значения)
Генерация оверлейного кода
Проверять наличие кода с побочными эффектами при работе в защищенном режиме
Удаление из объектной программы лишней информации, ненужной на этапе компоновки
Подавление вывода всех сообщений при условном ассемблировании, кроме сообщений об ошибках (то есть тестирование программы на предмет выявления синтаксических ошибок)
Генерация предупреждающих сообщений разного уровня полноты: w0 — сообщения не генерируются;
w1, w2 — сообщения генерируются
Генерация предупреждающих сообщений класса xxx (эти же функции выполняют директивы WARN и NOWARN). Знак ?-? означает ?запретить генерацию сообщений класса xxx?. Знак ? ? означает ?разрешить генерацию сообщений класса xxx?. Классы предупреждающех сообщений обозначаются идентификатором из трех символов: ALN — выравнивание сегмента в памяти;
ASS — подразумевается использование 16-разрядного сегмента; BRK — требуются квадратные скобки;
ICG — неэффективная генерация кода; LCO — переполнение счетчика адреса; OPI — открытый блок условия IF; OPP — открытая процедура;
OPS — открытый сегмент;
OVF — арифметическое переполнение; PDC — конструкция, зависящая от прохода;
PRO — запись в память в защищенном режиме требует переопределения регистра CS. Использование этого класса предупреждений имеет смысл при написании программ, работающих в защищенном режиме (под Windows). На уроке 16 обсуждался момент, связанный с тем, что в защищенном режиме запрещено производить запись в сегмент кода. Класс предупреждений PRO призван уже на стадии трансляции программы предупредить такого рода ошибки;
RES — предупреждение о резервируемом слове;
TPI — предупреждение о недопустимости в Turbo Pascal; /W — разрешить все сообщения;
/W- — запретить все сообщения
Включить в листинг все блоки условного ассемблирования для директив IF, IFNDEF, IFDEF и т. п., в том числе и невыполняющиеся
/z
/zi, /zd, /zn
При возникновении ошибок наряду с сообщением о них выводить соответствующие строки текста
/zi — включить в объектный файл информацию для отладки;
/zd — поместить в объектный файл информацию о номерах строк, что необходимо для работы отладчика на уровне исходного текста программы;
/zn — запретить помещение в объектный файл отладочной информации.
Опции компоновщика (редактора связей) TLINK
/x /m
/s
/l /n /c /v /3 /d
/t
Не создавать файл карты (map) Создать файл карты
То же, что /m, но дополнительно в файл карты включается информация о сегментах (адрес, длина в байтах, класс, имя сегмента и т. д.)
Создать раздел в файле карты с номерами строк
Игнорировать библиотеки, указываемые другими компиляторами
Различать строчные и прописные буквы в идентификаторах (в том числе и внешних) Включить отладочную информацию в выполняемый файл
Поддержка 32-битного кода
Предупреждать о дублировании символов в компонуемых библиотеках
Создать файл типа .com (по умолчанию .exe)
Лекция 12. Команды пересылки данных (1 пара)
Для удобства практического применения и отражения их специфики команды данной группы удобнее рассматривать в соответствии с их функциональным назначением, согласно которому их можно разбить на следующие группы команд: ? пересылки данных общего назначения ? ввода-вывода в порт
? работы с адресами и указателями ? преобразования данных
? работы со стеком
Команды пересылки данных общего назначения
К этой группе относятся следующие команды: mov , xchg , mov - это основная команда пересылки данных. Она реализует самые разнообразные варианты пересылки.
Отметим особенности применения этой команды: ? командой mov нельзя осуществить пересылку из одной области памяти в другую. Если такая необходимость возникает, то нужно использовать в качестве промежуточного буфера любой доступный в данный момент регистр общего назначения.
? К примеру, рассмотрим фрагмент программы для пересылки байта из ячейки fls в ячейку fld: masm model small .data fls db 5 fld db ? .code start: ... mov al,fls mov fld,al ... end start
? нельзя загрузить в сегментный регистр значение непосредственно из памяти. Поэтому для выполнения такой загрузки нужно использовать промежуточный объект. Это может быть регистр общего назначения или стек. Если вы посмотрите листинги 3.1 и 5.1, то увидите в начале сегмента кода две команды mov, выполняющие настройку сегментного регистра ds. При этом изза невозможности загрузить впрямую в сегментный регистр значение адреса сегмента, содержащееся в предопределенной переменной @data, приходится использовать регистр общего назначения ax;
? нельзя переслать содержимое одного сегментного регистра в другой сегментный регистр. Это объясняется тем, что в системе команд нет соответствующего кода операции. Но необходимость в таком действии часто возникает. Выполнить такую пересылку можно, используя в качестве промежуточных все те же регистры общего назначения. Вот пример инициализации регистра es значением из регистра ds:
mov ax,ds mov es,ax
? Но есть и другой, более красивый способ выполнения данной операции — использование стека и команд push и pop:
push ds ;поместить значение регистра ds в стек pop es ;записать в es число из стека
? нельзя использовать сегментный регистр cs в качестве операнда назначения. Причина здесь простая. Дело в том, что в архитектуре микропроцессора пара cs:ip всегда содержит адрес команды, которая должна выполняться следующей. Изменение командой mov содержимого регистра cs фактически означало бы операцию перехода, а не пересылки, что недопустимо.
Для двунаправленной пересылки данных применяют команду xchg. Для этой операции можно, конечно, применить последовательность из нескольких команд mov, но изза того, что операция обмена используется довольно часто, разработчики системы команд микропроцессора посчитали нужным ввести отдельную команду обмена xchg. Естественно, что операнды должны иметь один тип. Не допускается (как и для всех команд ассемблера) обменивать между собой содержимое двух ячеек памяти. К примеру, xchg ax,bx ;обменять содержимое регистров ax и bx xchg ax,word ptr [si] ;обменять содержимое регистра ax
;и слова в памяти по адресу в [si]
Команды ввода-вывода в порт
На уроке 6 при обсуждении вопроса о том, где могут находиться операнды машинной команды, мы упоминали порт ввода- вывода.
Посмотрите на рис. 1. На нем показана сильно упрощенная, концептуальная схема управления оборудованием компьютера.
Рис. 1. Концептуальная схема управления оборудованием компьютера Как видно из рис. 1, самым нижним уровнем является уровень BIOS, на котором работа с оборудованием ведется напрямую через порты. Тем самым реализуется концепция независимости от оборудования. При замене оборудования необходимо будет лишь подправить соответствующие функции BIOS, переориентировав их на новые адреса и логику работы портов.
Принципиально управлять устройствами напрямую через порты несложно. Сведения о номерах портов, их разрядности, формате управляющей информации приводятся в техническом описании устройства. Необходимо знать лишь конечную цель своих действий, алгоритм, в соответствии с которым работает конкретное устройство, и порядок программирования его портов. То есть, фактически, нужно знать, что и в какой последовательности нужно послать в порт (при записи в него) или считать из него (при чтении) и как следует трактовать эту информацию. Для этого достаточно всего двух команд, присутствующих в системе команд микропроцессора: in аккумулятор,номер_порта — ввод в аккумулятор из порта с номером номер_порта;
out порт,аккумулятор — вывод содержимого аккумулятора в порт с номером номер_порта.
Команды работы с адресами и указателями памяти
При написании программ на ассемблере производится интенсивная работа с адресами операндов, находящимися в памяти. Для поддержки такого рода операций есть специальная группа команд, в которую входят следующие команды: lea назначение,источник — загрузка эффективного адреса;
lds назначение,источник — загрузка указателя в регистр сегмента данных ds;
les назначение,источник — загрузка указателя в регистр дополнительного сегмента данных es; lgs назначение,источник — загрузка указателя в регистр дополнительного сегмента данных gs; lfs назначение,источник — загрузка указателя в регистр дополнительного сегмента данных fs; lss назначение,источник — загрузка указателя в регистр сегмента стека ss.
Kotrial lea похожа на команду mov тем, что она также производит пересылку. Однако, обратите внимание, команда lea производит пересылку не данных, а эффективного адреса данных (то есть смещения данных относительно начала сегмента данных) в регистр, указанный операндом назначение. Часто для выполнения некоторых действий в программе недостаточно знать значение одного лишь эффективного адреса данных, а необходимо иметь полный указатель на данные. Вы помните, что полный указатель на данные состоит из сегментной составляющей и смещения.
Все остальные команды этой группы позволяют получить в паре регистров такой полный указатель на операнд в памяти. При этом имя сегментного регистра, в который помещается сегментная составляющая адреса, определяется кодом операции. Соответственно, смещение помещается в регистр общего назначения, указанный операндом назначение.
Но не все так просто с операндом источник. На самом деле, в команде в качестве источника нельзя указывать непосредственно имя операнда в памяти, на который мы бы хотели получить указатель. Предварительно необходимо получить само значение полного указателя в некоторой области памяти и указать в команде получения полного адреса имя этой области. Для выполнения этого действия необходимо вспомнить директивы резервирования и инициализации памяти.
При применении этих директив возможен частный случай, когда в поле операндов указывается имя другой директивы определения данных (фактически, имя переменной). В этом случае в памяти формируется адрес этой переменной. Какой адрес будет сформирован (эффективный или полный), зависит от применяемой директивы. Если это dw, то в памяти формируется только 16-битное значение эффективного адреса, если же dd — в память записывается полный адрес. Размещение этого адреса в памяти следующее: в младшем слове находится смещение, в старшем — 16-битная сегментная составляющая адреса.
Например, при организации работы с цепочкой символов удобно поместить ее начальный адрес в некоторый регистр и далее в цикле модифицировать это значение для последовательного доступа к элементам цепочки. В листинге 1 производится копирование строки байт str_1 в строку байт str_2. В строках 12 и 13 в регистры si и di загружаются значения эффективных адресов переменных str_1 и str_2.
В строках 16 и 17 производится пересылка очередного байта из одной строки в другую. Указатели на позиции байтов в строках определяются содержимым регистров si и di. Для пересылки очередного байта необходимо увеличить на единицу регистры si и di, что и делается командами сложения inc (строки 18, 19). После этого программу необходимо зациклить до обработки всех символов строки.
Листинг 1. Копирование строки ;---------Prg_7_2.asm--------------- masm model small .data
... str_1 db ?Ассемблер — базовый язык компьютера‘ str_2 db 50 dup (? ?) full_pnt dd str_1 ...
.code start: ... lea si,str_1 lea di,str_2 les bx,full_pnt ;полный указатель на str1 в пару es:bx m1: mov al,[si] mov [di],al inc si inc di
;цикл на метку m1 до пересылки всех символов
... end start
Необходимость использования команд получения полного указателя данных в памяти, то есть адреса сегмента и значения смещения внутри сегмента, возникает, в частности, при работе с цепочками.
В строке 14 листинга 1 в двойном слове full_pnt формируются сегментная часть адреса и смещение для переменной str_1. При этом 2 байта смещения занимают младшее слово full_pnt, а значение сегментной составляющей адреса — старшее слово full_pnt. В строке 14 командой les эти компоненты адреса помещаются в регистры bx и es.
Команды преобразования данных
К этой группе можно отнести множество команд микропроцессора, но большинство из них имеют те или иные особенности, которые требуют отнести их к другим функциональным группам.
Поэтому из всей совокупности команд микропроцессора непосредственно к командам преобразования данных можно отнести только одну команду: xlat [адрес_таблицы_перекодировки]
Это очень интересная и полезная команда. Ее действие заключается в том, что она замещает значение в регистре al другим байтом из таблицы в памяти, расположенной по адресу, указанному операндом адрес_таблицы_перекодировки.
Слово ?таблица? весьма условно — по сути это просто строка байт. Адрес байта в строке, которым будет производиться замещение содержимого регистра al, определяется суммой (bx) (al), то есть содержимое al выполняет роль индекса в байтовом массиве.
При работе с командой xlat обратите внимание на следующий тонкий момент. Несмотря на то, что в команде указывается адрес строки байт, из которой должно быть извлечено новое значение, этот адрес должен быть предварительно загружен (например, с помощью команды lea) в регистр bx. Таким образом, операнд адрес_таблицы_перекодировки на самом деле не нужен (необязательность операнда показана заключением его в квадратные скобки). Что касается строки байт (таблицы перекодировки), то она представляет собой область памяти размером от 1 до 255 байт (диапазон числа без знака в 8-битном регистре).
В качестве иллюстрации работы данной команды мы рассмотрим программу, которая преобразует двузначное шестнадцатеричное число, вводимое с клавиатуры (то есть в символьном виде), в эквивалентное двоичное представление в регистре al. Ниже (листинг 2) приведен вариант этой программы с использованием команды xlat.
Листинг 2. Использование таблицы перекодировки
;---------Prg_7_3.asm----------------------;Программа преобразования двузначного шестнадцатеричного числа
;в двоичное представление с использованием команды xlat. ;Вход: исходное шестнадцатеричное число; вводится с клавиатуры. ;Выход: результат преобразования в регистре al.
.data ;сегмент данных message db ?Введите две шестнадцатеричные цифры,$‘ tabl db 48 dup (0),0,1,2,3,4,5,6,7,8,9, 8 dup (0), db 0ah,0bh,0ch,odh,0eh,0fh,27 dup (0) db 0ah,0bh,0ch,odh,0eh,0fh, 153 dup (0)
.stack 256 ;сегмент стека .code
;начало сегмента кода proc main ;начало процедуры main mov ax,@data ;физический адрес сегмента данных в регистр ax mov ds,ax ;ax записываем в ds lea bx,tabl ;загрузка адреса строки байт в регистр bx mov ah,9 mov dx,offset message int 21h ;вывести приглашение к вводу xor ax,ax ;очистить регистр ax mov ah,1h ;значение 1h в регистр ah int 21h ;вводим первую цифру в al xlat ;перекодировка первого введенного символа в al mov dl,al shl dl,4 ;сдвиг dl влево для освобождения места для младшей цифры int 21h ;ввод второго символа в al xlat ;перекодировка второго введенного символа в al add al,dl ;складываем для получения результата mov ax,4c00h ;пересылка 4c00h в регистр ax int 21h ;завершение программы endp main ;конец процедуры main code ends ;конец сегмента кода endmain ;конец программы с точкой входа main
Сама по себе программа проста; сложность вызывает обычно формирование таблицы перекодировки. Обсудим этот момент подробнее.
Прежде всего нужно определиться с значениями тех байтов, которые вы будете изменять. В нашем случае это символы шестнадцатеричных цифр. Сконструируем в сегменте данных таблицу, в которой на места байтов, соответствующих символам шестнадцатеричных цифр, помещаем их новые значения, то есть двоичные эквиваленты шестнадцатеричных цифр. Строки 8-10 листинга 2 демонстрируют, как это сделать. Байты этой таблицы, смещения которых не совпадают со значением кодов шестнадцатеричных цифр, нулевые. Таковыми являются первые 48 байт таблицы, промежуточные байты и часть в конце таблицы.
Желательно определить все 256 байт таблицы. Дело в том, что если мы ошибочно поместим в al код символа, отличный от символа шестнадцатеричной цифры, то после выполнения команды xlat получим непредсказуемый результат. В случае листинга 2 это будет ноль, что не совсем корректно, так как непонятно, что же в действительности было в al — код символа ?0? или что-то другое.
Поэтому, наверное, есть смысл здесь поставить ?защиту от дурака?, поместив в неиспользуемые байты таблицы какой-нибудь определенный символ. После каждого выполнения xlat нужно будет просто контролировать значение в al на предмет совпадения с этим символом, и если оно произошло, выдавать сообщение об ошибке.
После того как таблица составлена, с ней можно работать. В сегменте команд строка 18 инициализирует регистр bx значением адреса таблицы tabl. Далее все очень просто. Поочередно вводятся символы двух шестнадцатеричных цифр, и производится их перекодировка в соответствующие двоичные эквиваленты.
Команды работы со стеком
Эта группа представляет собой набор специализированных команд, ориентированных на организацию гибкой и эффективной работы со стеком.
Стек — это область памяти, специально выделяемая для временного хранения данных программы. Важность стека определяется тем, что для него в структуре программы предусмотрен отдельный сегмент. На тот случай, если программист забыл описать сегмент стека в своей программе, компоновщик tlink выдаст предупреждающее сообщение.
Для работы со стеком предназначены три регистра: ? ss — сегментный регистр стека;
? sp/esp — регистр указателя стека;
? bp/ebp — регистр указателя базы кадра стека.
Размер стека зависит от режима работы микропроцессора и ограничивается 64 Кбайт (или 4 Гбайт в защищенном режиме).
В каждый момент времени доступен только один стек, адрес сегмента которого содержится в регистре ss. Этот стек называется текущим. Для того чтобы обратиться к другому стеку (?переключить стек?), необходимо загрузить в регистр ss другой адрес. Регистр ss автоматически используется процессором для выполнения всех команд, работающих со стеком.
Перечислим еще некоторые особенности работы со стеком: ? запись и чтение данных в стеке осуществляется в соответствии с принципом LIFO (Last In First Out — ?последним пришел, первым ушел?);
? по мере записи данных в стек последний растет в сторону младших адресов. Эта особенность заложена в алгоритм команд работы со стеком;
? при использовании регистров esp/sp и ebp/bp для адресации памяти ассемблер автоматически считает, что содержащиеся в нем значения представляют собой смещения относительно сегментного регистра ss.
В общем случае стек организован так, как показано на рис. 2.
Рис. 2. Концептуальная схема организации стека Для работы со стеком предназначены регистры ss, esp/sp и ebp/bp.
Эти регистры используются комплексно, и каждый из них имеет свое функциоtrialое назначение. Регистр esp/sp всегда указывает на вершину стека, то есть содержит смещение, по которому в стек был занесен последний элемент. Команды работы со стеком неявно изменяют этот регистр так, чтобы он указывал всегда на последний записанный в стек элемент. Если стек пуст, то значение esp равно адресу последнего байта сегмента, выделенного под стек.
При занесении элемента в стек процессор уменьшает значение регистра esp, а затем записывает элемент по адресу новой вершины.
При извлечении данных из стека процессор копирует элемент, расположенный по адресу вершины, а затем увеличивает значение регистра указателя стека esp.
Таким образом, получается, что стек растет вниз, в сторону уменьшения адресов.
Что делать, если нам необходимо получить доступ к элементам не на вершине, а внутри стека? Для этого применяют регистр ebp. Регистр ebp — регистр указателя базы кадра стека.
Например, типичным приемом при входе в подпрограмму является передача нужных параметров путем записи их в стек. Если подпрограмма тоже активно работает со стеком, то доступ к этим параметрам становится проблематичным. Выход в том, чтобы после записи нужных данных в стек сохранить адрес вершины стека в указателе кадра (базы) стека — регистре ebp. Значение в ebp в дальнейшем можно использовать для доступа к переданным параметрам.
Начало стека расположено в старших адресах памяти. На рис. 2 этот адрес обозначен парой ss:ffff. Смещение ffff приведено здесь условно. Реально это значение определяется величиной, которую программист задает при описании сегмента стека в своей программе.
К примеру, для программы в листинге 2 началу стека будет соответствовать пара ss:0100h. Адресная пара ss:ffff — это максимальное для реального режима значение адреса начала стека, так как размер сегмента в нем ограничен величиной 64 Кбайт (0ffffh).
Для организации работы со стеком существуют специальные команды записи и чтения. push источник — запись значения источник в вершину стека.
Интерес представляет алгоритм работы этой команды, который включает следующие действия (рис. 3): ? (sp) = (sp) - 2; значение sp уменьшается на 2;
? значение из источника записывается по адресу, указываемому парой ss:sp.
Рис. 3. Принцип работы команды push pop назначение — запись значения из вершины стека по месту, указанному операндом назначение. Значение при этом ?снимается? с вершины стека.
Алгоритм работы команды pop обратен алгоритму команды push (рис. 4): ? запись содержимого вершины стека по месту, указанному операндом назначение; ? (sp) = (sp) 2; увеличение значения sp.
Рис. 4. Принцип работы команды pop pusha — команда групповой записи в стек.
По этой команде в стек последовательно записываются регистры ax, cx, dx, bx, sp, bp, si, di. Заметим, что записывается оригинальное содержимое sp, то есть то, которое было до выдачи команды pusha (рис. 5).
Рис. 5. Принцип работы команды pusha pushaw — почти синоним команды pusha. В чем разница? На уроке 5 мы обсуждали один из атрибутов сегмента — атрибут разрядности. Он может принимать значение use16 или use32.
Рассмотрим работу команд pusha и pushaw при каждом из этих атрибутов: ? use16 — алгоритм работы pushaw аналогичен алгоритму pusha.
? use32 — pushaw не изменяется (то есть она нечувствительна к разрядности сегмента и всегда работает с регистрами размером в слово — ax, cx, dx, bx, sp, bp, si, di). Команда pusha чувствительна к установленной разрядности сегмента и при указании 32-разрядного сегмента работает с соответствующими 32-разрядными регистрами, то есть eax, ecx, edx, ebx, esp, ebp, esi, edi. pushad — выполняется аналогично команде pusha, но есть некоторые особенности, которые вы можете узнать из ?Справочника команд?.
Следующие три команды выполняют действия, обратные вышеописанным командам: popa;
popaw; popad.
Группа команд, описанная ниже, позволяет сохранить в стеке регистр флагов и записать слово или двойное слово в стеке. Отметим, что перечисленные ниже команды — единственные в системе команд микропроцессора, которые позволяют получить доступ (и которые нуждаются в этом доступе) ко всему содержимому регистра флагов. pushf — сохраняет регистр флагов в стеке.
Работа этой команды зависит от атрибута размера сегмента: ? use16 — в стек записывается регистр flags размером 2 байта; ? use32 — в стек записывается регистр eflags размером 4 байта. pushfw — сохранение в стеке регистра флагов размером в слово. Всегда работает как pushf с атрибутом use16. pushfd — сохранение в стеке регистра флагов flags или eflags в зависимости от атрибута разрядности сегмента (то есть то же, что и pushf).
Аналогично, следующие три команды выполняют действия, обратные рассмотренным выше операциям: popf popfw popfd
И в заключение отметим основные виды операции, когда использование стека практически неизбежно: ? вызов подпрограмм;
? временное сохранение значений регистров; ? определение локальных переменных.
Лекция 13. Команды передачи управления (1 пара)
На предыдущих уроках мы познакомились с некоторыми командами, из которых формируются линейные участки программы. Каждая из них в общем случае выполняет некоторые действия по преобразованию или пересылке данных, после чего микропроцессор передает управление следующей команде. Но очень мало программ работают таким последовательным образом. Обычно в программе есть точки, в которых нужно принять решение о том, какая команда будет выполняться следующей. Это решение может быть
? безусловным — в данной точке необходимо передать управление не той команде, которая идет следующей, а другой, которая находится на некотором удалении от текущей команды;
? условным — решение о том, какая команда будет выполняться следующей, принимается на основе анализа некоторых условий или данных.
Как вы помните, программа представляет собой последовательность команд и данных, занимающих определенное пространство оперативной памяти. Эта пространство памяти может быть либо непрерывным, либо состоять из нескольких фрагментов.
На уроке 5 нами были рассмотрены средства организации фрагментации кода программы и ее данных на сегменты. То, какая команда программы должна выполняться следующей, микропроцессор узнает по содержимому пары регистров cs:(e)ip: ? cs — сегментный регистр кода, в котором находится физический (базовый) адрес текущего сегмента кода;
? eip/ip — регистр указателя команды, в котором находится значение, представляющее собой смещение в памяти следующей команды, подлежащей выполнению, относительно начала текущего сегмента кода.
? Напомню, почему мы записываем регистры eip/ip через косую черту. Какой конкретно регистр будет использоваться, зависит от установленного режима адресации use16 или use32. Если указано use16, то используется ip, если use32, то используется eip.
Таким образом, команды передачи управления изменяют содержимое регистров cs и eip/ip, в результате чего микропроцессор выбирает для выполнения не следующую по порядку команду программы, а команду в некотором другом участке программы. Конвейер внутри микропроцессора при этом сбрасывается.
По принципу действия, команды микропроцессора, обеспечивающие организацию переходов в программе, можно разделить на четыре группы: 1. Команды безусловной передачи управления: o команда безусловного перехода;
o вызова процедуры и возврата из процедуры;
o вызова программных прерываний и возврата из программных прерываний. 2. Команды условной передачи управления: o команды перехода по результату команды сравнения cmp; o команды перехода по состоянию определенного флага;
o команды перехода по содержимому регистра ecx/cx. 3. Команды управления циклом: o команда организации цикла со счетчиком ecx/cx;
o команда организации цикла со счетчиком ecx/cx с возможностью досрочного выхода из цикла по дополнительному условию.
Безусловные переходы
Предыдущее обсуждение выявило некоторые детали механизма перехода. Команды перехода модифицируют регистр указателя команды eip/ip и, возможно, сегментный регистр кода cs. Что именно должно подвергнуться модификации, зависит: ? от типа операнда в команде безусловного перехода (ближний или дальний);
? от указания перед адресом перехода (в команде перехода) модификатора; при этом сам адрес перехода может находиться либо непосредственно в команде (прямой переход), либо в регистре или ячейке памяти (косвенный переход).
Модификатор может принимать следующие значения: ? near ptr — прямой переход на метку внутри текущего сегмента кода. Модифицируется только регистр eip/ip (в зависимости от заданного типа сегмента кода use16 или use32) на основе указанного в команде адреса (метки) или выражения, использующего символ извлечения значения СЧА — $;
? far ptr — прямой переход на метку в другом сегменте кода. Адрес перехода задается в виде непосредственного операнда или адреса (метки) и состоит из 16-битного селектора и 16/32-битного смещения, которые загружаются, соответственно, в регистры cs и ip/eip;
? word ptr — косвенный переход на метку внутри текущего сегмента кода. Модифицируется (значением смещения из памяти по указанному в команде адресу, или из регистра) только eip/ip. Размер смещения 16 или 32 бит;
? dword ptr — косвенный переход на метку в другом сегменте кода. Модифицируются (значением из памяти — и только из памяти, из регистра нельзя) оба регистра, cs и eip/ip. Первое слово/двойное слово этого адреса представляет смещение и загружается в ip/eip; второе/третье слово загружается в cs.
Команда безусловного перехода jmp
Синтаксис команды безусловного перехода jmp [модификатор] адрес_перехода - безусловный переход без сохранения информации о точке возврата.
Адрес_перехода представляет собой адрес в виде метки либо адрес области памяти, в которой находится указатель перехода.
Всего в системе команд микропроцессора есть несколько кодов машинных команд безусловного перехода jmp.
Их различия определяются дальностью перехода и способом задания целевого адреса.
Дальность перехода определяется местоположением операнда адрес_перехода. Этот адрес может находиться в текущем сегменте кода или в некотором другом сегменте. В первом случае переход называется внутрисегментным, или близким, во втором — межсегментным, или дальним. Внутрисегментный переход предполагает, что изменяется только содержимое регистра eip/ip. Можно выделить три варианта внутрисегментного использования команды jmp: ? прямой короткий; ? прямой;
? косвенный.
Процедуры
В языке ассемблера есть несколько средств, решающих проблему дублирования участков программного кода. К ним относятся: ? механизм процедур; ? макроассемблер;
? механизм прерываний.
Процедура, часто называемая также подпрограммой, — это основная функциональная единица декомпозиции (разделения на несколько частей) некоторой задачи.
Процедура представляет собой группу команд для решения конкретной подзадачи и обладает средствами получения управления из точки вызова задачи более высокого уровня и возврата управления в эту точку.
В простейшем случае программа может состоять из одной процедуры. Другими словами, процедуру можно определить как правильным образом оформленную совокупность команд, которая, будучи однократно описана, при необходимости может быть вызвана в любом месте программы.
Для описания последовательности команд в виде процедуры в языке ассемблера используются две директивы: PROC и ENDP.
Синтаксис описания процедуры таков (рис. 1).
Рис. 1. Синтаксис описания процедуры в программе
Из рис. 1 видно, что в заголовке процедуры (директиве PROC) обязательным является только задание имени процедуры. Среди большого количества операндов директивы PROC следует особо выделить [расстояние].
Этот атрибут может принимать значения near или far и характеризует возможность обращения к процедуре из другого сегмента кода. По умолчанию атрибут [расстояние] принимает значение near. Процедура может размещаться в любом месте программы, но так, чтобы на нее случайным образом не попало управление. Если процедуру просто вставить в общий поток команд, то микропроцессор будет воспринимать команды процедуры как часть этого потока и соответственно будет осуществлять выполнение команд процедуры.
Более подробно вопросы, связанные с описанием и использованием процедур в программах ассемблера, рассматриваются на уроках 10 и 14. Примеры использования процедур вы можете посмотреть в приложении 7.
Условные переходы
Микропроцессор имеет 18 команд условного перехода (см. ?Описание команд?). Эти команды позволяют проверить: ? отношение между операндами со знаком (?больше — меньше?); ? отношение между операндами без знака (?выше — ниже?)2;
? состояния арифметических флагов zf, sf, cf, of, pf (но не af).
Команды условного перехода имеют одинаковый синтаксис: jcc метка_перехода
Как видно, мнемокод всех команд начинается с ?j? — от слова jump (прыжок), cc — определяет конкретное условие, анализируемое командой.
Что касается операнда метка_перехода, то эта метка может находится только в пределах текущего сегмента кода, межсегментная передача управления в условных переходах не допускается. В связи с этим отпадает вопрос о модификаторе, который присутствовал в синтаксисе команд безусловного перехода. В ранних моделях микропроцессора (i8086, i80186 и i80286) команды условного перехода могли осуществлять только короткие переходы — на расстояние от -128 до 127 байт от команды, следующей за командой условного перехода. Начиная с модели микропроцессора 80386 это ограничение снято, но, как видите, только в пределах текущего сегмента кода.
Для того чтобы принять решение о том, куда будет передано управление командой условного перехода, предварительно должно быть сформировано условие, на основании которого и будет приниматься решение о передаче управления.
Источниками такого условия могут быть: ? любая команда, изменяющая состояние арифметических флагов; ? команда сравнения cmp, сравнивающая значения двух операндов; ? состояние регистра ecx/cx.
Обсудим эти варианты, чтобы разобраться с тем, как работают команды условного перехода.
Команда сравнения cmp
Команда сравнения cmp имеет интересный принцип работы. Он абсолютно такой же, как и у команды вычитания. sub операнд_1,операнд_2. Команда cmp так же, как и команда sub, выполняет вычитание операндов и устанавливает флаги. Единственное, чего она не делает — это запись результата вычитания на место первого операнда.
Синтаксис команды cmp: cmp операнд_1,операнд_2 (compare) — сравнивает два операнда и по результатам сравнения устанавливает флаги.
Флаги, устанавливаемые командой cmp, можно анализировать специальными командами условного перехода. Прежде чем мы их рассмотри
Вы можете ЗАГРУЗИТЬ и ПОВЫСИТЬ уникальность своей работы