Справочник по языку Ассемблера IBM PC

         

Доступ к элементам массива


При работе с массивами необходимо четко представлять себе, что все элементы массива располагаются в памяти компьютера последовательно.
Само по себе такое расположение ничего не говорит о назначении и порядке использования этих элементов. И только лишь программист с помощью составленного им алгоритма обработки определяет, как нужно трактовать эту последовательность байт, составляющих массив. Так, одну и ту же область памяти можно трактовать как одномерный массив, и одновременно те же самые данные могут трактоваться как двухмерный массив. Все зависит только от алгоритма обработки этих данных в конкретной программе. Сами по себе данные не несут никакой информации о своем “смысловом”, или логическом, типе. Помните об этом принципиальном моменте.

Эти же соображения можно распространить и на индексы элементов массива. Ассемблер не подозревает об их существовании и ему абсолютно все равно, каковы их численные смысловые значения.
Для того чтобы локализовать определенный элемент массива, к его имени нужно добавить индекс. Так как мы моделируем массив, то должны позаботиться и о моделировании индекса. В языке ассемблера индексы массивов — это обычные адреса, но с ними работают особым образом. Другими словами, когда при программировании на ассемблере мы говорим об индексе, то скорее подразумеваем под этим не номер элемента в массиве, а некоторый адрес.

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

mas dw 0,1,2,3,4,5

Пусть эта последовательность чисел трактуется как одномерный массив. Размерность каждого элемента определяется директивой dw, то есть она равна 2 байта. Чтобы получить доступ к третьему элементу, нужно к адресу массива прибавить 6. Нумерация элементов массива в ассемблере начинается с нуля.
То есть в нашем случае речь, фактически, идет о 4-м элементе массива — 3, но об этом знает только программист; микропроцессору в данном случае все равно — ему нужен только адрес.

В общем случае для получения адреса элемента в массиве необходимо начальный (базовый) адрес массива сложить с произведением индекса (номер элемента минус единица) этого элемента на размер элемента массива:

база + (индекс*размер элемента)


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

индексная адресация со смещением — режим адресации, при котором эффективный адрес формируется из двух компонентов:

постоянного (базового) — указанием прямого адреса массива в виде имени идентификатора, обозначающего начало массива; переменного (индексного) — указанием имени индексного регистра.
К примеру:
mas dw 0,1,2,3,4,5 ... mov si,4 ;поместить 3-й элемент массива mas в регистр ax: mov ax,mas[si]



базовая индексная адресация со смещением — режим адресации, при котором эффективный адрес формируется максимум из трех компонентов:

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

Этот вид адресации удобно использовать при обработке двухмерных массивов. Пример использования этой адресации мы рассмотрим далее при изучении особенностей работы с двухмерными массивами.



Напомним, что в качестве базового регистра может использоваться любой из восьми регистров общего назначения. В качестве индексного регистра также можно использовать любой регистр общего назначения, за исключением esp/sp.

Микропроцессор позволяет масштабировать индекс. Это означает, что если указать после имени индексного регистра знак умножения “*” с последующей цифрой 2, 4 или 8, то содержимое индексного регистра будет умножаться на 2, 4 или 8, то есть масштабироваться.

Применение масштабирования облегчает работу с массивами, которые имеют размер элементов, равный 2, 4 или 8 байт, так как микропроцессор сам производит коррекцию индекса для получения адреса очередного элемента массива. Нам нужно лишь загрузить в индексный регистр значение требуемого индекса (считая от 0). Кстати сказать, возможность масштабирования появилась в микропроцессорах Intel, начиная с модели i486. По этой причине в рассматриваемом здесь примере программы стоит директива .486. Ее назначение, как и ранее использовавшейся директивы .386, в том, чтобы указать ассемблеру при формировании машинных команд на необходимость учета и использования дополнительных возможностей системы команд новых моделей микропроцессоров.

В качестве примера использования масштабирования рассмотрим листинг 3, в котором просматривается массив, состоящий из слов, и производится сравнение этих элементов с нулем. Выводится соответствующее сообщение.
Листинг 3. Просмотр массива слов с использованием масштабирования ;prg_12_2.asm MASM MODEL small STACK 256 .data ;начало сегмента данных ;тексты сообщений: mes1 db 'не равен 0!$',0ah,0dh mes2 db 'равен 0!$',0ah,0dh mes3 db 0ah,0dh,'Элемент $' mas dw 2,7,0,0,1,9,3,6,0,8 ;исходный массив .code .486 ;это обязательно main: mov ax,@data mov ds,ax ;связка ds с сегментом данных xor ax,ax ;обнуление ax prepare: mov cx,10 ;значение счетчика цикла в cx mov esi,0 ;индекс в esi compare: mov dx,mas[esi*2] ;первый элемент массива в dx cmp dx,0 ;сравнение dx c 0 je equal ;переход, если равно not_equal: ;не равно mov ah,09h ;вывод сообщения на экран lea dx,mes3 int 21h mov ah,02h ;вывод номера элемента массива на экран mov dx,si add dl,30h int 21h mov ah,09h lea dx,mes1 int 21h inc esi ;на следующий элемент dec cx ;условие для выхода из цикла jcxz exit ;cx=0? Если да — на выход jmp compare ;нет — повторить цикл equal: ;равно 0 mov ah,09h ;вывод сообщения mes3 на экран lea dx,mes3 int 21h mov ah,02h mov dx,si add dl,30h int 21h mov ah,09h ;вывод сообщения mes2 на экран lea dx,mes2 int 21h inc esi ;на следующий элемент dec cx ;все элементы обработаны? jcxz exit jmp compare exit: mov ax,4c00h ;стандартный выход int 21h end main ;конец программы

<


Еще несколько слов о соглашениях:

Если для описания адреса используется только один регистр, то речь идет о базовой адресации и этот регистр рассматривается как базовый:
;переслать байт из области данных, адрес которой находится в регистре ebx: mov al,[ebx]



Если для задания адреса в команде используется прямая адресация (в виде идентификатора) в сочетании с одним регистром, то речь идет об индексной адресации. Регистр считается индексным, и поэтому можно использовать масштабирование для получения адреса нужного элемента массива:
add eax,mas[ebx*4] ;сложить содержимое eax с двойным словом в памяти ;по адресу mas + (ebx)*4



Если для описания адреса используются два регистра, то речь идет о базово-индексной адресации. Левый регистр рассматривается как базовый, а правый — как индексный. В общем случае это не принципиально, но если мы используем масштабирование с одним из регистров, то он всегда является индексным. Но лучше придерживаться определенных соглашений.
Помните, что применение регистров ebp/bp и esp/sp по умолчанию подразумевает, что сегментная составляющая адреса находится в регистре ss.

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

К примеру:
mov ax,mas[ebx][ecx*2] ;адрес операнда равен [mas+(ebx)+(ecx)*2] ... sub dx,[ebx+8][ecx*4] ;адрес операнда равен [(ebx)+8+(ecx)*4]

Но имейте в виду, что масштабирование эффективно лишь тогда, когда размерность элементов массива равна 2, 4 или 8 байт. Если же размерность элементов другая, то организовывать обращение к элементам массива нужно обычным способом, как описано ранее.

Рассмотрим пример работы с массивом из пяти трехбайтовых элементов (листинг 4). Младший байт в каждом из этих элементов представляет собой некий счетчик, а старшие два байта — что-то еще, для нас не имеющее никакого значения. Необходимо последовательно обработать элементы данного массива, увеличив значения счетчиков на единицу.



Листинг 4. Обработка массива элементов с нечетной длиной ;prg_11_3.asm MASM MODEL small ;модель памяти STACK 256 ;размер стека .data ;начало сегмента данных N=5 ;количество элементов массива mas db 5 dup (3 dup (0)) .code ;сегмент кода main: ;точка входа в программу mov ax,@data mov ds,ax xor ax,ax ;обнуление ax mov si,0 ;0 в si mov cx,N ;N в cx go: mov dl,mas[si] ;первый байт поля в dl inc dl ;увеличение dl на 1 (по условию) mov mas[si],dl ;заслать обратно в массив add si,3 ;сдвиг на следующий элемент массива loop go ;повтор цикла mov si,0 ;подготовка к выводу на экран mov cx,N show: ;вывод на экран содержимого ;первых байт полей mov dl,mas[si] add dl,30h mov ah,02h int 21h loop show exit: mov ax,4c00h ;стандартный выход int 21h end main ;конец программы






Двухмерные массивы


С представлением одномерных массивов в программе на ассемблере и организацией их обработки все достаточно просто. А как быть если программа должна обрабатывать двухмерный массив? Все проблемы возникают по-прежнему из-за того, что специальных средств для описания такого типа данных в ассемблере нет. Двухмерный массив нужно моделировать. На описании самих данных это почти никак не отражается — память под массив выделяется с помощью директив резервирования и инициализации памяти.

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

Если последовательность однотипных элементов в памяти трактуется как двухмерный массив, расположенный по строкам, то адрес элемента (i, j) вычисляется по формуле

(база + количество_элементов_в_строке * размер_элемента * i+j)

Здесь i = 0...n–1 указывает номер строки, а j = 0...m–1

указывает номер столбца.

Например, пусть имеется массив чисел (размером в 1 байт) mas(i, j) с размерностью 4 на 4
(i= 0...3, j = 0...3):

23 04 05 67 05 06 07 99 67 08 09 23 87 09 00 08

В памяти элементы этого массива будут расположены в следующей последовательности:

23 04 05 67 05 06 07 99 67 08 09 23 87 09 00 08

Если мы хотим трактовать эту последовательность как двухмерный массив, приведенный выше, и извлечь, например, элемент
mas(2, 3) = 23, то проведя нехитрый подсчет, убедимся в правильности наших рассуждений:

Эффективный адрес mas(2, 3) = mas + 4 * 1 * 2 + 3 = mas + 11

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

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


сочетание прямого адреса, как базового компонента адреса, и двух индексных регистров для хранения индексов:
mov ax,mas[ebx][esi]



сочетание двух индексных регистров, один из которых является и базовым и индексным одновременно, а другой — только индексным:
mov ax,[ebx][esi]



В программе это будет выглядеть примерно так:
;Фрагмент программы выборки элемента ;массива mas(2,3) и его обнуления .data mas db 23,4,5,67,5,6,7,99,67,8,9,23,87,9,0,8 i=2 j=3 .code ... mov si,4*1*i mov di,j mov al,mas[si][di] ;в al элемент mas(2,3) ...

В качестве законченного примера рассмотрим программу поиска элемента в двухмерном массиве чисел (листинг 5). Элементы массива заданы статически.
Листинг 5. Поиск элемента в двухмерном массиве ;prg_11_4.asm MASM MODEL small STACK 256 .data ;матрица размером 2x5 — если ее не инициализировать, ;то для наглядности она может быть описана так: ;array dw 2 DUP (5 DUP (?)) ;но мы ее инициализируем: array dw 1,2,3,4,5,6,7,3,9,0 ;логически это будет выглядеть так: ;array= {1 2} ; {3 4} ; {5 6} ; {7 3} ; {9 0} elem dw 3 ;элемент для поиска failed db 0ah,0dh,'Нет такого элемента в массиве!','$' success db 0ah,0dh,'Такой элемент в массиве присутствует ','$' foundtime db ? ;количество найденных элементов fnd db ' раз(а)',0ah,0dh,'$' .code main: mov ax,@data mov ds,ax xor ax,ax mov si,0 ;si=столбцы в матрице mov bx,0 ;bx=строки в матрице mov cx,5 ;число для внешнего цикла (по строкам) external: ;внешний цикл по строкам mov ax,array[bx][si] ;в ax первый элемент матрицы push cx ;сохранение в стеке счётчика внешнего цикла mov cx,2 ;число для внутреннего цикла (по столбцам) mov si,0 iternal: ;внутренний цикл по строкам inc si ;передвижение на следующий элемент в строке ;сравниваем содержимое текущего элемента в ax с искомым элементом: cmp ax,elem ;если текущий совпал с искомым, то переход на here для обработки, ;иначе цикл продолжения поиска je here ;иначе — цикл по строке cx=2 раз loop iternal here: jcxz move_next ;просмотрели строку? inc foundtime ;иначе увеличиваем счётчик совпавших move_next: ;продвижение в матрице pop cx ;восстанавливаем CX из стека (5) add bx,1 ;передвигаемся на следующую строку loop external ;цикл (внешний) cmp foundtime,0h ;сравнение числа совпавших с 0 ja eql ;если больше 0, то переход not_equal: ;нет элементов, совпавших с искомым mov ah,09h ;вывод сообщения на экран mov dx,offset failed int 21h jmp exit ;на выход eql: ;есть элементы, совпавшие с искомым mov ah,09h ;вывод сообщений на экран mov dx,offset success int 21h mov ah,02h mov dl,foundtime add dl,30h int 21h mov ah,09h mov dx,offset fnd int 21h exit: ;выход mov ax,4c00h ;стандартное завершение программы int 21h end main ;конец программы

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

количество вхождений искомого элемента в массив. В качестве индексных регистров используются si и bx.




ENTER


(setup parameter block for ENTERing procedure)

Установка кадра стека для параметров процедуры

 

Схема команды:  enter loc_size,lex_lev 

