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

Textnew2

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

GLOBAL _start EXTERN write, exit, printf

SEGMENT .text _start:

;--- write(1, txt, lentxt) == <4>(ebx, ecx, edx) push dword lentxt

push dword txt push dword 1 call write

add esp, 12

; call printf(formt, x, y) push dword [y] push dword [x] push dword formt call printf

add esp,12 push dword 1 call exit

 

SEGMENT .data

txt

 

db 'Privet!',27,'[10;40H',27,'[1;31;44mOu-key!'

 

db 27,'[0m',10

lentxt

equ $-txt

x

dd 3456

y

dd -78881

formt

db 'X=%d, Y=%d',10,0

Рис. 5.6.3. Пример использования системных функций через библиотеку libc

В этой программе использованы системные функции write и exit ОС Linux, а также очень удобная функция форматированного вывода printf, хорошо известная всем программистам на языке Си.

Еще раз подчеркнем, что для компоновки с помощью gcc, завершающей построение исполняемого файла (вместо компоновки с помощью комповщика ld), в этой программе строки

GLOBAL _start

и

_start:

должны быть заменены, соответственно, на строки GLOBAL main

и

101

main:

С помощью стандартного вывода в рассматриваемой программе на экран выводятся фрагменты текста "Privet!" и "Ou_key!", которые сопровождаются управляющими последовательностями в самом тексте, который представляется на вывод функцией write. Управляющие последовательности позволяют позиционировать курсор (переустанавливать его в любое место экрана и тем самым направлять вывод в любое место экрана), а также управлять цветовыми атрибутами вывода. Подробней о управляющих последовательностях смотрите соответствующую литературу, например [15]. (Заметим, что управляющие последовательности доступны, конечно, не только через стандартные библиотеки, но и выводом через прерывание INT 80h, используемое в Linux на более низком уровне.)

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

Пример листинга на рис. 5.6.3 предназначен для получения выполняемой программы, которая будет выполняться в 32-битной версии Linux.

Для 63-битной архитектуры этот исходный код должен быть изменен так, чтобы соответствовать правилам вызова подпрограмм в архитектуре x86-64. На рис. 5.6.4 приведен такой текст исходного файла программы.

GLOBAL _start EXTERN write, exit, printf

SEGMENT .text _start:

;--- write(1, txt, lentxt) == <4>(ebx, ecx, edx)

mov rdx, dword lentxt

mov rsi, dword txt

mov rdi, dword 1

call write

; call printf(formt, x, y)

mov rdi, formt

102

mov rsi, [x]

mov rdx, [y]

mov rax, 0

call printf

mov rdi, 1

call exit

SEGMENT .data

txt

 

db 'Privet!',27,'[10;40H',27,'[1;31;44mOu-key!'

 

db 27,'[0m',10

lentxt

equ $-txt

x

dd 3456

y

dd -78881

formt

db 'X=%d, Y=%d',10,0

Рис. 5.6.4. Использования вызовов в архитектуру x86-64

Упражнения.

1.Построить библиотеку объектных модулей Linux, содержащих доступные извне процедуры с именами atoi, itoa. Процедура atoi должна выполнять преобразование последовательности цифр, заданной адресом, который содержит регистр eax, в двоичный код, выдаваемой ее также через регистр eax. Процедура itoa должна выводить в стандартный вывод последовательность десятичных цифр, представляющих исходное значение аргумента в регистре eax, заданное им в двоичном коде. Кроме того, библиотека должна содержать процедуру lfwrite, которая переводит вывод на экране на следующую строку и рассмотренные выше процедуры stdread, stdwrite, stdexit, собранные в один объектный файл. Дополнительно построить программу, которая использует все эти процедуры и при разработке которой используется данная библиотека объектных модулей.

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

3.Построить библиотеку объектных модулей, описанных в первом упражнении, для ОС Windows и разработать тестовый пример проверки функционирования этих модулей.

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

A(0,M)=M+1; A(N,0)=A(N-1,1); A(N,M)=A(N-1, A(N,M-1))

103

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

