Рефераты. ЯЗЫК МАКРОАССЕМБЛЕРА IBM PC В ПК под строкой понимается последовательность соседних байтов или слов. В связи с этим все строковые команды имеют две разновидности для работы со строками из байтов (в мнемонику операций входит буква B) и для работы со строками из слов (в мнемонику входит W).
Имеются следующие операции над строками:
пересылка элементов строк (в память, из памяти, память-память);
сравнение двух строк; просмотр строки с целью поиска элемента, равного заданному.
Каждая из этих операций выполняется только над одним элементом строки, однако одновременно происходит автоматическая настройка на следующий или предыдущий элемент строки. Имеются специальные команды повторения (REP и др.), которые заставляют следующую за ними строковую команду многократно повторяться (до 2^16 раз), в связи с чем такая пара команд позволяет обработать всю строку, причем намного быстрее, чем запрограммированный цикл.
Кроме того, строки можно просматривать вперед (от их начала к концу) и назад. Направление просмотра зависит от флага направления DF, значение которого можно менять с помощью команд STD (DF:=1) и CLD (DF:=0). При DF=0 все последующие строковые команды программы просматривают строки вперед, а при DF=1 - назад.
В строковых командах операнды явно не указываются, а подразумеваются. Если команда работает с одной строкой, то адрес очередного, обрабатываемого сейчас элемента строки задается парой регистров DS и SI или парой ES и DI, а если команда работает с двумя строками, то адрес элемента одной из них определяется парой DS:SI, а адрес элемента другой - парой ES:DI. После выполнения операции значение регистра SI и/или DI увеличивается (при DF=0) или уменьшается (при DF=1) на 1 (для байтовых строк) или на 2 (для строк из слов).
Начальная установка всех этих регистров, а также флага DF должна быть выполнена до начала операции над строкой. Если сегментный регистр DS уже имеет нужное значение, тогда загрузить регистр SI можно с помощью команды
LEA SI,<начальный/конечный адрес строки>
Если же надо загрузить сразу оба регистра DS и SI, тогда можно воспользоваться командой
LDS SI,m32
которая в регистр SI заносит первое слово, а в регистр DS - второе слово из двойного слова, имеющего адреc m32 (таким образом, по адресу m32+2 должен храниться сегмент, а по адресу m32 - смещение начального или конечного элемента строки). Начальную загрузку регистров ES и DI обычно осуществляют одной командой
LES DI,m32
которая действует аналогично команде LDS.
Перечислим вкратце строковые команды ПК.
Команда загрузки элемента строки в аккумулятор (LODSB или LODSW) пересылает в регистр AL или AX очередной элемент строки, на который указывает пара DS:SI, после чего увеличивает (при DF=0) или уменьшает (при DF=1) регистр SI на 1 или 2.
Команда записи аккумулятора в строку (STOSB или STOSW) заносит содержимое регистра AL или AX в тот элемент строки, на который указывает пара ES:DI, после чего изменяет регистр DI на 1 или 2.
Команда пересылки строк (MOVSB или MOVSW) считывает элемент первой строки, определяемый парой DS:SI, в элемент второй строки, определяемый парой ES:DI, после чего одновременно меняет регистры SI и DI.
Команда сравнения строк (CMPSB или CMPSW) сравнивает очередные элементы строк, указываемые парами DS:SI и ES:DI, и результат сравнения (равно, меньше и т.п.) фиксирует в флагах, после чего меняет регистры SI и DI.
Команда сканирования строки (SCASB или SCASW) сравнивает элемент строки, адрес которого задается парой ES:DI, со значением регистра AL или AX и результат сравнения фиксирует в флагах, после чего меняет содержимое регистра DI.
Перед любой строковой командой можно поставить одну из двух команд, называемых "префиксами повторения", которая заставит многократно повториться эту строковую команду. Число повторений (обычно это длина строки) должно быть указано в регистре CX. Префикс повторения REPZ (синонимы - REPE, REP) сначала заносит 1 в флаг нуля ZF, после чего, постоянно уменьшая CX на 1, заставляет повторяться следующую за ним строковую команду до тех пор, пока в CX не окажется 0 или пока флаг ZF не изменит свое значение на 0. Другой префикс повторения REPNZ (синоним - REPNE) действует аналогично, но только вначале устанавливает флаг ZF в 0, а при при изменении его на 1 прекращает повторение строковой команды.
Пример. Пусть надо переписать 10000 байтов начиная с адреса A в другое место памяти начиная с адреса B. Если оба этих имени относятся к сегменту данных, на начало которого указывает регистр DS, тогда эту пересылку можно сделать так:
CLD ;DF:=0 (просмотр строки вперед)
MOV CX,1000 ;CX - число повторений
MOV AX,DS
MOV ES,AX ;ES:=DS
LEA SI,A ;ES:SI - "откуда"
LEA DI,B ;DS:DI - "куда"
REP MOVSB ;пересылка CX байтов