Назначение: установка границы в стеке для локальных переменных процедуры.


Алгоритм работы:

поместить текущее значение регистра ebp/bp в стек;

сохранить текущее значение esp/sp в промежуточной переменной fp (имя переменной выбрано случайно);

если лексический уровень вложенности (операнд lex_lev) не равен нулю, то (lex_lev–1) раз делать следующее:

в зависимости от установленного режима адресации use16 или use32 выполнить вычитание (bp–2) или (ebp–4) и записать результат обратно в ebp/bp;

сохранить значение ebp/bp в стеке;

сохранить в стеке значение промежуточной переменной fp;

записать значение промежуточной переменной fp в регистр ebp/bp;

уменьшить значение регистра esp/sp на величину, заданную первым операндом, минус размер области локальных переменных loc_size: esp/sp=(esp/sp)–loc_size.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


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

Изображение структуры некоторой программы в виде блоков

В правом верхнем углу каждого блока (процедуры) стоит номер лексического уровня вложенности этого блока относительно других блоков программы. Большинство блочно-структурированных языков в качестве основного метода распределения памяти для переменных в блоках используют автоматическое распределение памяти. Это означает, что при входе в блок (вызове процедуры и т. п.) в некотором месте памяти (или в стеке) выделяется область памяти для переменных этого блока (ее можно назвать областью инициализации). После выхода из этого блока связь программы с этой областью теряется, то есть эти переменные становятся недоступными. Но если, как в нашем примере, в этой процедуре есть вложенные блоки (процедуры), то для некоторого внутреннего блока (например, C) могут быть доступны области инициализации (переменные) блоков, объемлющих данный блок. В нашем примере для блока C доступны также переменные блоков B и A, но не D. Возникает вопрос: как же программа, находясь в конкретной точке своего выполнения, может отслеживать то, какие области инициализации ей доступны? Это делается с помощью структуры данных, называемой дисплеем. Дисплей содержит указатели на самую последнюю область текущего блока и на области инициализации всех блоков, объемлющих данный блок в программе. Например, если в программе A была вызвана сначала процедура B, а затем C, то дисплей содержит указатели на области инициализации A, B и C (см. рисунок ниже).




Если после этого вызвать процедуру D (в то время как B и C еще не завершены), то картина изменится.



После того как некоторый блок (процедура) завершает свою работу, ее область инициализации удаляется из памяти (стека) и одновременно соответствующим образом корректируется дисплей. Большинство языков высокого уровня хранят локальные данные блоков в стеке. Эти переменные называют еще автоматическими или динамическими. Память для них резервируется путем уменьшения значения регистра-указателя стека esp/sp на величину, равную длине области, занимаемой этими динамическими переменными. Доступ к этим переменным осуществляется посредством регистра ebp/bp. Если один блок вложен в другой, то для его динамических (локальных) переменных также выделяется место (кадр) в стеке, но в этот кадр помещается указатель на кадр стека для включающего его блока. Команды enter и leave как раз и позволяют поддержать в языке ассемблера принципы работы с переменными блоков как в блочно-структурированных языках. Дисплей организуется с помощью второго операнда команды enter и стека. Например, в начале работы главной процедуры A и после вызова процедуры B кадр стека будет выглядеть так.



Соответственно, после вызова процедур C и D стек будет выглядеть, как показано ниже.



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

.286 proc1   proc ;зарезервировать в стеке место для локальных переменных ;proc1 16 байт ;лексический уровень вложенности 0         enter   16,0 ...         leave         ret proc1   endp         

См. также: урок 14 и команды ,


HLT


(HaLT)

Остановка

Схема команды:  hlt 

Назначение: остановка микропроцессора до прерывания или перезагрузки.


Алгоритм работы:


перевод микропроцессора в состояние остановки.


Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


В результате выполнения команды микропроцессор переходит в состояние остановки. Из этого состояния его можно вывести сигналами на входах RESET, NMI, INTR. Если для возобновления работы микропроцессора используется прерывание, то сохраненное значение пары cs:eip/ip указывает на команду, следующую за hlt. Для иллюстрации применения данной команды рассмотрим еще один способ переключения микропроцессора из защищенного в реальный режим и его возврата обратно в реальный режим (см. урок 16). Как известно, в микропроцессоре не предусмотрено специальных средств для подобного переключения. Сброс микропроцессора можно инициировать, если вывести байт со значением 0feh в порт клавиатуры 64h. После этого микропроцесор переходит в реальный режим и управление получает программа BIOS, которая анализирует байт отключения в CMOS-памяти по адресу 0fh. Для нас интерес представляют два значения этого байта — 5h и 0ah:

5h — сброс микропроцессора инициирует инициализацию программируемого контроллера прерываний на значение базового вектора 08h (см. уроки 15 и 17). Далее управление передается по адресу, который находится в ячейке области данных BIOS 0040:0067;

0ah — сброс микропроцессора инициирует непосредственно передачу управления по адресу в ячейке области данных BIOS 0040:0067 (то есть без перепрограммирования контроллера прерываний).

Таким образом, если вы не используете прерываний, то достаточно установить байт 0fh в CMOS-памяти в 0ah. Предварительно, конечно, вы должны инициализировать ячейку области данных BIOS 0040:0067 значением адреса, по которому необходимо передать управление после сброса. Для программирования CMOS-памяти используются номера портов 070h и 071h. Вначале в порт 070h заносится нужный номер ячейки CMOS-памяти, а затем в порт 071h — новое значение этой ячейки.


;работаем в реальном режиме, готовимся к переходу ;в защищенный режим:         push    es         mov     ax,40h         mov     es,ax         mov     word ptr es:[67h],offset ret_real ;ret_real — метка в программе, с которой должно ; начаться выполнение программы после сброса         mov     es:[69h],cs         mov     al,0fh  ;будем обращаться к ячейке 0fh в CMOS         out     70h,al         jmp     $+2     ;чуть задержимся, чтобы аппаратура отработала ;сброс без перепрограммирования контроллера         mov     al,0ah         out     71h,al ;переходим в защищенный режим установкой ;бита 0 cr0 в 1 (см. урок 16) ;работаем в защищенном режиме ;готовимся перейти обратно в реальный режим         mov     al,01fch         out     64h,al  ;сброс микропроцессора  hlt ;остановка до физического окончания процесса сброса         ret_real:       ...     ;метка, на которую будет передано                         ;управление после сброса         

См. также: уроки 15, 16, 17


IDIV


(Integer DIVide)

Деление целочисленное со знаком

 

Схема команды:  idiv делитель 

Назначение: операция деления двух двоичных значений со знаком.


Алгоритм работы:


Для команды необходимо задание двух операндов — делимого и делителя. Делимое задается неявно, и размер его зависит от размера делителя, местонахождение которого указывается в команде:

если делитель размером в байт, то делимое должно быть расположено в регистре ax. После операции частное помещается в al, а остаток — в ah;

если делитель размером в слово, то делимое должно быть расположено в паре регистров dx:ax, причем младшая часть делимого находится в ax. После операции частное помещается в ax, а остаток — в dx;

если делитель размером в двойное слово, то делимое должно быть расположено в паре регистров edx:eax, причем младшая часть делимого находится в eax. После операции частное помещается в eax, а остаток — в edx;

Остаток всегда имеет знак делимого. Знак частного зависит от состояния знаковых битов (старших разрядов) делимого и делителя.


Состояние флагов после выполнения команды:

11 07 06 04 02 00
OF SF ZF AF PF CF
? ? ? ? ?

Применение:


Команда выполняет целочисленное деление операндов с учетом их знаковых разрядов. Результатом деления являются частное и остаток от деления. При выполнении операции деления возможно возникновение исключительной ситуации: 0 — ошибка деления. Эта ситуация возникает в одном из двух случаев: делитель равен 0 или частное слишком велико для его размещения в регистре eax/ax/al.

;деление слов         mov     ax,1045 ;делимое         mov     bx,587  ;делитель         cwd             ;расширение делимого dx:ax         idiv    bx      ;частное в ax, остаток в dx         

См. также: урок 8, приложение 7 и команду



IMUL


(Integer MULtiply)

Умножение целочисленное со знаком

Схема команды:  imul множитель_1 
imul множ_1,множ_2 
imul рез-т,множ_1,множ_2

Назначение: операция умножения двух целочисленных двоичных значений со знаком.


Алгоритм работы:


Алгоритм работы команды зависит от используемой формы команды. Форма команды с одним операндом требует явного указания местоположения только одного сомножителя, который может быть расположен в ячейке памяти или регистре. Местоположение второго сомножителя фиксировано и зависит от размера первого сомножителя:

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

если операнд, указанный в команде, — слово, то второй сомножитель располагается в ax;

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

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

при умножении байтов результат помещается в ax;

при умножении слов результат помещается в пару dx:ax;

при умножении двойных слов результат помещается в пару edx:eax.

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

в команде с двумя операндами первый операнд определяет местоположение первого сомножителя. На его место впоследствии будет записан результат. Второй операнд определяет местоположение второго сомножителя;

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

Состояние флагов после выполнения команды:

11 07 06 04 02 00
OF SF ZF AF PF CF
r ? ? ? r

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


для однооперандной формы команды imul регистры ax/dx/edx являются знаковыми расширениями регистров al/ax/eax;

для двухоперандной формы команды imul для размещения результата умножения достаточно размерности указанных регистров назначения r16/r32;

то же для трехоперандной команды умножения.

Применение:

Команда выполняет целочисленное умножение операндов с учетом их знаковых разрядов. Для выполнения этой операции необходимо наличие двух сомножителей. Размещение и задание их местоположения в команде зависит от формы применяемой команды умножения, которая, в свою очередь, определяется моделью микропроцессора. Так, для микропроцессора i8086 возможна только однооперандная форма команды, для последующих моделей микропроцессоров дополнительно можно использовать двух- и трехоперандные формы этой команды.

.486 ...         mov     bx,186         imul    eax,bx,8 ;если результату не хватило размерности операнда1, ;то перейдем на m1, где скорректируем ситуацию:         jc      m1         

См. также: урок 8, приложение 7 и команду


IN


(INput operand from port)

Ввод операнда из порта

 

Схема команды:  in аккумулятор,ном_порта 

Назначение: ввод значения из порта ввода-вывода.


Алгоритм работы:


Передает байт, слово, двойное слово из порта ввода-вывода в один из регистров al/ax/eax. Состояние флагов после выполнения команды: выполнение команды не влияет на флаги.


Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команда применяется для прямого управления оборудованием компьютера посредством портов. Номер порта задается вторым операндом в виде непосредственного значения или значения в регистре dx. Непосредственным значением можно задать порт с номером в диапазоне 0-255. При использовании порта с большим номером используется регистр dx. Размер данных определяется размерностью первого операнда и может быть байтом, словом, двойным словом. В качестве примера применения рассмотрим фрагмент обработчика прерывания от клавиатуры 9. Это прерывание вызывается всякий раз при нажатии любой клавиши на клавиатуре. Обработчик этого прерывания должен прочитать скан-код клавиши, подтвердить микропроцессору клавиатуры факт приема скан-кода, преобразовать этот код в соответствии с клавишами-переключателями и поместить преобразованный код в буфер клавиатуры, находящийся в области BIOS. Действия чтения и подтверждения приема скан-кода могут выглядеть, к примеру, так:

        in      al,60h  ;читаем скан-код         push    ax      ;сохраним его на время         in      al,61h  ;читаем порт 61h         or      al,80h  ;старший бит байта из порта 61h в 1         out     61h,al  ;подтверждаем факт приема скан-кода         pop     ax         out     61h,al  ;восстановили байт в порту 61h         

См. также: урок 7 и команды , ,



INC


(INCrement operand by 1)

Увеличить операнд на 1

Схема команды:  inc операнд 

Назначение: увеличение значения операнда в памяти или регистре на 1.


Алгоритм работы:


команда увеличивает операнд на единицу.


Состояние флагов после выполнения команды:

11 07 06 04 02
OF SF ZF AF PF
r r r r

Применение:


Команда используется для увеличения значения байта, слова, двойного слова в памяти или регистре на единицу. При этом команда не воздействует на флаг cf.

        inc     ax      ;увеличить значение в ax на 1         

См. также: урок 8 и команды , ,



INS/INSB/INSW/INSD


(Input String Byte/Word/Double word operands)

Ввод строк байтов/слов/двойных слов из порта

 

Схема команды:  ins приемник,порт 
insb 
insw 
insd

Назначение: ввод из порта в память последовательности байт, слов, двойных слов.


Алгоритм работы:

передать данные из порта ввода-вывода, номер которого загружен в регистр dx, в память по адресу es:edi/di;

в зависимости от состояния флага df изменить значение регистров edi/di:

если df=0, то увеличить содержимое этих регистров на длину структурного элемента последовательности;

если df=1, то уменьшить содержимое этих регистров на длину структурного элемента последовательности;