6. ВСПОМОГАТЕЛЬНЫЕ СРЕДСТВА БАЗОВОЙ АРХИТЕКТУРЫ

6.1. Использование строковых команд пересылки

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

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

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

MOV ESI, oblast1 MOV EDI, oblast2

MOV ECX, число_байтов povt: MOV AL, [ESI]

MOV [ESI], AL INC ESI

INC EDI LOOP povt

Рис. 6.1.1. Простейшее решение по пересылке данных из одной области в другую

104

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

Заметим, что для такой, простейшей в своей цели задачи, рассмотренное решение требует целых девяти команд, причем шесть из низ многократно выполняются внутри цикла. Более тщательный анализ показывает, что для пересылки одного очередного байта при этом требуется выполнение шести команд, образуемые десятью байтами командного кода. С учетом того, что байты командного кода процессор, в свою очередь извлекает из памяти (хотя в современных компьютерах и из наиболее быстрой памяти внутреннего кэша), то получается, что на пересылку одного байта данных процессор тратит время, обусловленное в том числе и извлечением (пересылкой из памяти в процессор) десяти байтов. Тем самым, пересылается в пять раз (!!!) больше байтов, чем кажется действительно необходимым. Для сравнения заметим, что в более ранней архитектуре компьютеров IBM360 (больших компьютеров) была команда

MVC куда(сколько), откуда

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

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

Особенностью строковых команд является то, что они как бы разбивают универсальное действие упомянутой выше команды MVC на подготовительные вспомогательные команды и некоторую специфическую команду, которая и осуществляет многократно желаемое действие. Подготовительные действия заключаются в занесении адресов области исходных данных и размещения результатов в специализированные регистры ESI, EDI (или в регистры SI, DI для 16-битного варианта), а также занесение числа пересылаемых байтов в регистр ECX (в регистр CX для 16-бит- ного варианта). Именно эти три команды требовались и в простейшем техническом решении, приведенном на рис. 6.1.1, так что никакого "откровения" здесь не видно.

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

105

рый называется флажком направления - DF (Direction Flag). Для изменения указанного флага служат две команды: CLD и STD, первая из них сбрасывает значение флага в нуль, а вторая устанавливает его в единицу. Значение флага DF, равное нулю, отвечает пересылке в направлении от младших адресов к старшим (в прямом направлении).

Собственно строковая команда пересылки обозначается на ассемблере мнемокодом MOVSx, где символ x задает конкретную модификацию команды. Во внутренней части цикла, организуемом с помощью строковой команды пересылки MOVSx, записывается всего лишь эта специальная команда и предшествующий ей специальный префикс повторения REP. Этот префикс при трансляции в машинный двоичный код преобразуется в специальный байт префикса. Поэтому программа, равносильна раннее приведенной на рис. 61.1, будет иметь вид, представленный на рис. 6.1.2.

MOV ESI, oblast1

MOV EDI, oblast2

MOV ECX, число_байтов

CLD

REP MOVSB

Рис. 6.1.2. Пересылка данных с помощью строковой команды

Еще раз отметим, что в этом решении первые четыре команды выполняются однократно, а многократному выполнения подлежит только одна командная строка (из двух байтов). Команды MOVSx имеют три модификации, задаваемые на ассемблере последним символом мнемокода, который обозначает размер единицы пересылаемого кода. Для последнего возможны значения B, W и D, что соответствует обозначениям BYTE, WORD и DWORD, задавая тем самым единицу пересылки в один, два и четыре байта.

Действия команды MOVSx заключается в том, что элемент данных (размер которого задан метасимволом x) пересылается из места, заданного адресом в регистре ESI, в место, заданное адресом в регистре EDI. (Таким образом, как бы берется значение операнда [ESI] и пересылается по месту операнда [EDI]). Затем, в ходе выполнения той же команды изменяются значения регистров ESI и EDI, оба они при DF=0 увеличиваются, а при DF=1 уменьшаются на число, определяемое модификатором x (на один для x=B, на два для x=W и на четыре для x=D).