1.7. СТЕК. ПОДПРОГРАММЫ.

1.7.1 Стек
В ПК имеются специальные команды работы со стеком, т.е. областью памяти, доступ к элементам которой осуществляется по принципу "последним записан - первым считан". Но для того, чтобы можно было воспользоваться этими командами, необходимо соблюдение ряда условий.
Под стек можно отвести область в любом месте памяти. Размер ее может быть любым, но не должен превосходить 64Кб, а ее начальный адрес должен быть кратным 16. Другими словами, эта область должна быть сегментом памяти; он называется сегментом стека. Начало этого сегмента (первые 16 битов начального адреса) должно обязательно храниться в сегментном регистре SS.
Хранимые в стеке элементы могут иметь любой размер, однако следует учитывать, что в ПК имеются команды записи в стек и чтения из него только слов. Поэтому для записи байта в стек его надо предварительно расширить до слова, а запись или чтение двойных слов осуществляются парой команд.
В ПК принято заполнять стек снизу вверх, от больших адресов к меньшим: первый элемент записывается в конец области, отведенной под стек, второй элемент - в предыдущую ячейку области и т.д. Считывается всегда элемент, записанный в стек последним. В связи с этим нижняя граница стека всегда фиксирована, а верхняя - меняется. Слово памяти, в котором находится элемент стека, записанный последним, называется вершиной стека. Адрес вершины, отсчитанный от начала сегмента стека, обязан находиться в указателе стека - регистре SP. Таким образом, абсолютный адрес вершины стека определяется парой SS:SP.