при наличии префикса выполнить определяемые им действия (см. команду rep).

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команда вводит данные из порта ввода-вывода, номер которого загружен в регистр dx, в память по адресу es:edi/di. Сегментная составляющая адреса должна быть обязательно в регистре es. Замена сегментного регистра недопустима. Непосредственное задание порта в команде также недопустимо - для этого используется регистр dx. Размеры вводимых элементов зависят от применяемой команды. Команда ins может работать с элементами размером в байт, слово, двойное слово. В качестве операндов в команде указывается символическое имя ячейки памяти, в которую вводятся элементы из порта ввода-вывода. Реально это символическое имя используется лишь для получения типа элемента последовательности, а его адрес должен быть предварительно загружен в пару регистров es:edi/di. Транслятор, обработав команду ins и выяснив тип операнда, генерирует одну из машинных команд insb, insw или insd. Машинного аналога для команды ins нет. Для того чтобы эти команды можно было использовать для ввода последовательности элементов, имеющих размерность байт, слово, двойное слово, необходимо использовать префикс rep. Префикс rep заставляет циклически выполняться команду ввода до тех пор, пока содержимое регистра ecx/cx не станет равным нулю.

.286 ;ввести 10 байт из порта 300h (номер порта bgr условно) ;в цепочку байт в памяти по адресу str_10  db      10 dup(0) adr_str dd      str_10         les     di,adr_str         mov     dx,300h rep     insb ...         

См. также: уроки 2, 11 и команды , , , , , ,



INT


(INTerrupt)

Вызов подпрограммы обслуживания прерывания

Схема команды:  int номер_прерывания 

Назначение: вызов подпрограммы обслуживания прерывания с номером прерывания, заданным операндом команды.


Алгоритм работы:

записать в стек регистр флагов eflags/flags и адрес возврата. При записи адреса возврата вначале записывается содержимое сегментного регистра cs, затем содержимое указателя команд eip/ip;

сбросить в ноль флаги if и tf;

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

Состояние флагов после выполнения команды:

09 08
IF TF
0

Применение:


Как видно из синтаксиса, существуют две формы этой команды:

int 3 — имеет свой индивидуальный код операции 0cch и занимает один байт. Это обстоятельство делает ее очень удобной для использования в различных программных отладчиках для установки точек прерывания путем подмены первого байта любой команды. Микропроцессор, встречая в последовательности команд команду с кодом операции 0cch, вызывает программу обработки прерывания с номером вектора 3, которая служит для связи с программным отладчиком.

Вторая форма команды занимает два байта, имеет код операции 0cdh и позволяет инициировать вызов подпрограммы обработки прерывания с номером вектора в диапазоне 0–255. Особенности передачи управления, как было отмечено, зависят от режима работы микропроцессора.

 

;вызов обработчика аппаратного прерывания 08h из программы:         int     08h         

См. также: уроки 15, 17 и команды ,



INTO


(INTerrupt if Overflow)

Прерывание, если переполнение

 

Схема команды:  into 

Назначение: инициирование прерывания с номером 4, если установлен флаг of.


Алгоритм работы:


Проанализировать состояние флага of:

если of=0, то никаких действий производить не нужно — передать управление на следующую команду;

если of=1, то дальнейшие действия, как при команде int, то есть:

записать в стек регистр флагов eflags/flags и адрес возврата. При записи адреса возврата вначале записывается содержимое сегментного регистра cs, затем содержимое указателя команд eip/ip;

сбросить в ноль флаги if и tf;

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

Состояние флагов после выполнения команды:

09 08
IF TF
r r

Применение:


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

.486 ...         mov     bx,186         imul    eax,bx,8 ;если результату не хватило размерности операнда1, ;то of установится в 1 ;исправим ситуацию в обработчике прерывания 3         into         

См. также: уроки 8, 15, 17 и команды , ,



IRET/IRETD


(Interrupt RETurn)

Возврат из прерывания

Схема команды:  iret 
iretd 

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


Алгоритм работы:


Работа команды зависит от режима работы микропроцесссора:

в реальном режиме команда iret последовательно извлекает из стека и затем восстанавливает в микропроцессоре содержимое следующих регистров: eip/ip, cs, eflags/flags. Далее прерванная программа продолжается с точки прерывания;

в защищенном режиме действия команды зависят от состояния флага NT (вложенной задачи) в регистре флагов:

если NT=0, то производятся действия по возврату управления прерванной программе, при этом характер этих действий зависит от соотношения уровней привилегированности прерванной программы и программы обработки прерывания;

в случае NT=1 производятся действия по переключению задач.

Состояние флагов после выполнения команды:

11 10 09 08 07 06 04 02 00
OF DF IF TF SF ZF AF PF CF
r r r r r r r r r

Применение:


Команду iret необходимо применять для восстановления сохраненных командой int регистров флагов, указателя команд и сегментного регистра кода. Число этих команд в программе обработки прерывания должно соответствовать количеству точек выхода из нее. Команда iretd используется в старших моделях микропроцессоров для извлечения из стека и восстановления 32-битных регистров.

my_int1c        proc ;программа обработки прерывания 1Ch ...         iret         endp         

См. также: уроки 15, 17 и команды ,



JCC JCXZ/JECXZ


(Jump if condition)

(Jump if CX=Zero/ Jump if ECX=Zero)

Переход, если выполнено условие

Переход, если CX/ECX равен нулю

 

Схема команды:  jcc метка 
jcxz метка 
jecxz метка

Назначение: переход внутри текущего сегмента команд в зависимости от некоторого условия.


Алгоритм работы команд (кроме jcxz/jecxz):


Проверка состояния флагов в зависимости от кода операции (оно отражает проверяемое условие):

если проверяемое условие истинно, то перейти к ячейке, обозначенной операндом;

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

Алгоритм работы команды jcxz/jecxz:


Проверка условия равенства нулю содержимого регистра ecx/cx:

если проверяемое условие истинно, то есть содержимое ecx/cx

равно 0, то перейти к ячейке, обозначенной операндом метка;

если проверяемое условие ложно, то есть содержимое ecx/cx

не равно 0, то передать управление следующей за jcxz/jecxz команде программы.

Состояние флагов после выполнения команды:

11 07 06 05 04 03 02 01 00
OF SF ZF 0 AF 0 PF 1 CF
?     ?   r

Применение (кроме jcxz/jecxz):


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

Команда Состояние проверяемых флагов Условие перехода
JA CF = 0 и ZF = 0 если выше
JAE CF = 0 если выше или равно
JB CF = 1 если ниже
JBE CF = 1 или ZF = 1 если ниже или равно
JC CF = 1 если перенос
JE ZF = 1 если равно
JZ ZF = 1 если 0
JG ZF = 0 и SF = OF если больше
JGE SF = OF если больше или равно
JL SF <> OF если меньше
JLE ZF=1 или SF <> OF если меньше или равно
JNA CF = 1 и ZF = 1 если не выше
JNAE CF = 1 если не выше или равно
JNB CF = 0 если не ниже
JNBE CF=0 и ZF=0 если не ниже или равно
JNC CF = 0 если нет переноса
JNE ZF = 0 если не равно
JNG ZF = 1 или SF <> OF если не больше
JNGE SF <> OF если не больше или равно
JNL SF = OF если не меньше
JNLE ZF=0 и SF=OF если не меньше или равно
JNO OF=0 если нет переполнения
JNP PF = 0 если количество единичных битов результата нечетно (нечетный паритет)
JNS SF = 0 если знак плюс (знаковый (старший) бит результата равен 0)
JNZ ZF = 0 если нет нуля
JO OF = 1 если переполнение
JP PF = 1 если количество единичных битов результата четно (четный паритет)
JPE PF = 1 то же, что и JP, то есть четный паритет
JPO PF = 0 то же, что и JNP
JS SF = 1 если знак минус (знаковый (старший) бит результата равен 1)
JZ ZF = 1 если ноль
<
Логические условия "больше" и "меньше" относятся к сравнениям целочисленных значений со знаком, а "выше и "ниже" — к сравнениям целочисленных значений без знака. Если внимательно посмотреть, то у многих команд можно заметить одинаковые значения флагов для перехода. Это объясняется наличием нескольких ситуаций, которые могут вызвать одинаковое состояние флагов. В этом случае с целью удобства ассемблер допускает несколько различных мнемонических обозначений одной и той же машинной команды условного перехода. Эти команды ассемблера по действию абсолютно равнозначны, так как это одна и та же машинная команда. Изначально в микропроцессоре i8086 команды условного перехода могли осуществлять только короткие переходы в пределах -128...+127 байт, считая от следующей команды. Начиная с микропроцессора i386, эти команды уже могли выполнять любые переходы в пределах текущего сегмента команд. Это стало возможным за счет введения в систему команд микропроцессора дополнительных машинных команд. Для реализации межсегментных переходов необходимо комбинировать команды условного перехода и команду безусловного перехода jmp. При этом можно воспользоваться тем, что практически все команды условного перехода парные, то есть имеют команды, проверяющие обратные условия.
Применение jcxz/jecxz:

Команда Состояние флагов в eflags/flags Условие перехода
JCXZ не влияет если регистр CX=0
JECXZ не влияет если регистр ECX=0
Команду jcxz/jecxz удобно использовать со всеми командами, использующими регистр ecx/cx для своей работы. Это команды организации цикла и цепочечные команды. Очень важно отметить то, что команда jcxz/jecxz, в отличие от других команд перехода, может выполнять только близкие переходы в пределах -128...+127 байт, считая от следующей команды. Поэтому для нее особенно актуальна проблема передачи управления далее чем в указанном диапазоне. Для этого можно привлечь команду безусловного перехода jmp. Например, команду jcxz/jecxz можно использовать для предварительной проверки счетчика цикла в регистре cx для обхода цикла, если его счетчик нулевой.
...         jcxz    m1      ;обойти цикл, если cx=0 cycl: ;некоторый цикл         loop    cycl m1:     ...         

См. также: уроки 10, 11 и команду


JMP


(JuMP)

Переход безусловный

Схема команды:  jmp метка 

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


Алгоритм работы:


Команда jmp в зависимости от типа своего операнда изменяет содержимое либо только одного регистра eip, либо обоих регистров cs и eip:

если операнд в команде jmp — метка в текущем сегменте команд (a8, 16, 32), то ассемблер формирует машинную команду, операнд которой является значением со знаком, являющимся смещением перехода относительно следующей за jmp команды. При этом виде перехода изменяется только регистр eip/ip;

если операнд в команде jmp — символический идентификатор ячейки памяти (m16, 32, 48), то ассемблер предполагает, что в ней находится адрес, по которому необходимо передать управление. Этот адрес может быть трех видов:

значением абсолютного смещения метки перехода относительно начала сегмента кода. Размер этого смещения может быть 16 или 32 бит в зависимости от режима адресации;

дальним указателем на метку перехода в реальном и защищенном режимах, содержащим два компонента адреса — сегментный и смещение. Размеры этих компонентов также зависят от установленного режима адресации (use16 или use32). Если текущим режимом является use16, то адрес сегмента и смещение занимают по 16 бит, причем смещение располагается в младшем слове двойного слова, отводимого под этот полный адрес метки перехода. Если текущим режимом является use32, то адрес сегмента и смещение занимают, соответственно, 16 и 32 бит, — в младшем двойном слове находится смещение, в старшем — адрес сегмента;

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

Для понимания различий механизмов перехода в реальном и защищенном режимах нужно помнить следующее. В реальном режиме микропроцессор просто изменяет cs и eip/ip в соответствии с содержимым указателя в памяти. В защищенном режиме микропроцессор предварительно анализирует байт прав доступа AR в дескрипторе, номер которого определяется по содержимому сегментной части указателя. В зависимости от состояния байта AR микропроцессор выполняет либо переход, либо переключение задач.
Состояние флагов после выполнения команды (за исключением случая переключения задач):

выполнение команды не влияет на флаги

Применение:


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



Константные выражения в условных директивах


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

.data mas db ... len dd ... ... .code ... .erre (len-mas) lt 10 ;генерация ошибки, если длина ;области mas меньше 10 байт ...

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

истина — число, которое содержит двоичные единицы во всех разрядах; ложь — число, которое содержит двоичные нули во всех разрядах.

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

Таблица 1. Операторы отношений

Оператор/СинтаксисРезультат отношения
EQ (equal) — равновыражение_1 EQ выражение_2истина — если выражение_1 равно выражение_2
NE (not equal) — не
равно
Выражение_1 NE выражение_2Истина — если выражение_1 не равно выражение_2
LT (less than) — меньшеВыражение_1 LT выражение_2Истина — если выражение_1 меньше выражение_2
LE (less or equal) — меньше или равноВыражение_1 LE выражение_2Истина — если выражение_1 меньше или равно выражение_2
GT (greater than) — большеВыражение_1 GT выражение_2Истина — если выражение_1 больше выражение_2
GE (greater or equal) — больше или равноВыражение_1 GE выражение_2Истина — если выражение_1 больше или равно выражение_2

Таблица 2. Логические операторы

ОператорСинтаксисРезультат
NOT — логическое отрицаниеNOT выражениеИстина — если выражение ложно;
ложь — если выражение истинно
AND — логическое Ивыражение_1 AND выражение_2Истина — если выражение_1 и выражение_2 истинны
OR — логическое ИЛИвыражение_1 OR выражение_2Истина — если выражение_1 или выражение_2 истинны
XOR — исключающее ИЛИвыражение_1 XOR выражение_2Истина — если выражение_1 = (NOT выражение_2)



LAHF


(Load AH register from register Flags)

Загрузка регистра AH флагами из регистра eFlags/Flags

 