Действие префикса повторения, управляющего повторением команды MOVSx, заключается в проверке текущего значения регистра ECX на нуль, в принятии решения о повторении или прекращении выполнения строковой команды и в уменьшении на единицу значения этого регистра, если принято решение о повторении строковой команды. Прекращается выполнение команды MOVSx только в том случае, когда значение регистра ECX равно нулю.

106

Получается, что если в составе командной строки использован префикс повторения REP и только что полученное значение регистра ECX не равно нулю, то действия команды MOVSx повторяются. Эти повторения происходят до тех пор пока содержимое регистра ECX не станет равным нулю. (Для 16-битного варианта во всех указанных в описании регистрах используются только младшая половина.)

При использовании команды MOVSW в регистр ECX должно заноситься число пересылаемых слов (пар байтов), а при использовании команды MOVSD в регистр ECX следует заносить число пересылаемых двойных слов (четверок байтов).

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

Рис. 6.1.3. Пересылка между частично перекрывающимися областями

В этом примере результирующая область данных oblst1 начинается на два байта дальше области данных oblst2, используемой в качестве исходных. Для этого случае пересылка первого байта a из области oblst2 на место первого байта области oblst1 изменит третий байт c исходной области (а пересылка второго байта b области oblst2 изменит четвертый байт исходной области). В результате (как ни странно!) результирующая область oblst1 будет заполнена чередующимися значениями байтов a и b, но никакими другими. Заметим, что этот "трюк" может быть использован именно для быстрого заполнения повторяющимися значениями, если соответствующим образом подобрать взаимное размещение частично пересекающихся областей.

Общее решения пересылки между областями данных с указанным расположением заключается в пересылке вначале самых последних байтов, затем предпоследних и т.д., что, как нетрудно видеть, обеспечивает правильное решение. Для такого решения вместо команды CLD необходимо использовать команду STD, а в регистры ESI и EDI занести адреса не начала соответствующих областей данных, а адреса их последних элементов. В самом общем случае требуется предварительно проверять взаимное расположение областей данных, между которыми пересылаются данные, и выбор в зависимости от этого одного из возможных вариантов направления (если области взаимно пересекаются и область назначения начинается в памяти раньше области исходных данных, то неверный результат дает пересылка, начиная со старших адресов).