Значение 0 в регистре SP свидетельствует о том, что стек полностью заполнен (его вершина "дошла" до начала области стека). Поэтому для
контроля за переполнением стека надо перед новой записью в стек проверять условие SP=0 (сам ПК этого не делает). Для пустого стека значение SP должно равняться размеру стека, т.е. пара SS:SP должна указывать на байт, следующий за последним байтом области стека. Контроль за чтением из пустого стека, если надо, обязана делать сама программа.
Начальная установка регистров SS и SP может быть произведена в самой программе, однако в MASM предусмотрена возможность автоматической загрузки этих регистров. Если в директиве SEGMENT, начинающей описание сегмента стека, указать параметр STACK, тогда ассемблер (точнее, загрузчик) перед тем, как передать управление на первую команду машинной программы, загрузит в регистры SS и SP нужные значения. Например, если в программе сегмент стека описан следующим образом:
ST SEGMENT STACK
DB 256 DUP(?) ;размер стека - 256 байтов
ST ENDS
и если под этот сегмент была выделена область памяти начиная с абсолютного адреса 12340h, тогда к началу выполнения программы в регистре SS окажется величина 1234h, а в регистре SP - величина 100h (=256). Отметим, что эти значения соответствуют пустому стеку.
1.7.2 Основные стековые команды
При соблюдении указанных требований в программе можно использовать команды, предназначенные для работы со стеком. Основными из них являются следующие.
Запись слова в стек: PUSH op
Здесь op обозначает любой 16-битовый регистр (в том числе и сегментный) или адрес слова памяти. По этой команде значение регистра SP уменьшается на 2 (вычитание происходит по модулю 2^16), после чего указанное операндом слово записывается в cтек по адресу SS:SP.
Чтение слова из стека: POP op
Слово, считанное из вершины стека, присваивается операнду op (регистру, в том числе сегментному, но не CS, или слову памяти), после чего значение SP увеличивается на 2.
Переход с возвратом: CALL op
Эта команда записывает адрес следующей за ней команды в стек и затем делает переход по адресу, определяемому операндом op. Она используется для переходов на подпрограммы с запоминанием в стеке адреса возврата.
Имеются следующие разновидности этой команды (они аналогичны вариантам команды безусловного перехода JMP):
внутрисегментный относительный длинный переход (op - непосредственный операнд размером в слово, а в MASM - это метка из текущего сегмента команд или имя близкой процедуры (см. ниже)); в этом случае в стек заносится только текущее значение счетчика команд IP, т.е. смещение следующей команды; внутрисегментный абсолютный косвенный переход (op - адрес слова памяти, в которой находится адрес (смещение) той команды, на которую и будет сделан переход); и здесь в стек записывается только смещение адреса возврата; межсегментный абсолютный прямой переход (op - непосредственный операнд вида seg:ofs, а в MASM - это FAR PTR <метка> или имя дальней процедуры (см. ниже)); здесь в стек заносится текущие значение регистров CS и IP (первым в стек записывается содержимое CS), т.е. абсолютный адрес возврата, после чего меняются регистры CS и IP; межсегментный абсолютный косвенный переход (op - адрес двойного слова, в котором находится пара seg:ofs, задающая абсолютный адрес перехода); и здесь в стеке спасается содержимое регистров CS и IP. Переход (возврат) по адресу из стека: RET op
Из стека считывается адрес и по нему производится переход. Если указан операнд (а это должно быть неотрицательное число), то после чтения адреса стек еще очищается на это число байтов (к SP добавляется это число). Команда используется для возврата из подпрограммы по адресу, записанному в стек по команде CALL при вызове подпрограммы, и одновременной очистки стека от параметров, которые основная программа занесла
в стек перед обращением к подпрограмме.
Команда RET имеет две разновидности (хотя в MASM они записываются и одинаково): в одном случае из стека считывается только одно слово смещение адреса возврата, а во втором - из стека считывается пара seg: ofs, указывающая абсолютный адрес возврата. Как ассемблер определяет, какой из этих двух случаев имеет место, объяснено ниже.
В ПК стек в основном используется для организации подпрограмм и прерываний. Подпрограммы рассматриваются ниже, а прерывания - в главе 3. Однако, даже если программе не нужен стек, она все равно должна отвести под него место. Дело в том, что стеком будет неявно пользоваться операционная система при обработке прерываний, которые возникают (например, при нажатии клавиш на клавиатуре) в то время, когда выполняется программа. Для нужд ОС рекомендуется выделять в стеке 64 байта.

1.7.3 Подпрограммы
Типичная схема огранизации подпрограмм, обычно используемая трансляторами с языков высокого уровня для реализации процедур и функций (в частности, рекурсивных), следующая.
При обращении к подпрограмме в стек заносятся параметры для нее и адрес возрата, после чего делается переход на ее начало:
PUSH param1 ;запись 1-го параметра в стек
...
PUSH paramk ;запись последнего (k-го) параметра в стек
CALL subr ;переход в возратом на подпрограмму
(Замечание: если необходимо вычислить параметр или если его размер отличен от слова, тогда для записи параметра в стек нужно, конечно, несколько команд, а не одна.) Состояние стека после выполнения этих команд обращения к подпрограмме показано на рис. a