Схема команды:  lahf 

Назначение: извлечение содержимого младшего байта регистра eflags/flags, в котором содержатся пять флагов: cf, pf, af, zf и sf.


Алгоритм работы:


команда загружает регистр ah содержимым младшего байта регистра eflags/flags. Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Из-за того, что регистр флагов непосредственно недоступен, команду lahf можно применять для анализа и последующего изменения командой sahf состояния некоторых флагов регистра eflags/flags.

;сбросить в ноль флаг cf         lahf         and     ah,11111110b         sahf         

См. также: команду



LDS/LES/LFS/LGS/LSS


(Load pointer into ds/es/fs/gs/ss segment register)

Загрузка сегментного регистра ds/es/fs/gs/ss указателем из памяти

Схема команды:  lds приемник,источник 
les приемник,источник 
lfs приемник,источник 
lgs приемник,источник 
lss приемник,источник 

Назначение: получение полного указателя в виде сегментной составляющей и смещения.


Алгоритм работы:


Алгоритм работы команды зависит от действующего режима адресации (use16 или use32):

если use16, то загрузить первые два байта из ячейки памяти источник в 16-разрядный регистр, указанный операндом приемник. Следующие два байта в области источник должны содержать сегментную составляющую некоторого адреса; они загружаются в регистр ds/es/fs/gs/ss;

если use32, то загрузить первые четыре байта из ячейки памяти источник в 32-разрядный регистр, указанный операндом приемник. Следующие два байта в области источник должны содержать сегментную составляющую, или селектор, некоторого адреса; они загружаются в регистр ds/es/fs/gs/ss.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Таким образом, с помощью данных команд в паре регистров ds/es/fs/gs/ss и приемник оказывается полный адрес некоторой ячейки памяти. Это обстоятельство можно использовать, к примеру, при работе с цепочечными командами, где существуют жесткие соглашения на размещение адресов обрабатываемых строк. Помните, что любая загрузка сегментного регистра приводит к обновлению соответствующего теневого регистра (см. урок 16). Смотрите также описание команды cmps с примером использования.
См. также: уроки 5, 7, 11, команды и операторы ассемблера и



LEA


(Load Effective Address)

Загрузка эффективного адреса

 

Схема команды:  lea приемник,источник 

Назначение: получение эффективного адреса (смещения) источника.


Алгоритм работы:


алгоритм работы команды зависит от действующего режима адресации (use16 или use32):

если use16, то в регистр приемник загружается 16-битное значение смещения операнда источник;

если use32, то в регистр приемник загружается 32-битное значение смещения операнда источник.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Данная команда является альтернативой оператору ассемблера offset. В отличие от offset команда lea допускает индексацию операнда, что позволяет более гибко организовать адресацию операндов.

;загрузить в регистр bx адрес пятого элемента массива mas .data mas     db      10 dup (0) .code ...         mov     di,4         lea     bx,mas[di] ;или         lea     bx,mas[4] ;или         lea     bx,mas+4         

См. также: уроки 5, 7, 11 и команды , , , , , , операторы ассемблера и



LEAVE


(LEAVE from procedure)

Выход из процедуры

Схема команды:  leave 

Назначение: удаление из стека области локальных (динамических) переменных, выделенной командой enter.


Алгоритм работы:


команда выполняет обратные команде enter действия:

содержимое ebp/bp копируется в esp/sp, тем самым восстанавливается значение esp/sp, которое было до вызова данной процедуры. С другой стороны, восстановление старого значения esp/sp означает освобождение пространства в стеке, отведенного для завершающейся процедуры (локальные переменные процедуры уничтожаются);

из стека восстанавливается содержимое ebp/bp, которое было до входа в процедуру. После этого действия значение esp/sp также становится таким, каким оно было до входа в процедуру.

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

выполнение команды не влияет на флаги

Применение:


Команда leave не имеет операндов и выполняет обратные команде enter действия. Эта команда должна находиться непосредственно перед командой ret, которая в зависимости от соглашений конкретного языка по вызову процедур удаляет или не удаляет аргументы из стека (см. урок 14).

.286 proc1   proc         enter   16,0 ...         leave         ret proc1   endp         

См. также: урок 14 и команды ,



LGDT


(Load Global Descriptor Table)

Загрузка регистра глобальной дескрипторной таблицы

 

Схема команды:  lgdt источник 

Назначение: загрузка регистра gdtr значениями базового адреса и размера глобальной дескрипторной таблицы GDT.


Алгоритм работы:


команда выполняет загрузку 16 бит размера и 32 бит значения базового адреса начала таблицы GDT в памяти в системный регистр gdtr. Эта загрузка производится в соответствии с форматом этого регистра (см. урок 16). Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команду lgdt применяют при подготовке к переходу в защищенный режим для загрузки системного регистра gdtr. В качестве операнда в команде указывается адрес области в формате 16+32. Младшее слово области — размер GDT, двойное слово по старшему адресу — значение базового адреса начала этой таблицы. Данные два компонента должны быть сформированы в памяти заранее.

.286 ;структура для описания псевдодескриптора gdtr point   STRUC lim     dw      0 adr     dd      0  ENDS .data point_gdt       point   

.code ... ;загружаем gdtr         xor     eax,eax         mov     ax,gdt_seg         shl     eax,4         mov     point_gdt.adr,eax         lgdt    point_gdt ...         

См. также: уроки 16, 17 и команду



LIDT


(Load Interrupt Descriptor Table)

Загрузка регистра глобальной дескрипторной таблицы

Схема команды:  lidt источник 

Назначение: загрузка регистра idtr значениями базового адреса и размера глобальной дескрипторной таблицы IDT.


Алгоритм работы:


Команда lidt аналогична lgdt, но для дескрипторной таблицы прерываний IDT (см. урок 17).
Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команду lidt применяют при подготовке к переходу в защищенный режим для загрузки системного регистра idtr. В качестве операнда в команде указывается адрес области в формате 16+32. Младшее слово области — размер IDT, двойное слово по старшему адресу — значение базового адреса начала этой таблицы. Два данных компонента должны быть сформированы в памяти заранее.

.386 ;структура для описания псевдодескрипторов gdtr и idtr point   STRUC lim     dw      0 adr     dd      0  ENDS .data point_idt       point   

.code ... ;загружаем idtr         xor     eax,eax         mov     ax,IDT_SEG         shl     eax,4         mov     point_idt.adr,eax         lidt    point_idt ...         

См. также: урок 17 и команду



LODS/LODSB/LODSW/LODSD


(LOad String Byte/Word/Double word operands)

Загрузка строки байтов/слов/двойных слов

 

Схема команды:  lods источник 
lodsb 
lodsw 
lodsd

Назначение: загрузка элемента из последовательности (цепочки) в регистр-аккумулятор al/ax/eax.


Алгоритм работы:

загрузить элемент из ячейки памяти, адресуемой парой ds:esi/si, в регистр al/ax/eax. Размер элемента определяется неявно (для команды lods) или явно в соответствии с применяемой командой (для команд lodsb, lodsw, lodsd);

изменить значение регистра si на величину, равную длине элемента цепочки. Знак этой величины зависит от состояния флага df:

df=0 — значение положительное, то есть просмотр от начала цепочки к ее концу;

df=1 — значение отрицательное, то есть просмотр от конца цепочки к ее началу.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


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

str     db      ... ...         cld         lea     si,str         lodsb   ;загрузить первый байт из str в al         

См. также: урок 11 и команды , , , , , ,



LOOP


(LOOP control by register cx)

Управление циклом по cx

Схема команды:  loop метка 

Назначение: организация цикла со счетчиком в регистре cx.


Алгоритм работы:

выполнить декремент содержимого регистра ecx/cx;

анализ регистра ecx/cx:

если ecx/cx=0, передать управление следующей за loop команде;

если ecx/cx=1, передать управление команде, метка которой указана в качестве операнда loop.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


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

для предотвращения выполнения цикла при нулевом ecx/cx используйте команду jecxz/jcxz. Если этого не сделать, то при изначально нулевом ecx/cx

цикл повторится 4 294 967 295/65 536 раз;

смещение метки, являющейся операндом loop, не должно выходить из диапазона -128...+127 байт. Это смещение, как и в командах условного перехода, является относительным от значения счетчика адреса следующей за loop команды.

 

        mov     cx,10 ...         jcxz    m1 cycl: ;тело цикла         loop    cycl m1:         

См. также: урок 10 и команды /, /, /



LOOPE/LOOPZ LOOPNE/LOOPNZ


(LOOP control by register cx not equal 0 and ZF=1)

(LOOP control by register cx not equal 0 and ZF=0)

Управление циклом по cx c учетом значения флага ZF

 

Схема команды:  loope/loopz метка 
loopne/loopnz метка 

Назначение: организация цикла со счетчиком в регистре cx с учетом флага zf.


Алгоритм работы:

выполнить декремент содержимого регистра ecx/cx;

проанализировать регистр ecx/cx:

если ecx/cx=0, передать управление следующей за loopxx команде;

если ecx/cx=1, передать управление команде, метка которой указана в качестве операнда loopxx;

анализ флага zf:

если zf=0, для команд loope/loopz это означает выход из цикла, для команд loopne/loopnz — переход к началу цикла;

если zf=1, для команд loope/loopz это означает переход к началу цикла, для команд loopne/loopnz — выход из цикла.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команды loopxx удобно использовать вместе с командами, которыe в результате своей работы меняют значение флага zf. Типичный пример — команда сравнения cmp.

;найти первый пробел в строке символов str     db      'Найти первый пробел' str_size=$-str ...         cld         mov     cx,str_size         lea     si,str cycl:         lodsb         cmp     al,' '         loopne  cycl         jcxz    m1      ;переход, если пробелов нет         dec     si      ;в si — адрес пробела в строке str ... m1         

См. также: уроки 8, 10, 11 и команду



Макродирективы


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

директивы повторения , ,

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



Макрокоманды


Идейно макрокоманда представляет собой дальнейшее развитие механизма замены текста.
С помощью макрокоманд в текст программы можно вставлять последовательности строк (которые логически могут быть данными или командами) и даже более того — привязывать их к контексту места вставки.

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

Листинг 1. Пример программы на ассемблере ;---------Prg_3_1.asm---------------------------------- ;Программа преобразования двузначного шестнадцатеричного числа ;в символьном виде в двоичное представление. ;Вход: исходное шестнадцатеричное число из двух цифр, ;вводится с клавиатуры. ;Выход: результат преобразования должен ;быть в регистре al. ;------------------------------------------------------ data segment para public 'data' ;сегмент данных message db 'Введите две шестнадцатеричные цифры,$' data ends stk segment stack db 256 dup ('?') ;сегмент стека stk ends code segment para public 'code' ;начало сегмента кода main proc ;начало процедуры main assume cs:code,ds:data,ss:stk mov ax,data ;адрес сегмента данных в регистр ax mov ds,ax ;ax в ds mov ah,9 mov dx,offset message int 21h xor ax,ax ;очистить регистр ax mov ah,1h ;1h в регистр ah int 21h ;генерация прерывания с номером 21h mov dl,al ;содержимое регистра al в регистр dl sub dl,30h ;вычитание: (dl)=(dl)-30h cmp dl,9h ;сравнить (dl) с 9h jle M1 ;перейти на метку M1 если dl<9h или dl=9h sub dl,7h ;вычитание: (dl)=(dl)-7h M1: ;определение метки M1 mov cl,4h ;пересылка 4h в регистр cl shl dl,cl ;сдвиг содержимого dl на 4 разряда влево int 21h ;вызов прерывания с номером 21h sub al,30h ;вычитание: (dl)=(dl)-30h cmp al,9h ;сравнить (al) с 9h 28 jle M2 ;перейти на метку M2, если al<9h или al=9h sub al,7h ;вычитание: (al)=(al)-7h M2: ;определение метки M2 add dl,al ;сложение: (dl)=(dl)+(al) mov ax,4c00h ;пересылка 4c00h в регистр ax int 21h ;вызов прерывания с номером 21h main endp ;конец процедуры main code ends ;конец сегмента кода end main ;конец программы с точкой входа main

<
/a>

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

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

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

тело макроопределения

endm

Где должны располагаться макроопределения?
Есть три варианта:

В начале исходного текста программы до сегмента кода и данных с тем, чтобы не ухудшать читабельность программы.
Этот вариант следует применять в случаях, если определяемые вами макрокоманды актуальны только в пределах одной этой программы. В отдельном файле.
Этот вариант подходит при работе над несколькими программами одной проблемной области. Чтобы сделать доступными эти макроопределения в конкретной программе, необходимо в начале исходного текста этой программы записать директиву include имя_файла, к примеру:
masm model small include show.inc ;в это место будет вставлен текст файла show.inc ...



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

Недостаток двух последних способов в том, что в исходный текст программы включаются абсолютно все макроопределения.
Для исправления ситуации можно использовать директиву purge, в качестве операндов которой через запятую перечисляются имена макрокоманд, которые не должны включаться в текст программы.
К примеру,
... include iomac.inc purge _outstr,_exit ...

<


В данном случае в исходный текст программы перед началом компиляции TASM вместо строки include iomac.inc вставит строки из файла iomac.inc. Но вставленный текст будет отличаться от оригинала тем, что в нем будут отсутствовать макроопределения _outstr и _exit.