Для 16-битной архитектуры существенным оказывается еще одна особенность строковых команд. Она обусловлена использованием (по умолчанию) различных сегментов памяти для неявных операндов строковых команд, основанных на регистрах SI и DI. (Для них используются специальные сегментные регистры DS и ES,

107

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

Рассмотренная строковая команда MOVSx позволяет написать высокоэффективную процедуру для пересылки одной строки данных в область, заданную другой строкой. Эта процедура по своему смыслу равносильна системной функции strncpy системной библиотеки языка С.

Такая процедура может быть описана программой, приведенной на рис. 6.1.4

;procedure strncpy(target* edi, source* esi, maxlen ecx) strncpy: push edi

push esi push ecx cld

rep movsb pop ecx pop esi pop edi ret

Рис. 6.1.4. Эффективная процедура strncpy копирования строк

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

Для сравнения на рис. 6.1.5 приведена менее эффективная процедура пересылки строк, не использующая строковых команд.

;procedure astrcpy(target* edi, source* esi, maxlen ecx) astrncpy: push eax

push edi push esi push ecx

met2: mov al, [esi] mov [edi], al inc esi

inc edi

loop met2,ecx pop ecx

pop esi pop edi pop eax

108

ret

;end procedure astrcpy

Рис. 6.1.5. Неэффективная процедура astrncpy копирования строк

Кроме весьма общих команд пересылки MOVSx, в составе архитектуры Intel имеются еще менее значимые строковые команды STOSx и LODSx, где последний символ - модификатор x имеет уже рассмотренный смысл и конкретные обозначения, задавая размер элемента, с которым оперируют строковые команды.

Команды STOSB, STOSW и STOSD (в применении вместе с предшествующим им префиксом повторения REP) обеспечивают многократное "расписывание" содержимого, соответственно, регистра AL, AX или EAX по последовательности соседних ячеек памяти. Число заполняемых таким образом одно-, двухили четырехбайтовых элементов, задается предварительно в регистре ECX, а адрес области памяти, куда нужно записывать содержимое указанных регистров, должно быть предварительно занесено в регистр EDI. Теоретически может быть использована запись от старших адресов к младшим, хотя содержательных причин такого варианта не просматривается. Следует обратить внимание на обязательное задание для направления пересылки через флаг DF регистра флагов EFLAGS (практически с помощью команды CLD) - рассчитывать, что этот флаг сброшен по умолчанию совершенно не предусмотрительно и может вызвать очень неочевидную ошибку. Практически команды STOSx могут использоваться и используются для быстрого обнуления больших массивов оперативной памяти.

Еще менее значимые команды LODSx выполняют занесение в регистр AL, AX или EAX, соответствующий их модификатору, значения из элемента памяти по адресу, который задает регистр ESI. Причем тут же автоматически значение регистра ESI изменяется, так что далее указывает на следующий элемент в направлении, которое определяется флагом DF. (Величина изменения - на единицу, два или четыре - определяется модификатором x команды.) Применять эту команду с префиксом повторения не имеет никакого смысла, практически она может служит только для замены пары команд

MOV AL, [ESI] INC ESI

или аналогичных, где вместо регистра AL задаются регистры AX, EAX, а вместо команды инкремента (или декремента для DF=1) используется команды ADD ESI, 2 или ADD ESI, 4 (либо для DF=1 соответствующие команды вычитания). Такая замена несколько ускоряет требуемое суммарное действие и занимаем меньше места в машинной памяти, хотя экономия от этого и незначительна. (Заметим, что для 16битного варианта все указанные выше регистры должны быть использованы без расширяющей их разрядности буквы E.)

6.2. Применение строковых команд сравнения

109

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

CLC операнд1(сколько), операнд2

которая была предназначена для сравнения произвольных последовательностей байтов (длиной не более 255).

Вархитектуре Intel для аналогичных целей содержится строковая команда с обобщенной мнемоникой CMPSx, где последний метасимвол x должен представлен одной из букв B, W или D, согласно типу размера элементов строкового массива (соответствуя обозначениям BYTE, WORD и DWORD). Данная команда также предназначена лишь для центральной части фрагмента программного кода, который в совокупности реализует сравнение строковых массивов. Подготовительными командами в этом фрагменте должны задаваться необходимые значения для регистров ESI, EDI и ECX. Регистр ESI перед первым выполнением строковой команды сравнения CMPSx должен содержать адрес первого элемента операнда операнд1, а регистр EDI - первого из сравниваемых элементов операнда операнд1. При этом, аналогично строковым командам пересылки, должен быть соответствующим образом установлен флаг направления DF в регистре флагов. Если требуется сравнение последовательности элементов от младших адресов к старшим, на этот флаг устанавливается командой CLD, а для противоположного направления сравнения - командой STD. Следует обратить внимание, что при сравнении от старших адресов

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

операнд1 и операнд2.

Для сравнения последовательности элементов строк команда CMPSx должна использоваться с префиксом повторения (Без префикса повторения эта команда выполнится всего лишь с одним элементом из заданных строк, хотя содержимое регистров ESI и EDI при этом изменится, чтобы показывать на следующий элемент каждой из строк.) Префикс повторения для команд сравнения может задаваться в одном из двух вариантов, которые обозначаются на ассемблере мнемокодами REPE и REPNE. Префикс REPE используется, если сравнение следующих очередных элементов строк следует производить только после обнаружения равенства элементов в текущем шаге сравнения. Префикс REPNE предназначен для повторения действий команды строкового сравнения при обнаружении не сравнения элементов на текущем шаге.

Таким образом, если строки сравниваются на предмет нахождения первого различного в них элемента, то необходимо применить префикс REPE непосредственно перед командой CMPSx. Если же строки сравниваются на предмет нахождения первого совпадающего по значению элемента, то следует использовать префикс REPNE. Следует заметить, что синонимами префиксов REPE и REPNE служат, соответственно, префиксы REPZ и REPNZ.

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

110

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