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

Textnew2

.pdf
Скачиваний:
13
Добавлен:
06.02.2018
Размер:
1.48 Mб
Скачать

GLOBAL _start SEGMENT .text

_start:

;--- read(1, buf, 80 == <3>(ebx, ecx, edx) mov eax,3 ; N function=read

mov ebx,0 ; 0 handle=0 (stdin) mov ecx, buf ; address of buf mov edx,80 ; number of byte int 80h

mov [len],eax

mov byte [buf+1],'!'

;--- write(1, buf, [len]) == <4>(ebx, ecx, edx) mov eax,4h ; N function=write

mov ebx,1 ; N handle=1 (stdout) mov ecx, buf ; address of buf mov edx,[len] ; number of byte int 80h

mov eax,1

int 80h ; function=exit SEGMENT .data

buf times 80 db 0 ; или resb 80; но тогда будет предупреждение lendd 0

Рис. 2.5.1. Простейшее использование прямой адресации

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

Для доступа ко второму байту (считая, что начальный байт области называется первым), используется также прямой способ адресации, записываемый в операндах в виде

[buf+1]

Если ли мы запишем в операнде [buf+0], то попадем на самое начало области, что равносильно записывается как [buf], для обозначения 10-го байта в этой области достаточно записать операнд в виде [buf+9]. Заметим, что в профессиональном программирования целесообразно нумеровать элементы массива и, в частности, байты области данных, начиная с нуля, так что n-й байт области данных с именем obl обозначится в операнде команды как [obl+n], где элемент n должен быть запи-

31

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

nequ число

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

MOV [xxx], -127

не может быть компилятором ассемблера NASM преобразован в машинный код, даже при правильно определенной области данных xxx (например, с помощью директивы xxx DD 0), потому что непонятно, сколько байтов двоичного кода требуется поместить в качестве значения константы -127: 8 битов, 16 битов или все 32 бита. (Если человеческие записи чисел не используют нулевых цифр перед первой значащей цифрой, то в машинных структурах совсем наоборот, более того, машинные способы записи отрицательных чисел в первом случае требуют запомнить шестнадцатеричный код 7F, во втором - FF7F, а в третьем - FFFFFF7F.) Для решения проблемы однозначной определенности в ассемблеры введены дополнительные средства. В ассемблере NASM они представляют собой модификаторы разрядности, задаваемые служебными словами BYTE, WORD и DWORD. Эти модификатора ставятся перед теми операндами, разрядность которых необходимо уточнить.

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

MOV BYTE [xxx], -127 MOV WORD [xxx], -127 MOV DWORD [xxx], -127

Вассемблерах MASM и TASM принята другая методология использования имен данных. Согласно возвышенным принципам, заложенным в эти ассемблеры, именам данных соотносятся не только значения, задаваемые смещением начала именованных данных относительно начала сегмента, но и атрибуты. К этим атрибутам относят имя сегмента, в котором данные определены, и характеристику размера. Эти характеристики обозначаются служебными словами BYTE, WORD и DWORD (и рядом других). Поэтому в этих ассемблерах запись команды в виде

MOV [xxx], -127

интерпретируется по разному в зависимости от определения имени xxx. На самом деле это далеко не идеальное решение, так как, во-первых значение имени может быть задано директивой EQU и тогда вступает далеко не явное допущение, во-вто- рых, иногда возникает необходимость в область данных, определенную одним размером, частично заполнить данными меньшего размера (например, младшую и старшую половину отдельными командами). Если имя области данных на самом деле именует область неоднородной - с точки зрения программиста - структуры, то использование обозначения вида [имя_области + число] естественно для доступа к части фактической структуры, но автоматически приобретает атрибут начального поля в такой структуре.

32

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

С учетом отмеченной необходимости и в MASM, TASM явно задавать размер операндов там, где это необходимо или при желании отказаться от решения по умолчанию, в эти ассемблеры также введены средства явного задания размера операндов. К сожалению, высокие соображения привели к более сложным конструкциям. Для задания размеров в 8, 16 и 32 бита предназначены обозначения BYTE PTR, WORD PTR и DWORD PTR, так что на этих ассемблерах описанные выше примеры запишутся как

MOV BYTE PTR [xxx], -127 MOV WORD PTR [xxx], -127 MOV DWORD PTR [xxx], -127

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

Упражнения.

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

2.Разработайте программу для MS-DOS, которая будет выводить три различных текста небольшой длины, помещая их на отдельных строках экрана и используя для вывода каждого из текстов отдельный вызов программного прерывания.

3.Разработать программу для MS-DOS, которая выводит четыре строки текста, причем первая и четвертые строки завершаются управляющими символами 13,10, вторая - только управляющим символом 13, а вторая строка завершается только управляющим символом 10. Объяснить результаты работы программы.

3.АРХИТЕКТУРНЫЕ ЭЛЕМЕНТЫ ДЛЯ ПОСТРОЕНИЯ ПРОГРАММ

3.1.Организация условных переходов

33

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

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

Например, для вычисления модуля разности двух переменных a и b (описанных

всегменте данных как двойные слова, т.е. переменные типа long или int для 32-бит- ной архитектуры) с результатом в регистре eax, можно использовать следующую последовательность команд

MOV eax, [a] SUB eax, [b] JGE meta NEG eax

meta:

Здесь мы встретились с двумя новыми командами: командой вычитания SUB и командой NEG изменения знака числа на противоположный. Первая из них чрезвычайно похожа на команду ADD, но выполняет не сложение, а вычитание. Мнемоника второй восходит к английскому слову negative, а сама команда имеет только один операнд, который перед выполнением содержит исходное значение для выполнения операции, а после выполнения команды этот операнд содержит уже значение с противоположным знаком (реализуется оператор вида N = -N из языка Си). В последней команде в качестве операнда поэтому оказывается возможным использовать только регистровую адресацию или задавать с помощью адресации место в памяти, где собственно и находится преобразуемое значение.

Команды условного перехода в архитектуре Intel имеют одну "хитрую" особенность, унаследованную от первых очень примитивных микропроцессоров. Она состоит в том, что простейшая форма записи команд условного перехода порождает машинные команды, неспособные осуществлять переход далее чем на 127 байтов вперед и далее чем на 128 байтов назад! (Связано это с очень экономным кодированием относительного места перехода, размещаемого всего в одном байте.) Как следствие этого, попытки указать условный переход далее указанного диапазона вызывают ошибку со следующим текстом сообщения о ней: "short jump is out of range". В таких случаях программисту необходимо явно указать использование другого варианта построения команды условного перехода. Это указание заключается

вдобавлении модификатора NEAR перед меткой, задаваемой в качестве операнда. В общем случае это приводит к записи вида

Jcc NEAR метка

36

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

IF условие THEN оператор

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

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

Специализированной командой для вычисления условия является команда CMP операнд1, операнд2

название которой восходит к слову compare и которая сравнивает значения операндов операнд1 и операнд2. Результат такого сравнения автоматически фиксируется в специальном регистре, обозначаемом в рассматриваемой архитектуре как EFLAGS (регистр флагов). Это 32-битный регистр, но основное значение имеет его младшая 16-битная половина, обозначаемая просто FLAGS. В этом регистре отдельными битами отображается ситуация равенства нулю результата, его отрицательности, появления переполнения при выполнении некоторых операций (биты ZF, NF, CF) и т.п. Для разветвления действия программы по значениям отдельных установленных бит в этом регистре имеются специальные команды, но они используются достаточно редко, чтобы начинать с их изучения.

Общей мнемоникой команды разветвления является Jcc метка

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

Таблица 3.1. Команды условных переходов.

 

Мнемокод Условие

Математическое

Примечание

 

перехода

отношение

 

JE

равно

=

знаковое

JNE

не равно

?

знаковое

34

JL

меньше

<

знаковое

JG

больше

>

знаковое

JLE

меньше или равно

<=

знаковое

JGE

больше или равно

>=

знаковое

JA

выше (above)

>

беззнаковое

JB

ниже (below)

<

беззнаковое

JC

по флагу переноса

 

 

JNC

если CF не установлен

 

Команды условного перехода для знакового сравнения соответствуют использованию предварительно сформированного условия на основе значений модификатора типа signed, а беззнакового - на основе модификатора unsigned. Сами мнемоники построены, исходя из начальных букв английских слов equal (равно), less (меньше), greater (больше), not (нет). Кроме перечисленных в таблице мнемоник имеются еще JNL, JNG, JNA, JNB, JAE, JBE, отвечающие, соответственно, условиям не меньше, не больше, не выше, не ниже, выше или равно, ниже или равно. Причем мнемоники в парах JGE и JNL, JLE и JNG, JAE и JNB, JBE и JNA обозначают одинаковые по действиям команды.

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

Для знакомых с программированием на языке Бейсик, действия команд условного перехода можно представлять как реализацию оператора Бейсика

IF условие THEN GOTO метка

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

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

Сделаем пояснения по использованию беззнаковых команд условного перехода. Они предназначены, в частности, для сравнения адресов на предмет, который из них находится в памяти раньше или позже. Дело в том, что если старший бит второго из адресов будет иметь единичное значение, а первого адреса - нулевое значение, то знаковые команды сравнения рассматривают такую ситуацию как сравнение двух чисел, из которых первое положительно, а второе отрицательно. В результате такого непродуманного сравнения получится, что более дальний адрес будет истолкована как меньший (отрицательный адрес!). Аналогичная ситуация возникает, когда сравниваются порядковые номера символов, рассматриваемых как одно-

35

(командой INT 80H), за которой записаны команды обращения к системной функции exit завершения выполнения программы.

Если длина введенного текста достаточна для решения поставленной задачи, то рассмотренного выше перехода командой JL wrno не происходит, а выполняются помещенные за ней команды

mov al, [buf+4] cmp al, [buf+5]

которые переносят в регистр al содержимое пятого символа текста (с математической системе отсчета индексов от единичного), а затем сравнивая содержимое этого регистра с шестым символом введенного текста.

При несовпадении сравненных значений следующая команда jne wrne выполняет переход на метку wrne, а при совпадении перехода не происходит и выполняются последующие команды

mov ecx, mese mov edx, lenmese

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

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

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

mes db 'Текст' lenmes equ $-mes

директива EQU вычисляет значение выражения, которое задает разность между относительным адресом текущей строки (а это относительный адрес того места, где закончился текст из предыдущей строки) и относительным адресом, который соответствует в памяти имени mes, которое обозначает отметку начала текста. Тем самым результат дает сколько байтов занимает данный текст.

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

Следует запомнить, что правильный результат для длины текста, формируемой рассмотренным образом (в символической константе lenmes нашего примера), получается только, если указанная директива EQU размещается непосредственно за директивой определения текста и в ее выражении безошибочно используется имя

40

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

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

. . . формирование условия

JL metk1 JG metka JE metk2

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

. . . формирование условия

JL metk1 JG metka

;сюда попадаем, при выполнении условия равенства

;и метку, например metk2, ставить совершенно излишне

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

Кроме команд условного перехода в архитектуре имеется (совершенно необходимая) команда безусловного перехода, изображаемая мнемокодом JMP. Она при записи на ассемблере имеет вид

JMP метка

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

IF условие THEN оператор1 ELSE оператор2.

Разворачивание такого условного оператора в последовательность команд имеет общую структуру вида

. . . формирование условия

37

Jncc метка_оп2

. . . команды оператора1 JMP метка_за метка_оп2:

. . . команды оператора2 метка_за:

где условное обозначение ncc мнемоники условного перехода обозначает условие, противоположное для исходного условия условие в операторе высокого уровня. Например, если исходное условие есть равно, то используется переход по мнемонике JNE, если исходное условие есть "не больше", то используется мнемоника JG и т.п. Основная цель такого приема в том, чтобы при выполнение исходного условия команда Jncc метка_оп2 не осуществляла переход на метку, с которой начинается выполнение оператора оператор2, а выполнялись следующие по записи команды, которые реализуют оператор оператор1. В конце же последовательности команд, которые реализуют оператор оператор1, должен выполняться "скачок", чтобы не попасть на выполнение команд оператора оператор2, а продолжить выполнение программы далее за пределами рассматриваемой условной конструкции.

Далее на рис. 3.1.1 приведена более содержательная программа для Linux, которая демонстрирует простое использование различных переходов для решение практических задач. Эта программа осуществляет ввод строки текста и сравнение в нем пятого и шестого символа. По результатам сравнения выдается одно из двух сообщений - равны символы или нет. Причем при вводе строки меньшей, чем из шести символов, выдается сообщение о невозможности выполнения задачи.

GLOBAL _start SEGMENT .text

_start:

;--- read(1, buf, 80) == <3>(ebx, ecx, edx) mov eax,3 ; N function=read

mov ebx,0 ; 0 handle=0 (stdin) mov ecx, buf ; address of buf mov edx,80 ; number of byte int 80h

cmp eax, 7 jl wrno

mov al, [buf+4] cmp al, [buf+5] jne wrne

mov ecx, mese mov edx, lenmese

;--- write(1, mese, lenmese) == <4>(ebx, ecx, edx)

38

write: mov eax,4 ; N function=write mov ebx,1 ; N handle=1 (stdout) int 80h

mov eax,1

int 80h ; function=exit Linux

wrne: mov ecx, mesne mov edx, lenmesne jmp write

wrno: mov ecx, mesno mov edx, lenmesno jmp write

SEGMENT .data

buf times 80 db 0 ; или resb 80; но тогда будет предупреждение mesno db 'Длина текста меньше 6'

lenmesno equ $-mesno

mese db 'Пятый и шестой символы совпадают' lenmese equ $-mese

mesnedb 'Пятый символ отличается от шестого'; lenmesne equ $-mesne

Рис. 3.1.1. Ввод текста и сравнение в нем 5-го и 6-го символов

Напомним, что число символов, введенных системной функцией write, возвращается в регистре eax, (причем выдается число символов, включая завершающий символ перевода строки) поэтому после завершения ввода текста содержимое этого регистра сравнивается с числом 7 посредством команды

cmp eax, 7

за которой следует команда условного перехода JL wrno. В месте программы, отмеченной меткой wrno, выполняется вывод сообщения о недостаточной длине введенного текста. Для чего подготавливается содержимое регистров ecx и edx, в которые помещается адрес текста сообщения и длина этого сообщения, соответственно. После этой подготовки командой JMP write задается принудительный переход на метку write. Начиная со строки, отмеченной меткой write, продолжается подготовка к вызову системной функции write. С этой целью необходимые значения заносятся в регистры eax и ebx (значение 4 - номер системной функции и значение 1 - хэндла стандартного вывода). Далее следует собственно обращение к системной функции

39

Соседние файлы в предмете Операционные системы