А теперь вернемся к программе из . Проанализируем ее текст, выявим повторяющиеся участки и составим для них макроопределения (листинг 2).


Листинг 2. Пример 1 создания и использования макрокоманд ;prg_3_1.asm с макроопределениями init_ds macro


;Макрос настройки ds на сегмент данных mov ax,data mov ds,ax endm


out_str macro str ;Макрос вывода строки на экран. ;На входе — выводимая строка. ;На выходе - сообщение на экране. push ax mov ah,09h mov dx,offset str int 21h pop ax endm


clear_r macro rg ;очистка регистра rg xor rg,rg endm


get_char macro ;ввод символа ;введенный символ в al mov ah,1h int 21h endm


conv_16_2 macro ;макрос преобразования символа шестнадцатеричной цифры ;в ее двоичный эквивалент в al sub dl,30h cmp dl,9h jle $+5 sub dl,7h endm


exit macro ;макрос конца программы mov ax,4c00h int 21h endm

data segment para public 'data' message db 'Введите две шестнадцатеричные цифры (буквы A,B,C,D,E,F — прописные): $' data ends

stk segment stack db 256 dup('?') stk ends


code segment para public 'code' assume cs:code,ds:data,ss:stk main proc init_ds out_str message


clear_r ax get_char mov dl,al conv_16_2 mov cl,4h shl dl,cl get_char conv_16_2 add dl,al xchg dl,al ;результат в al exit main endp code ends end main



В листинге 2 в строках , , , , , описаны макроопределения. Их назначение приведено сразу после заголовка в теле каждого макроопределения.

Все эти макроопределения можно использовать и при написании других программ. Посмотрите на модернизированный исходный текст программы из листинга 3.1 в листинге 2 (строки ). Если не обращать внимания на некоторые неясные моменты, то сам сегмент кода стал внешне более читабельным и даже можно сказать, что в нем появился какой то смысл.

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



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

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

Результатом применения данной синтаксической конструкции в исходном тексте программы будет ее замещение строками из конструкции тело макроопределения. Но это не простая замена.
Обычно макрокоманда содержит некоторый список аргументов — список_фактических_аргументов, которыми корректируется макроопределение.
Места в теле макроопределения, которые будут замещаться фактическими аргументами из макрокоманды, обозначаются с помощью так называемых формальных аргументов.
Таким образом, в результате применения макрокоманды в программе формальные аргументы в макроопределении замещаются соответствующими фактическими аргументами; в этом и заключается учет контекста.
Процесс такого замещения называется макрогенерацией, а результатом этого процесса является макрорасширение.

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



(строки ), как был осуществлен вызов макрокоманды clear_rg с фактическим параметром ax (строка ) и как выглядит результат работы макрогенератора, сформировавшего команду ассемблера xor ax,ax (строка 75);
24 clear_r macro rg 25 ;очистка регистра rg 26 xor rg,rg 27 endm ... 74 clear_r ax 75000E 33 C0 xor ax,ax

Таким образом в итоге мы получили то, что и требовалось — команду очистки заданного регистра, в данном случае ax.
В другом месте программы вы можете выдать ту же макрокоманду, но уже с другим именем регистра.

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

строка может состоять из:

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

для того чтобы указать, что некоторый символ внутри строки, представляющей фактический параметр, является собственно символом, а не чем-то иным, например некоторым разделителем или ограничивающей скобкой, применяется специальный оператор “!”.
Этот оператор ставится непосредственно перед описанным выше символом, и его действие эквивалентно заключению данного символа в угловые скобки (см. предыдущий пункт); если требуется вычисление в строке некоторого константного выражения, то в начале этого выражения нужно поставить знак “%”:
% константное_выражение — значение константное_выражение

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

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

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

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

<


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

REQ, которое говорит о том, что требуется обязательное явное задание фактического аргумента при вызове макрокоманды; =<любая_строка> — если аргумент при вызове макрокоманды не задан, то в соответствующие места в макрорасширении будет вставлено значение по умолчанию, соответствующее значению любая_строка.
Будьте внимательны: символы, входящие в любая_строка, должны быть заключены в угловые скобки.

Но не всегда ассемблер может распознать в теле макроопределения формальный аргумент. Это, например, может произойти в случае, когда он является частью некоторого идентификатора. В этом случае последовательность символов формального аргумента отделяют от остального контекста с помощью специального символа &.
Этот прием часто используется для задания модифицируемых идентификаторов и кодов операций.
К примеру, определим макрос, который предназначен для генерации в программе некоторой таблицы, причем параметры этой таблицы можно задавать с помощью аргументов макрокоманды:
... def_table macro type=b,len=REQ tabl_&type d&type len dup (0) endm ... .data def_tabl b,10 def_tabl w,5

После того как вы подвергнете трансляции текст программы, содержащий эти строки, вы получите следующие макрорасширения:
tabl_b db 10 dup (0) tabl_w dw 10 dup (0)

Символ & можно применять и для распознавания формального аргумента в строке, заключенной в кавычки ' '. Например:
num_char macro message ;... ;подсчитать количество (num) символов в строке jmp m1 elem db 'Строка &message содержит ' ;число символов в строке message в коде ASCII num db 2 dup (0) db ' символов',10,13,'$' ;конец строки для вывода функцией 09h m1: ;... ;вывести elem на экран endm

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

<


Эту директиву необходимо размещать непосредственно за заголовком макроопределения.
Результатом работы этой директивы будет генерация в каждом экземпляре макрорасширения уникальных имен для всех идентификаторов, перечисленных в список_идентификаторов. Эти уникальные имена имеют вид ??xxxx, где хххх — шестнадцатеричное число.
Для первого идентификатора в первом экземпляре макрорасширения хххх= 0000, для второго — хххх= 0001 и т. д. Контроль за правильностью размещения и использования этих уникальных имен берет на себя ассемблер.

Для того чтобы вам окончательно все стало понятно, введем и подвергнем трансляции листинг 3. В нем, кроме некоторых ранее рассмотренных макрокоманд, содержится макрокоманда num_char. Ее назначение — подсчитывать количество символов в строке, адрес которой передается этой макрокоманде в качестве фактического параметра. Строка должна удовлетворять требованию, предъявляемому к строке, предназначенной для вывода на экран функцией 09h прерывания 21h, то есть заканчиваться символом $.
Другой момент, который нашел отражение в этой программе, — использование символа $ для распознавания формального аргумента в строке, заключенной в кавычки ' ' (см. последний фрагмент).

Листинг 3. Пример 2 создания и использования макрокоманд
;prg_13_2.asm init_ds macro ;макрос настройки ds на сегмент данных mov ax,data mov ds,ax xor ax,ax endm out_str macro str ;макрос вывода строки на экран. ;На входе — выводимая строка. ;На выходе — сообщение на экране. push ax mov ah,09h mov dx,offset str int 21h pop ax endm exit macro ;макрос конца программы mov ax,4c00h int 21h endm num_char macro message local m1,elem,num,err_mes,find,num_exit ;макрос подсчета количества символов в строке. ;Длина строки — не более 99 символов. ;Вход: message — адрес строки символов, ограниченной '$' ;Выход: в al — количество символов в строке message и вывод сообщения jmp m1 elem db 'Строка &message содержит ' num db 2 dup (0) ;число символов в строке message в коде ASCII db ' символов',10,13,'$' ;конец строки для вывода функцией 09h err_mes db 'Строка &message не содержит символа конца строки',10,13,'$' m1: ;сохраняем используемые в макросе регистры push es push cx push ax push di push ds pop es ;настройка es на ds mov al,'$' ;символ для поиска — `$` cld ;сброс флага df lea di,message ;загрузка в es:di смещения строки message push di ;запомним di — адрес начала строки mov cx,99 ;для префикса repne — максимальная длина строки ;поиск в строке (пока нужный символ и символ в строке не равны) ;выход — при первом совпавшем repne scasb je find ;если символ найден — переход на обработку ;вывод сообщения о том, что символ не найден push ds ;подставляем cs вместо ds для функции 09h (int21h) push cs pop ds out_str err_mes pop ds jmp num_exit ;выход из макроса find: ;совпали ;считаем количество символов в строке: pop ax ;восстановим адрес начала строки sub di,ax ;(di)=(di)-(ax) xchg di,ax ;(di) (ax) sub al,3 ;корректировка на служебные символы — 10, 13, '$' aam ;в al две упакованные BCD-цифры результата подсчета or ax,3030h ;преобразование результата в код ASCII mov cs:num,ah mov cs:num+1,al ;вывести elem на экран push ds ;подставляем cs вместо ds для функции 09h (int21h) push cs pop ds out_str elem pop ds num_exit: push di push ax push cx push es endm

data segment para public 'data' msg_1 db 'Строка_1 для испытания',10,13,'$' msg_2 db 'Строка_2 для второго испытания',10,13,'$' data ends

stk segment stack db 256 dup('?') stk ends

code segment para public 'code' assume cs:code,ds:data,ss:stk main proc init_ds out_str msg_1 num_char msg_1 out_str msg_2 num_char msg_2 exit main endp code ends end main

В теле макроопределения можно размещать комментарии и делать это особым образом.
Если применить для обозначения комментария не одну, как обычно, а две подряд идущие точки с запятой, то при генерации макрорасширения этот комментарий будет исключен.
Если по какой-то причине необходимо присутствие комментария в макрорасширении, то его нужно задавать обычным образом, то есть с помощью одинарной точки с запятой. Например:
mes macro messsage ... ;этот комментарий будет включен в текст листинга ... ;;этот комментарий не будет включен в текст листинга endm




Макросредства языка ассемблера


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

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

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

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

Но это все глобальные направления, по которым развивается сам транслятор от версии к версии.
Что же делать программисту для решения его локальной задачи, для облегчения работы в определенной проблемной области?
Для этого разработчики компиляторов ассемблера включают в язык и постоянно совершенствуют аппарат макросредств. Этот аппарат является очень мощным и важным.

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

Макроассемблер в общей схеме трансляции программы на TASM


Если вы знакомы с языком С или С++, то конечно помните широко применяемый в них механизм препроцессорной обработки. Он является некоторым аналогом механизма заложенного в работу макроассемблера. Для тех, кто ничего раньше не слышал об этих механизмах, поясню их суть.
Основная идея — использование подстановок, которые замещают определенным образом организованную символьную последовательность другой символьной последовательностью. Создаваемая таким образом последовательность может быть как последовательностью, описывающей данные, так и последовательностью программных кодов. Главное здесь то, что на входе макроассемблера может быть текст программы весьма далекий по виду от программы на языке ассемблера, а на выходе обязательно будет текст на чистом ассемблере, содержащем символические аналоги команд системы машинных команд микропроцессора.

Таким образом, обработка программы на ассемблере с использованием макросредств неявно осуществляется транслятором в две фазы(рис. 1).
На первой фазе работает часть компилятора, называемая макроассемблером, функции которого на идейном уровне мы описали чуть выше.
На второй фазе трансляции работает непосредственно ассемблер, задачей которого является формирование объектного кода, содержащего текст исходной программы в машинном виде.

Далее мы обсудим основной набор макросредств, доступных при использовании компилятора TASM. Отметим, что большинство этих средств доступно и в компиляторе с языка ассемблера фирмы Microsoft.
Обсуждение начнем с простейших средств и закончим более сложными.



>


Массивы


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

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

в программе? , то есть как задать начальные значения его элементов? к элементам массива? с размерностью более одной? типовых операций с массивами?



Методы работы со структурой


Идея введения структурного типа в любой язык программирования состоит в объединении разнотипных переменных в один объект.
В языке должны быть средства доступа к этим переменным внутри конкретного экземпляра структуры. Для того чтобы сослаться в команде на поле некоторой структуры, используется специальный оператор — символ "." (точка). Он используется в следующей синтаксической конструкции:

адресное_выражение.имя_поля_структуры

Здесь:

адресное_выражение — идентификатор переменной некоторого структурного типа или выражение в скобках в соответствии с указанными ниже синтаксическими правилами (рис. 1); имя_поля_структуры — имя поля из шаблона структуры.
Это, на самом деле, тоже адрес, а точнее, смещение поля от начала структуры.

Таким образом оператор "." (точка) вычисляет выражение

(адресное_выражение) + (имя_поля_структуры)

Продемонстрируем на примере определенной нами структуры worker некоторые приемы работы со структурами.
К примеру, извлечь в ax значения поля с возрастом. Так как вряд ли возраст трудоспособного человека будет больше величины 99 лет, то после помещения содержимого этого символьного поля в регистр ax его будет удобно преобразовать в двоичное представление командой .
Будьте внимательны, так как из-за принципа хранения данных “младший байт по младшему адресу” старшая цифра возраста будет помещена в al, а младшая — в ah.
Для корректировки достаточно использовать команду xchg al,ah:

mov



Микропроцессоров Intel


Материал, приведенный в данном разделе справочной системы, связан с уроком 6, на котором мы рассматривали формат машинной команды микропроцессора и систему его команд в целом.

Выберите тему:




 
 
 

 
 
 
 
 
 
 

 
 
 
 



MOV


(MOVe operand)

Пересылка операнда

Схема команды:  mov приемник,источник 