Первыми командами подпрограммы обычно являются следующие:
PUSH BP ;спасти в стеке старое значение BP
MOV SP,BP ;установить BP на вершину стека
SUB SP,m ;отвести в стеке место (m байтов) под локальные
;величины подпрограммы (состояние стека в этот ;момент показано на рис. б)
Поясним эти "входные" команды. В подпрограмме для обращения к ячейкам стека, занятых параметрами, используется (как базовый) регистр BP: если в BP занести адрес вершины стека, то для доступа к этим ячейкам следует использовать адресные выражения вида i[BP] или, что то же самое, [BP+i]. (Отметим, что применять здесь регистры-модификаторы BX, SI и DI нельзя, т.к. формируемые по ним исполнительные адреса будут сегментироваться по умолчанию по регистру DS, а в данном случае нужно сегментирование по SS.) Однако данная подпрограмма может быть вызвана из другой, также использующей регистр BP, поэтому прежде, чем установить BP на вершину стека, надо спасти в стеке старое значение этого регистра, что и делает первая из "входных" команд. Вторая же команда устанавливает BP на вершину стека. Если предположить, что каждый параметр и адрес возврата занимают по слову памяти, тогда доступ к первому параметру обеспечивается адресным выражением [BP+4], ко второму - выражением [BP+6] и т.д. (см. рис. б).
Подпрограмме может потребоваться место для ее локальных величин.
Такое место обычно отводится в стеке (а для рекурсивных подпрограмм только в стеке) "над" ячейкой, занимаемой старым значением BP. Если под эти величины нужно m байтов, то такой "захват" места можно реализовать простым уменьшением значения регистра SP на m, что и делает 3-я "входная" команда. Доступ к локальным величинам обеспечивается адресными выражениями вида [BP-i]. Если подпрограмме не нужно место под локальные величины, тогда третью из "входных" команд следует опустить.
Выход из подпрограммы реализуется следующими командами:
MOV SP,BP ;очистить стек от локальных величин
POP BP ;восстановить старое значение BP
RET 2*k ;возврат из подпрограммы и очистка стека от
;параметров (считаем, что они занимают 2*k байтов) Первая из этих "выходных" команд заносит в регистр SP адрес той ячейки стека, где хранится старое значение регистра BP, т.е. происходит очистка стека от локальных величин (если их не было, то данную команду надо опустить). Вторая команда восстанавливает в BP это старое значение, одновременно удаляя его из стека. В этот момент состояние стека будет таким же, как и перед входом в подпрограмму (см. рис а). Третья команда считывает из стека адрес возврата (в результате чего SP "опускается" на 2 байта), затем добавляет к SP число, которое должно равняться числу байтов, занимаемых всеми параметрами подпрограммы, и затем осуществляет переход по адресу возврата. В этот момент состояние стека будет таким же, каким оно было перед обращением к подпрограмме.
Здесь описана универсальная схема организации работы подпрограмм.
В кокретных же случаях можно использовать более простые схемы. Например, параметры можно передавать не через стек, а через регистры, место под локальные величины можно отводить не в стеке, а в сегменте данных и т.п.

1.7.4 Процедуры в языке ассемблера
При составлении и вызове подпрограмм необходимо следить за тем, чтобы команды CALL и RET действовали согласовано - были одновременно близкими или дальними. В MASM эта проблема снимается, если подпрограмму описать как процедуру. Процедуры имеют следующий вид:
имя_процедуры PROC [NEAR или FAR]
...
имя_процедуры ENDP
Хотя в директиве PROC после имени процедуры не ставится двоеточие, это имя относится к меткам и его можно указывать в командах перехода, в частности в команде CALL, когда надо вызвать процедуру. Это же имя должно быть повторено в директиве ENDP, заканчивающей описание процедуры. Предложения между этими двумя директивами образуют тело процедуры (подпрограмму). Имя процедуры является фактически меткой первой из команд тела, поэтому данную команду не надо специально метить.
Если в директиве PROC указан параметр NEAR или он вообще не указан, то такая процедура считается "близкой" и обращаться к ней можно только из того сегмента команд, где она описана. Дело в том, что ассемблер будет заменять все команды CALL, где указано имя данной процедуры, на машинные команды близкого перехода с возвратом, а все команды RET внутри процедуры - на близкие возвраты. Если же в директиве PROC указан параметр FAR, то это "дальняя" процедура: все обращения к ней и все команды RET внутри нее рассматриваются ассемблером как дальние переходы. Обращаться к этой процедуре можно из любых сегментов команд. Таким образом, достаточно лишь указать тип процедуры (близкая она или дальняя), всю же остальную работу возьмет на себя ассемблер: переходы на нее и возвраты из нее будут автоматически согласованы с этим типом. В этом главное (и единственное) достоинство описания подпрограмм в виде процедур. (Отметим, что метки и имена, описанные в процедуре, не локализуются в ней.)
Например, вычисление ax:=sign(ax) можно описать в виде процедуры следующим образом:
sing proc far ;дальняя процедура
cmp ax,0
je sgn1 ;ax=0 - перейти к sgn1
mov ax,1 ;ax:=1 (флаги не изменились!)
jg sgn1 ;ax>0 - перейти к sgn1
mov ax,-1 ;ax:=-1
sgn1: ret ;дальний возврат
sign endp
...
Возможный пример обращения к этой процедуре:
;cx:=sign(var)
mov ax,var
call sign ;дальний вызов
mov cx,ax


Страницы: 1, 2, 3, 4, 5



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