Назначение: пересылка данных между регистрами или регистрами и памятью.


Алгоритм работы:


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

выполнение команды не влияет на флаги

Применение:


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

направление пересылки в команде mov всегда справа налево, то есть из второго операнда в первый;

значение второго операнда не изменяется;

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

лишь один из операндов может быть сегментным регистром;

желательно использовать в качестве одного из операндов регистр al/ax/eax, так как в этом случае TASM генерирует более быструю форму команды mov.

 

        mov     al,5         mov     bl,al         mov     bx,ds         

См. также: урок 10 и команды movs, , ,



MOVS/MOVSB/MOVSW/MOVSD


(MOVe String Byte/Word/Double word)

Пересылка строк байтов/слов/двойных слов

Схема команды:  movs приемник,источник 
movsb 
movsw 
movsd

Назначение: пересылка элементов двух последовательностей (цепочек) в памяти.


Алгоритм работы:

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

адрес источника — в пару регистров ds:esi/si (ds по умолчанию, допускается замена сегмента);

адрес приемника — в пару регистров es:edi/di (замена сегмента не допускается);

в зависимости от состояния флага df изменить значение регистров esi/si и edi/di:

если df=0, то увеличить содержимое этих регистров на длину структурного элемента последовательности;

если df=1, то уменьшить содержимое этих регистров на длину структурного элемента последовательности;

если есть префикс повторения, то выполнить определяемые им действия (см. команду rep).

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команды пересылают элемент из одной ячейки памяти в другую. Размеры пересылаемых элементов зависят от применяемой команды. Команда movs может работать с элементами размером в байт, слово, двойное слово. В качестве операндов в команде указываются идентификаторы последовательностей этих элементов в памяти. Реально эти идентификаторы используются лишь для получения типов элементов последовательностей, а их адреса должны быть предварительно загружены в указанные выше пары регистров. Транслятор, обработав команду movs и выяснив тип операндов, генерирует одну из машинных команд movsb, movsw или movsd. Машинного аналога для команды movs нет. Для адресации операнда приемник обязательно должен использоваться регистр es.
Для того чтобы эти команды можно было использовать для пересылки последовательности элементов, имеющих размерность байт, слово, двойное слово, необходимо использовать префикс rep. Префикс rep заставляет циклически выполняться команды пересылки до тех пор, пока содержимое регистра ecx/cx не станет равным нулю.

str1    db      'str1 копируется в str2' len_str1=$-str1 a_str1  dd      str1 str2    db      len_str1 dup (' ') a_str2  dd      str2 ...         mov     cx,len_str1         lds     si,str1         les     di,str2         cld rep     movsb         

См. также: урок 11 и команды , , , , , ,



MOVSX


(MOVe and Sign eXtension)

Пересылка со знаковым расширением

 

Схема команды:  movsx приемник,источник 

Назначение: преобразование элементов со знаком меньшей размерности в эквивалентные им элементы со знаком большей размерности.


Алгоритм работы:

считать содержимое источника;

записать содержимое операнда источника в операнд приемник, начиная с младших разрядов источника;

распространить значение знакового разряда источника на свободные старшие разряды операнда назначения.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


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

        mov     al,0ffh         movsx   bx,al   ;bx=0ffffh         

См. также: урок 8 и команды , , , , ,



MOVZX


(MOVe and Zero eXtension)

Пересылка с нулевым расширением

Схема команды:  movzx приемник,источник 

Назначение: преобразование элементов без знака меньшей размерности в эквивалентные им элементы без знака большей размерности.


Алгоритм работы:

считать содержимое источника;

записать содержимое операнда источника в операнд приемник, начиная с его младших разрядов;

распространить двоичный нуль на свободные старшие разряды операнда назначения.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


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

.data sl      db      ? .code ...         mov     al,0ffh         movzx   bx,al   ;bx=00ffh ... ;или из памяти:         movzx   eax,byte ptr sl         

См. также: урок 8 и команды ,
, , , ,



MUL


(MULtiply)

Умножение целочисленное без учета знака

 

Схема команды:  mul множитель_1 

Назначение: операция умножения двух целых чисел без учета знака.


Алгоритм работы:


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

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

если операнд, указанный в команде — слово, то второй сомножитель должен располагаться в ax;

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

Результат умножения помещается также в фиксированное место, определяемое размером сомножителей:

при умножении байтов результат помещается в ax;

при умножении слов результат помещается в пару dx:ax;

при умножении двойных слов результат помещается в пару edx:eax.

Состояние флагов после выполнения команды (если старшая половина результата нулевая):

11 07 06 04 02 00
OF SF ZF AF PF CF
0 ? ? ? 0

Состояние флагов после выполнения команды (если старшая половина результата ненулевая):

11 07 06 04 02 00
OF SF ZF AF PF CF
1 ? ? ? 1

Применение:


Команда mul выполняет целочисленное умножение операндов без учета их знаковых разрядов. Для этой операции необходимо наличие двух операндов-сомножителей, размещение одного из которых фиксировано, а другого задается операндом в команде. Контролировать размер результата удобно используя флаги cf и of.

mn_1    db      15 mn_2    db      25 ...         mov     al,mn_1         mul     mn_2         

См. также: урок 8 и команду



NEG


(NEGate operand)

Изменить знак операнда

Схема команды:  neg источник 

Назначение: изменение знака (получение двоичного дополнения) источника.


Алгоритм работы:

выполнить вычитание (0 – источник) и поместить результат на место источника;

если источник=0, то его значение не меняется.

Состояние флагов после выполнения команды (если результат нулевой):

11 07 06 04 02 00
OF SF ZF AF PF CF
r r r r 0

Состояние флагов после выполнения команды (если результат ненулевой):

11 07 06 04 02 00
OF SF ZF AF PF CF
r r r r 1

Применение:


Команда используется для формирования двоичного дополнения операнда в памяти или регистре. Операция двоичного дополнения предполагает инвертирование всех разрядов операнда с последующим сложением операнда с двоичной единицей. Если операнд отрицательный, то операция neg над ним означает получение его модуля.

        mov     al,2         neg     al      ;al=0feh — число -2 в дополнительном коде         

См. также: уроки 6, 8 и команду



NOP


(No OPeration)

Нет операции

 

Схема команды:  nop 

Назначение: пустая команда.


Алгоритм работы:


не производит никаких действий.
Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команда nop, занимая один байт, может использоваться для резервирования места в сегменте кода или организации программной задержки. В качестве иллюстрации можно обратиться к примеру, приведенному в описании команды hlt. В этом примере команду nop можно использовать вместо jmp $+2. Назначение jmp $+2 в этом фрагменте — задержка для синхронизации работы микропроцессора и аппаратуры компьютера.



NOT


(NOT operand)

Инвертирование операнда

Схема команды:  not источник 

Назначение: инвертирование всех битов операнда источник.


Алгоритм работы:


инвертировать все биты операнда источника: из 1 в 0, из 0 в 1.
Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


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

flag    db      0ffh ;значение флага — истина ... cycl: ...         cmp     flag,0         je      m1 ... m1:     not     flag    ;установить флаг в истину         

См. также: уроки 9, 12 и команду



О книге


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

Из книги вы узнаете:

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

Прочитав книгу, вы научитесь:

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

Изложение материала в книге ведется в форме уроков.

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

На третьем и четвертом уроках читатель знакомится с тем, что представляет собой типовая программа на языке ассемблера и что такое вообще “ассемблерный” уровень программирования на компьютере. Читатель также знакомится со средствами, с помощью которых возможны получение исполняемого модуля и его запуск для исполнения. Кроме этого на четвертом уроке читатель также узнает о средствах, которые помогут ему выйти из затруднительных положений, когда программа, написанная на ассемблере (и не только), отказывается работать.

На пятом и шестом уроках читатель познакомится с тем, как правильно оформить программу на ассемблере, узнает что представляют собой и как строятся ее синтаксические конструкции. В конце шестого урока читатель познакомится с классификацией машинных команд, в соответствии с которой будет вестись их обсуждение на последующих уроках (уроки 7, 8, 9, 10, 11).

Вторая часть книги, начиная с урока 12, посвящена углубленному изучению вопросов программирования с использованием языка ассемблера.
Так, на уроке 12 читатель подробно познакомится со средствами ассемблера для работы со структурами данных, работа с которыми обычно характерна для языков высокого уровня (таких как Pascal, C). Это несколько сближает уровень программирования на ассемблере с уровнем программирования на указанных языках.

На уроке 13 читатель очень подробно познакомится с очень полезными средствами языка ассемблера — макросредствами. Именно наличие этих средств, при надлежащем овладении ими, может сделать процесс программирования на ассемблере не только легким, но и приятным.

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

Заключительные уроки 15, 16 и 17 предназначены для логического завершения рассмотрения особенностей архитектуры современных моделей микропроцессоров фирмы Intel, отражением которых является ассемблер. Здесь читатель познакомится с режимами работы микропроцессора, поймет, как тот взаимодействует с остальными устройствами компьютера, и вообще получит массу информации, которая, возможно, не будет востребована немедленно, но, тем не менее, позволит читателю осмысленно подходить к вопросам программирования на компьютере, даже и без использования языка ассемблера.

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

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



Об ассемблере


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

Когда-то ассемблер был языком, без знания которого нельзя было заставить компьютер сделать что-либо полезное. Постепенно ситуация менялась. Появлялись более удобные средства общения с компьютером. Но, в отличие от других языков, ассемблер не умирал, более того он не мог сделать этого в принципе. Почему? В поисках ответа попытаемся понять, что такое язык ассемблера вообще.

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

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

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

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

Типичный современный компьютер (на базе i486 или Pentium) состоит из следующих компонентов (рис. 1).




Компьютер и периферийные устройства

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



Рис. 2. Структурная схема персонального компьютера

Обсудим схему на рис. 2 в несколько нетрадиционном стиле.

Человеку свойственно, встречаясь с чем-то новым, искать какие-то ассоциации, которые могут помочь ему познать неизвестное. Какие ассоциации вызывает компьютер? У меня, к примеру, компьютер часто ассоциируется с самим человеком. Почему?

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

Современным компьютерам, конечно, далеко до человека. Их можно сравнить с существами, взаимодействующими с внешним миром на уровне большого, но ограниченного набора безусловных рефлексов.
Этот набор рефлексов образует систему машинных команд. На каком бы высоком уровне вы не общались с компьютером, в конечном итоге все сводится к скучной и однообразной последовательности машинных команд.
Каждая машинная команда является своего рода раздражителем для возбуждения того или иного безусловного рефлекса. Реакция на этот раздражитель всегда однозначная и “зашита” в блоке микрокоманд в виде микропрограммы. Эта микропрограмма и реализует действия по реализации машинной команды, но уже на уровне сигналов, подаваемых на те или иные логические схемы компьютера, тем самым управляя различными подсистемами компьютера. В этом состоит так называемый принцип микропрограммного управления.

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

Таким образом, внешне являясь полиглотом, компьютер понимает только один язык — язык машинных команд. Конечно, для общения и работы с компьютером, необязательно знать этот язык, но практически любой профессиональный программист рано или поздно сталкивается с необходимостью его изучения. К счастью, программисту не нужно пытаться постичь значение различных комбинаций двоичных чисел, так как еще в 50-е годы программисты стали использовать для программирования символический аналог машинного языка, который назвали языком ассемблера. Этот язык точно отражает все особенности машинного языка. Именно поэтому, в отличие от языков высокого уровня, язык ассемблера для каждого типа компьютера свой.

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


Объединения


Представим ситуацию, когда мы используем некоторую область памяти для размещения некоторого объекта программы (переменной, массива или структуры). Вдруг после некоторого этапа работы у нас отпала надобность в использовании этих данных. Обычно память останется занятой до конца работы программы. Конечно, в принципе, ее можно было бы использовать для хранения других переменных, но при этом без принятия специальных мер нельзя изменить тип и имя. Неплохо было бы иметь возможность переопределить эту область памяти для объекта с другим типом и именем. Язык ассемблера предоставляет такую возможность в виде специального типа данных, называемого объединением.

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

Описание объединений в программе напоминает описание структур, то есть сначала описывается шаблон, в котором с помощью директив описания данных перечисляются имена и типы полей:

имя_объединения UNION

имя_объединения ENDS

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

Листинг 7, который мы сейчас рассмотрим, примечателен тем, что кроме демонстрации использования собственно типа данных “объединение” в нем показывается возможность взаимного вложения структур и объединений.
Постарайтесь внимательно отнестись к анализу этой программы. Основная идея здесь в том, что указатель на память, формируемый программой, может быть представлен в виде:

16-битного смещения; 32-битного смещения; пары из 16-битного смещения и 16-битной сегментной составляющей адреса; в виде пары из 32-битного смещения и 16-битного селектора.


Какие из этих указателей можно применять в конкретной ситуации, зависит от режима адресации (use16 или use32) и режима работы микропроцессора.
Так вот, описанный в листинге 7 шаблон объединения позволяет нам облегчить формирование и использование указателей различных типов.
Листинг 7 Пример использования объединения masm model small stack 256 .586P pnt struc ;структура pnt, содержащая вложенное объединение union ;описание вложенного в структуру объединения offs_16 dw ? offs_32 dd ? ends ;конец описания объединения segm dw ? ends ;конец описания структуры .data point union ;определение объединения, содержащего вложенную структуру off_16 dw ? off_32 dd ? point_16 pnt <>

point_32 pnt <>

point ends tst db "Строка для тестирования" adr_data point <> ;определение экземпляра объединения .code main: mov ax,@data mov ds,ax mov ax,seg tst ;записать адрес сегмента строки tst в поле структуры adr_data mov adr_data.point_16.segm,ax ;когда понадобится, можно извлечь значение из этого поля обратно, к примеру, в регистр bx: mov bx,adr_data.point_16.segm ;формируем смещение в поле структуры adr_data mov ax,offset tst ;смещение строки в ax mov adr_data.point_16.offs_16,ax ;аналогично, когда понадобится, можно извлечь значение из этого поля: mov bx,adr_data.point_16.offs_16 exit: mov ax,4c00h int 21h end main

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




Общие директивы управления листингом


Директивы этой группы предназначены для управления видом файла листинга. Все директивы являются парными — это означает, что если одна директива что-то разрешает, то другая, наоборот, запрещает. Рассмотрим назначение этих пар директив.

%LIST и %NOLIST (.LIST и .XLIST)


Директивы .LIST или %LIST определяют необходимость вывода в файл листинга всех строк исходного кода. Эти директивы подразумеваются по умолчанию.


Для запрета вывода в файл листинга всех строк исходного кода необходимо использовать директивы .XLIST или %NOLIST. В тексте программы их можно применять произвольное количество раз, при этом очередная директива отменяет действие предыдущей.

%CTLS и %NOCTLS


Если предыдущие директивы влияют на полноту представления исходного кода в целом, то директивы %CTLS и %NOCTLS управляют выводом в файл листинга самих директив управления листингом.

%SYMS и %NOSYMS Эти директивы определяют, включать (%SYMS) или не включать (%NOSYMS) в файл листинга таблицу идентификаторов. 



Опции компоновщика (редактора связей) TLINK


/x  Не создавать файл карты (map) 
/m  Создать файл карты 
/s  То же, что /m, но дополнительно в файл карты включается информация о сегментах (адрес, длина в байтах, класс, имя сегмента и т. д.) 
/l  Создать раздел в файле карты с номерами строк 
/n  Игнорировать библиотеки, указываемые другими компиляторами 
/c  Различать строчные и прописные буквы в идентификаторах (в том числе и внешних) 
/v  Включить отладочную информацию в выполняемый файл 
/3  Поддержка 32-битного кода 
/d  Предупреждать о дублировании символов в компонуемых библиотеках 
/t  Создать файл типа .com (по умолчанию .exe) 



Опции транслятора TASM


/a, /s  /a — сегменты в объектном файле должны быть размещены в алфавитном порядке; 
/s — сегменты в объектном файле следуют в порядке их описания в программе 
/c  Указание на включение в файл листинга с информацией о перекрестных ссылках 
/dимя_иденти-


фикатора[=значение] 

Определяет идентификатор. Это эквивалент директивы ассемблера =, как если бы она была записана в начале исходного текста программы 
/e, /r  /e — генерация инструкций эмуляции операций с плавающей точкой; 
/r — разрешение трансляции действительных инструкций с плавающей точкой, которые должны выполняться реальным арифметическим сопроцессором 
/h, /?  Вывод на экран справочной информации. Это эквивалентно запуску TASM без параметров 
/iпуть  Задает путь к включаемому по директиве INCLUDE файлу. Синтаксис аргумента “путь” такой же, как для команды PATH файла autoexec.bat 
/jдиректива_TASM  Определяет директивы, которые будут транслироваться перед началом трансляции исходного файла программы на ассемблере. В директиве не должно быть аргументов 
/khn  Задает максимальное количество идентификаторов, которое может содержать исходная программа, то есть фактически задается размер таблицы символов транслятора. 
По умолчанию программа может содержать до 16384 идентификаторов. Это значение можно увеличить (но не более чем до 32 768) или уменьшить до n. Сигналом к тому, что необходимо использовать данный параметр, служит появление сообщения “Out of hash space” (“Буферное пространство исчерпано”) 
/l, /la  /l — указывает на необходимость создания файла листинга, даже если он не “заказывается” в командной строке; 
/la — показать в листинге код, вставляемый транслятором для организации интерфейса с языком высокого уровня по директиве MODEL 
/ml, /mx, /mu  /ml — различать во всех идентификаторах прописные и строчные буквы; 
/mx — различать строчные и прописные символы во внешних и общих идентификаторах. Это важно при компоновке с программами на тех языках высокого уровня, в которых строчные и прописные символы в идентификаторах различаются; 
/mu — воспринимать все символы идентификаторов как прописные 
/mvn  Определение максимальной длины идентификаторов. Минимальное значение n равно 12 
/mn  Установка количества (n) проходов транслятора TASM. По умолчанию транслятор выполняет один проход. Максимально при необходимости можно задать выполнение до 5 проходов 
/n  Не выдавать в файле листинга таблицы идентификаторов (в таких таблицах содержатся все имена идентификаторов и их значения) 
/os, /o, /op, /oi  Генерация оверлейного кода
/p  Проверять наличие кода с побочными эффектами при работе в защищенном режиме 
/q  Удаление из объектной программы лишней информации, ненужной на этапе компоновки 
/t  Подавление вывода всех сообщений при условном ассемблировании, кроме сообщений об ошибках (то есть тестирование программы на предмет выявления синтаксических ошибок) 
/w0, /w1, /w2  Генерация предупреждающих сообщений разного уровня полноты: 
w0 — сообщения не генерируются; 
w1, w2 — сообщения генерируются 
/w-xxx, /w+xxx  Генерация предупреждающих сообщений класса 
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– — запретить все сообщения 
/x  Включить в листинг все блоки условного ассемблирования для директив IF, IFNDEF, IFDEF и т. п., в том числе и невыполняющиеся 
/z  При возникновении ошибок наряду с сообщением о них выводить соответствующие строки текста 
/zi, /zd, /zn  /zi — включить в объектный файл информацию для отладки; 
/zd — поместить в объектный файл информацию о номерах строк, что необходимо для работы отладчика на уровне исходного текста программы; 
/zn — запретить помещение в объектный файл отладочной информации. 



Опции транслятора TASM и редактора связей TLINK


В данном документе приведены опции командной строки:

Turbo Assembler фирмы Borland (TASM) (версия 3.0 и выше);

TLINK.

Во избежание несовместимости используйте программы TLINK и TASM одной версии.



Описание и инициализация массива в программе


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

Перечислением элементов массива в поле операндов одной из директив описания данных. При перечислении элементы разделяются запятыми. К примеру:

;массив из 5 элементов.Размер каждого элемента 4 байта: mas dd 1,2,3,4,5

Используя оператор повторения dup. К примеру:

;массив из 5 нулевых элементов. ;Размер каждого элемента 2 байта: mas dw 5 dup (0)


Такой способ определения используется для резервирования памяти с целью размещения и инициализации элементов массива. Используя директивы label и rept. Пара этих директив может облегчить описание больших массивов в памяти и повысить наглядность такого описания. Директива rept

относится к макросредствам языка ассемблера и вызывает повторение указанное число раз строк, заключенных между директивой и строкой endm. К примеру, определим массив байт в области памяти, обозначенной идентификатором mas_b. В данном случае директива label определяет символическое имя mas_b, аналогично тому, как это делают директивы резервирования и инициализации памяти. Достоинство директивы label в том, что она не резервирует память, а лишь определяет характеристики объекта. В данном случае объект — это ячейка памяти. Используя несколько директив label, записанных одна за другой, можно присвоить одной и той же области памяти разные имена и разный тип, что и сделано в следующем фрагменте:

... n=0 ... mas_b label byte mas_w label word rept 4 dw 0f1f0h endm

В результате в памяти будет создана последовательность из четырех слов f1f0. Эту последовательность можно трактовать как массив байт или слов в зависимости от того, какое имя области мы будем использовать в программе — mas_b

или mas_w. Использование цикла для инициализации значениями области памяти, которую можно будет впоследствии трактовать как массив.
Посмотрим на примере листинга 2, каким образом это делается.

Листинг 2 Инициализация массива в цикле ;prg_12_1.asm MASM MODEL small STACK 256 .data mes db 0ah,0dh,'Массив- ','$' mas db 10 dup (?) ;исходный массив i db 0 .code main: mov ax,@data mov ds,ax xor ax,ax ;обнуление ax mov cx,10 ;значение счетчика цикла в cx mov si,0 ;индекс начального элемента в cx go: ;цикл инициализации mov bh,i ;i в bh mov mas[si],bh ;запись в массив i inc i ;инкремент i inc si ;продвижение к следующему элементу массива loop go ;повторить цикл ;вывод на экран получившегося массива mov cx,10 mov si,0 mov ah,09h lea dx,mes int 21h show: mov ah,02h ;функция вывода значения из al на экран mov dl,mas[si] add dl,30h ;преобразование числа в символ int 21h inc si loop show exit: mov ax,4c00h ;стандартный выход int 21h end main ;конец программы



Описание шаблона структуры


Описание шаблона структуры имеет следующий синтаксис:

имя_структуры STRUC

имя_структуры ENDS

Здесь <описание полей> представляет собой последовательность директив описания данных db, dw, dd, dq

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

Как мы уже отметили при описании шаблона, память не выделяется, так как это всего лишь информация для транслятора.

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

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

worker struc ;информация о сотруднике nam db 30 dup (' ') ;фамилия, имя, отчество sex db 'м' ;пол, по умолчанию 'м' — мужской position db 30 dup (' ') ;должность age db 2 dup(‘ ’) ;возраст standing db 2 dup(‘ ’) ;стаж salary db 4 dup(‘ ’) ;оклад в рублях birthdate db 8 dup(‘ ’) ;дата рождения worker ends



Описание записи


Описание шаблона записи имеет следующий синтаксис (рис. 6):

имя_записи RECORD

Здесь:
<описание элементов> представляет собой последовательность описаний отдельных элементов записи согласно синтаксической диаграмме (см. рис. 6):

"

Синтаксис описания шаблона записи

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

>



Определение данных с типом структуры


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

[имя переменной] имя_структуры

Здесь:

имя переменной — идентификатор переменной данного структурного типа.
Задание имени переменной необязательно. Если его не указать, будет просто выделена область памяти размером в сумму длин всех элементов структуры. список значений — заключенный в угловые скобки список начальных значений элементов структуры, разделенных запятыми.
Его задание также необязательно.
Если список указан не полностью, то все поля структуры для данной переменной инициализируются значениями из шаблона, если таковые заданы.
Допускается инициализация отдельных полей, но в этом случае пропущенные поля должны отделяться запятыми. Пропущенные поля будут инициализированы значениями из шаблона структуры. Если при определении новой переменной с типом данной структуры мы согласны со всеми значениями полей в ее шаблоне (то есть заданными по умолчанию), то нужно просто написать угловые скобки.
К примеру: victor worker <>.

Для примера определим несколько переменных с типом описанной выше структуры.

data segment sotr1 worker <’Гурко Андрей Вячеславович’,,’художник’,’33’,‘15’,‘1800’,’26.01.64’< sotr2 worker <’Михайлова Наталья Геннадьевна’,’ж’,’программист’,’30’,’10’,’1680’,’27.10.58’< sotr3 worker <’Степанов Юрий Лонгинович’,,’художник’,’38’,’20’,’1750’,’01.01.58’< sotr4 worker <’Юрова Елена Александровна’,’ж’,’свяэист’,’32’,’2’,,’09.01.66’< sotr5 worker <> ;здесь все значения по умолчанию data ends



Определение экземпляра записи


Для использования шаблона записи в программе необходимо определить переменную с типом данной записи, для чего применяется следующая синтаксическая конструкция (рис. 7):

"

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

Если инициализировать поля не требуется, то достаточно указать ? при определении экземпляра записи:

... iotestrecord i1:1,i2:2=11,i3:1,i4:2=11,i5:2=00



OR


(logical OR)

Логическое включающее ИЛИ

 

Схема команды:  or приемник,маска 

Назначение: операция логического ИЛИ над битами операнда назначения.


Алгоритм работы:

выполнить операцию логического ИЛИ над битами операнда назначения, используя в качестве маски второй операнд — маска. При этом бит результата равен 0, если соответствующие биты операндов маска и назначения равны 0, в противном случае бит равен 1;

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

установить флаги.

Состояние флагов после выполнения команды:

11 07 06 04 02 00
OF SF ZF AF PF CF
0 r r ? 0

Применение:


Команду or можно использовать для работы с операндами на уровне битов. Типичное использование команды — установка определенных разрядов первого операнда в единицу.

        mov     al,01h         or      bl,al   ;установить нулевой бит в 1         

См. также: урок 9 и команды , ,



OUT


(OUT operand to port)

Вывод операнда в порт

Схема команды:  out ном_порта,аккумулятор 

Назначение: вывод значения в порт ввода-вывода.


Алгоритм работы:


Передать байт, слово, двойное слово из регистра al/ax/eax в порт, номер которого определяется первым операндом.
Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команда применяется для прямого управления оборудованием компьютера посредством портов. Номер порта задается первым операндом в виде непосредственного значения или значения в регистре dx. Непосредственным значением можно задать порт с номером в диапазоне 0...255. Для указания порта с большим номером используется регистр dx. Размер данных определяется размерностью второго операнда и может быть байтом, словом или двойным словом.

        out     64h,al         

См. также: уроки 2, 7, 16, 17 и команды , ,



OUTS/OUTSB/OUTSW/OUTSD


(OUTput Byte/Word/Double word String to port)

Вывод строки байтов/слов/двойных слов в порт

 

Схема команды:  outs порт,источник 
outsb 
outsw 
outsd

Назначение: вывод в порт из памяти последовательности байт, слов, двойных слов.


Алгоритм работы:

передать данные в порт ввода-вывода, номер которого загружен в регистр dx, из ячейки памяти по адресу ds:esi/si;

в зависимости от состояния флага df изменить значение регистров esi/si:

если df=0, то увеличить содержимое этих регистров на длину структурного элемента последовательности;

если df=1, то уменьшить содержимое этих регистров на длину структурного элемента последовательности;

при наличии префикса выполнить определяемые им deiqrbh (см. команду ).

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команда выводит данные в порт ввода-вывода, номер которого загружен в регистр dx, из ячейки памяти по адресу ds:esi/si (допускается замена сегмента). Недопустимо задание номера порта в команде в виде непосредственного операнда — для этого используется регистр dx. Размеры вводимых элементов зависят от применяемой команды. Команда outs может работать с элементами размером в байт, слово или двойное слово. В качестве операнда в команде указывается символическое имя ячейки памяти, из которой элемент выводится в порт ввода-вывода. Реально символическое имя используется лишь для получения типа элемента последовательности, а ее адрес должен быть предварительно загружен в пару регистров ds:esi/si. Транслятор, обработав команду outs и выяснив тип операндов, генерирует одну из машинных команд outsb, outsw или outsd. Машинного аналога для команды outs нет.
Для того чтобы эти команды можно было использовать для вывода в порт последовательности элементов, имеющих размерность байт, слово или двойное слово, необходимо использовать префикс rep. Он заставляет циклически выполняться команду вывода в порт до тех пор, пока содержимое регистра ecx/cx

не станет равным нулю.

.286 ;вывести последовательность 10 байт в порт 300h ;(номер порта взят условно) str_10  db      10 dup(0) adr_str dd      str_10         lds     si,adr_str         mov     dx,300h rep     outsb         

См. также: уроки 2, 7, 11 и команды , , , , ,



Пользовательские регистры


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

восемь 32-битных регистров, которые могут использоваться программистами для хранения данных и адресов (их еще называют (РОН)):

eax/ax/ah/al; ebx/bx/bh/bl; edx/dx/dh/dl; ecx/cx/ch/cl; ebp/bp; esi/si; edi/di; esp/sp.

cs, ds, ss, es, fs, gs; :

регистр флагов eflags/flags; регистр указателя команды eip/ip.

Пользовательские регистры микропроцессоров i486 и Pentium

Почему многие из этих регистров приведены с наклонной разделительной чертой?
Нет, это не разные регистры — это части одного большого 32-разрядного регистра. Их можно использовать в программе как отдельные объекты.
Так сделано для обеспечения работоспособности программ, написанных для младших 16-разрядных моделей микропроцессоров фирмы Intel, начиная с i8086.
Микропроцессоры i486 и Pentium имеют в основном 32-разрядные регистры. Их количество, за исключением сегментных регистров, такое же, как и у i8086, но размерность больше, что и отражено в их обозначениях — они имеют
приставку e (Extended).

Разберемся подробнее с составом и назначением пользовательских регистров.

>



POP


(POP operand from the stack)

Извлечение операнда из стека

Схема команды:  pop приемник 

Назначение: извлечение слова или двойного слова из стека.


Алгоритм работы:


Алгоритм работы команды зависит от установленного атрибута размера адреса — use16 или use32:

загрузить в приемник содержимое вершины стека (адресуется парой ss:esp/sp);

увеличить содержимое esp/sp на 4 (2 байта) для use32 (соответственно для use16).

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


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

my_proc proc    near         push    ax         push    bx ;тело процедуры, в которой изменяется содержимое ;регистров ax и bx ...         pop     bx         pop     ax         ret         endp         

См. также: уроки 7, 10, 14, 15, 16, 17 и команды , , , , , , , ,



POPA


(POP All general registers from the stack)

Извлечение всех регистров общего назначения из стека

 

Схема команды:  popa 

Назначение: извлечение из стека регистров общего назначения di, si, bp, sp, bx, dx, cx, ax.


Алгоритм работы:

извлечь из стека последовательно значения и загрузить ими регистры общего назначения di, si, bp, sp, bx, dx, cx, ax. Содержимое di восстанавливается первым. Содержимое sp извлекается, но не восстанавливается;

увеличить значение указателя стека esp/sp на 16.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


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

.386 my_proc proc    near         pusha ;тело процедуры, в которой изменяется ;содержимое регистров общего назначения ...         popa         ret         endp         

См. также: уроки 7, 10, 14, 15, 16, 17 и команды , , , , , , , ,



POPAD


(POP All general Double word registers from the stack)

Извлечение всех 32-разрядных регистров общего назначения из стека

Схема команды:  popad 

Назначение: извлечение из стека регистров общего назначения edi, esi, ebp, esp, ebx, edx, ecx, eax.


Алгоритм работы:

извлечь из стека последовательно значения и загрузить ими 32-разрядные регистры общего назначения edi, esi, ebp, esp, ebx, edx, ecx, eax. Содержимое edi восстанавливается первым. Содержимое esp извлекается но не восстанавливается;

увеличить значение указателя стека esp на 32.

Состояние флагов после выполнения команды:

выполнение команды не влияет на флаги

Применение:


Команда popad по принципу работы является обратной команде pushad и используется для восстановления всех 32-разрядных регистров общего назначения. Эту команду можно использовать в процедурах и программах обработки прерываний для восстановления регистров общего назначения прерванной программы.

.386 my_proc proc    near         pushad ;тело процедуры, в которой изменяется ;содержимое регистров общего назначения ...         popad         ret         endp         

См. также: уроки 7, 10, 14, 15, 16, 17 и команды , , , , , , , ,



POPF


(POP Flags register from the stack)

Извлечение регистра флагов из стека

 

Схема команды:  popf 

Назначение: извлечение из стека слова и восстановление его в регистр флагов flags.


Алгоритм работы:

извлечь из вершины стека слово и поместить его в регистр flags;

увеличить значение указателя стека esp на 2.

Состояние флагов после выполнения команды:

14 1312 11 10 09 08 07 06 04 02 00
NT IOPL OF DF IF TF SF ZF AF PF CF
r r r r r r r r r r r

Применение:


Команда popf по принципу работы является обратной команде pushf и используется для восстановления из стека содержимого регистра флагов eflags. Возможным вариантом использования этой команды являются программы обработки прерываний или другие случаи, в которых необходимо сохранять некоторый локальный контекст процесса вычисления. Из-за того, что регистр eflags/flags непосредственно недоступен, команда popf является одной из немногих возможностей влияния на его содержимое.

;установить значение регистра flags в 03h         mov     ax,3h         push    ax         popf         

См. также: уроки 7, 10, 14, 15, 16, 17 и команды , , , , , , , ,



POPFD


(POP eFlags Double word register from the stack)

Извлечение расширенного регистра флагов из стека

Схема команды:  popfd 

Назначение: извлечение из стека двойного слова и восстановление его в регистр флагов eflags.


Алгоритм работы:

извлечь из вершины стека двойное слово и поместить его в регистр eflags;

увеличить значение указателя стека esp на 4.

Состояние флагов после выполнения команды:

17 16 14 1312 11 10 09 08 07 06 04 02 00
VM RF NT IOPL OF DF IF TF SF ZF AF PF CF
0 r r r r r r r r r r r r

Применение:


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

.386 ;установить значение регистра eflags в 03h         mov     eax,3h         push    eax         popfd   eax     ;установить новое значение eflags         

См. также: уроки 7, 10, 14, 15, 16, 17 и команды , , , , , , , ,



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


 

название команды с расшифровкой ее мнемонического обозначения — это облегчит процесс запоминания и последующего использования команды в соответствии с ее функциональным назначением;

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

состояние флагов после выполнения команды;

описание типового применения команды с примером и (или) ссылка на урок, в котором демонстрируется пример применения команды;

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



Программная модель микропроцессора


На современном компьютерном рынке наблюдается большое разнообразие различных типов компьютеров. Поэтому возможно предположить возникновение у потребителя вопроса — как оценить возможности конкретного типа (или модели) компьютера и его отличительные особенности от компьютеров других типов (моделей). Рассмотрения для этого одной лишь только структурной схемы компьютера недостаточно, так как она принципиально мало чем различается у разных машин: у всех компьютеров есть оперативная память, процессор, внешние устройства.
Различными являются способы, средства и используемые ресурсы, с помощью которых компьютер функционирует как единый механизм. Чтобы собрать воедино все понятия, характеризующие компьютер с точки зрения его функциональных программно-управляемых свойств, существует специальный термин — архитектура ЭВМ.
Впервые понятие архитектура ЭВМ стало упоминаться с появлением машин 3-го поколения для их сравнительной оценки.

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

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



Псевдооператоры equ и =


К простейшим макросредствам языка ассемблера можно отнести псевдооператоры equ и "=" (равно).
Их мы уже неоднократно использовали при написании программ.
Эти псевдооператоры предназначены для присвоения некоторому выражению символического имени или идентификатора. Впоследствии, когда в ходе трансляции этот идентификатор встретится в теле программы, макроассемблер подставит вместо него соответствующее выражение.
В качестве выражения могут быть использованы константы, имена меток, символические имена и строки в апострофах. После присвоения этим конструкциям символического имени его можно использовать везде, где требуется размещение данной конструкции.

Синтаксис псевдооператора equ:

имя_идентификатора equ строка или числовое_выражение

Синтаксис псевдооператора “=”:

имя_идентификатора = числовое_выражение

Несмотря на внешнее и функциональное сходство псевдооператоры equ и “=” отличаются следующим:

из синтаксического описания видно, что с помощью equ

идентификатору можно ставить в соответствие как числовые выражения, так и текстовые строки, а псевдооператор “=” может использоваться только с числовыми выражениями; идентификаторы, определенные с помощью “=”, можно переопределять в исходном тексте программы, а определенные с использованием equ — нельзя.

Ассемблер всегда пытается вычислить значение строки, воспринимая ее как выражение. Для того чтобы строка воспринималась именно как текстовая, необходимо заключить ее в угловые скобки: <строка>.
Кстати сказать, угловые скобки являются оператором ассемблера, с помощью которого транслятору сообщается, что заключенная в них строка должна трактоваться как текст, даже если в нее входят служебные слова ассемблера или операторы. Хотя в режиме Ideal это не обязательно, так как строка для equ в нем всегда трактуется как текстовая.

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

masm model small stack 256 mas_size equ 10 ;размерность массива akk equ ax ;переименовать регистр mas_elem equ mas[bx][si] ;адресовать элемент массива .data ;описание массива из 10 байт: mas db mas_size dup (0) .code mov akk,@data ;фактически mov ax,@data mov ds,akk ;фактически mov ds,ax ... mov al,mas_elem ;фактически — mov al,mas[bx][si]

<
Псевдооператор “=” удобно использовать для определения простых абсолютных (то есть не зависящих от места загрузки программы в память) математических выражений.
Главное условие то, чтобы транслятор мог вычислить эти выражения во время трансляции.
К примеру:
.data adr1 db 5 dup (0) adr2 dw 0 len = 43 len = len+1 ;можно и так, через предыдущее определение len = adr2-adr1

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

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

Набор этих директив следующий:

директива слияния строк catstr:
идентификатор catstr строка_1,строка_2,... — значением этого макроса будет новая строка, состоящая из сцепленной слева направо последовательности строк строка_1,строка_2,...

В качестве сцепляемых строк могут быть указаны имена ранее определенных макросов.
К примеру:
pre equ Привет, name equ < Юля>

privet catstr pre,name ;privet= “Привет, Юля”



директива выделения подстроки в строке substr:
идентификатор substr строка,номер_позиции,размер — значением данного макроса будет часть заданной строки, начинающаяся с позиции с номером номер_позиции и длиной, указанной в размер.
Если требуется только остаток строки, начиная с некоторой позиции, то достаточно указать только номер_позиции без указания размера.
К примеру:
;продолжение предыдущего фрагмента: privet catstr pre,name ;privet= “Привет, Юля” name substr privet,7,3 ;name=“Юля”



директива определения вхождения одной строки в другую instr:
идентификатор instr номер_нач_позиции,строка_1,строка_2 — после обработки данного макроса транслятором идентификатору

будет присвоено числовое значение, соответствующее номеру (первой) позиции, с которой совпадают строка_1 и строка_2.
Если такого совпадения нет, то идентификатор получит значение 0; директива определения длины строки в текстовом макросе sizestr:
идентификатор sizestr строка — в результате обработки данного макроса значение идентификатор устанавливается равным длине строки.
;как продолжение предыдущего фрагмента: privet catstr pre,name ;privet= “Привет, Юля” len sizestr privet ;len=10



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