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

Textnew2

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

но такое наименование только в ассемблерах TASM, MASM (и специализированном ассемблере UNIX, называемом as). В используемом нами ассемблере NASM мнемоникой этой команда является XLATB. Трудно обнаруживаемая же ошибка появляется при попытке использовать в этом ассемблере обозначение XLAT. Дело в том, что данный ассемблер позволяет применять метки, не заканчивающиеся двоеточием, поэтому использование в отдельной строке программы последовательности латинских букв, не совпадающей ни с одной из мнемоник команд и допустимых директив, воспринимается последним как введение новой метки, на которую программист почему-то потом не ссылается (последнее никогда не рассматривается как ошибка).

На рис. 6.3.1 приведена демонстрационная программа для Linux, которая определяет принадлежность введенного символа к классу латинских букв, к классу русских букв или к классу символов арифметических операций. А также, "по ходу дела" определяется не является ли введенный символ цифрой. Для указанных классов символов использованы коды, задаваемые, соответственно, значениями 1, 2, 3 и 4 (не цифрами, а числовыми значениями байта!). Основу преобразования составляет таблица, названная в программе tabxlat.

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

;русской ли буквой или арифметическим символом

;пока не введен символ '!' ; управляющие символы '\r' и '\n' не анализируются GLOBAL _start

SEGMENT .text

_start:

read: mov eax,3 ; N function=read mov ebx,1 ; 0 handle=1 (stdin) mov ecx, cha ; address of buf mov edx,1 ; number of byte int 80h

cmp eax, 0 ; или test eax,eax

je read ; при вводе пустой строки повторить попытку ввода cmp byte [cha],'!'

je near kon

cmp byte [cha],10 ; '\n' je read

cmp byte [cha],13 ; '\r' je read

mov al, [cha] mov ebx, tabxlat xlatb

cmp al, 1 je wrlat

121

cmp al, 2 je wrrus cmp al, 3 je wrznk cmp al, 4 je wrzif

mov ecx, mesno mov edx, lenmesno

;--- write(1, mese, lenmese) == <4>(ebx, ecx, edx) write: mov eax,4 ; N function=write

mov ebx,1 ; N handle=1 (stdout) int 80h

jmp read

wrlat: mov ecx, meslat mov edx, lenmeslat jmp write

wrrus:

 

mov ecx, mesrus

 

mov edx, lenmesrus

 

jmp write

wrznk:

mov ecx, mesznk

 

mov edx, lenmesznk

 

jmp write

wrzif: mov ecx, meszif

 

mov edx, lenmeszif

 

jmp write

kon:

mov eax,1

 

int 80h ; function=exit for COM-file

 

SEGMENT .data

cha

db 0

 

mesno

db 13,10,'Вид не определен',13

lenmesno equ $-mesno

meslat

db 'Латинская буква',10

lenmeslat equ $-meslat

mesrus

db 'Русская буква',10

lenmesrus equ $-mesrus

mesznk

db 'Знак арифметической операции',10

lenmesznk equ $-mesznk

meszif

db 'Десятичная цифра',10

122

lenmeszif equ $-meszif

tabxlat db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0; 0 - 15 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0; 16 - 31

db 0,0,0,0,0,0,0,0,0,0,3,3,0,3,0,3; 32 - 47 (...*+.-./) db 4,4,4,4,4,4,4,4,4,4,0,0,0,0,0,0; 48 - 63 (012...9...) db 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1; 64 - 79 (.ABC..O) db 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0; 80 - 95 (P - Z ...) db 0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1; 96 - 111 (..ab - o) db 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0; 112 - 127 (p - z ...)

db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0;

128

- 143

db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0;

144

- 159

db 0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0;

160

- 175 (...г...)

db 0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0;

176

- 191 (...¦...)

db 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2;

192

- 207

db 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2;

208

- 223

db 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2;

224

- 239

db 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2;

240

- 255

Рис. 6.3.1. Программа определения лексического класса символа для Linux

В этой таблице на месте, где в исходной таблице кодирования должны быть символы * и +, записаны значения 3 (код класса арифметических операций). Те же самые значения записаны на месте символов - и /. В позициях, где в исходной таблице помещаются латинские буквы, записаны значения 1 (начиная с 65-го элемента таблицы по 89-й и с 98-го по 122). На месте цифр исходной таблицы записаны числа 4 (код класса цифр), а все места исходного размещения русских букв заполнены значениями 2. Символы, не относящиеся ни к одному из рассматриваемых классов, отмечены в таблице нулевым значением. Подчеркнем, что данная таблица отражает конкретное кодирование, выбранное текущей локализацией в операционной системе для консольного режима, и может заметно отличаться для различных систем кодирования. Следует, впрочем, заметить, что первая половина таблицы кодирования (так называемый код ASCII-7) оказывается для большинства современных систем кодирования одинаковой. В общем случае это оказывается неверным и какие-то системы компьютеров могут использовать иные кодировки.

Основную работу в программе выполняет команда XLATB. Прочитанный с стандартного ввода символ, если он отличен от управляющий символов '\n' и '\r' (которые отбрасываются, чтобы упростить поведение программы при ее запуске с вводом от клавиатуры), переносится в регистр AL. Обращение к команде XLATB, выполнение которой базируется на таблице tabxlat, выдает в регистре AL номер класса анализируемого символа. Дальнейшие действия осуществляются командами вида CMP AL,номер_класса, после которых применены команды условных переходов. Последние обеспечивают переходы на выдачу соответствующих сообщений.

123

6.4.Переключатели на основе косвенной адресации

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

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

Ввышеприведенном примере на рис. 6.3.1 по результатам анализа номера класса осуществлялись переходы на метки wrlat, wrrus, wrznk, wrzif, причем переход на эти метки осуществлялись, соответственно, при обнаружении номеров классов 1, 2, 3 и 4.

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

tabcase dd wrno,wrlat,wrrus,wrznk,wrzif

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

movzx eax,al

shl eax,2 ; in AX - offset for element of TABCASE mov ebx,tabcase

add ebx,eax ; bx - address to jump jmp dword [ebx]

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

jmp dword [ebx]

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

124

при 32-битной адресации) метке в машинном представлении соответствует четыре байта адреса. Именно поэтому таблица выбора tabcase была описана как состоящая из 4-байтовых элементов, определяемых директивой DD. Адресу метки wrno в этой таблице соответствует смещение 0, адресу метки wrlat - смещение 4, адресу метки wrrus - смещение 8, адресу метки wrznf - смещение 12, а адресу метки wrzif - смещение 16. Нетрудно видеть, что для перехода от номер класса символа к смещению метки соответствующего перехода необходимо всего лишь умножить значение класса символа на четыре. Последнее вычисление проще всего выполнить с помощью сдвига двоичного кода на два разряда влево. (Проще всего - с точки зрения числа машинных команд и мысленных усилий квалифицированного программиста.)

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

mov ebx, имя_таблицы_из_меток_переходов add ebx, eax

jmp dword [ebx]

(Заметим, что при необходимости вместо регистров EAX, EBX можно использовать другие 32-битные регистры.)

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

6.5. Программирование на стандартном ассемблере AT&T

Программирование для операционной системы Linux, как и для всех ОС семейства Unix, производится в подавляющем большинстве случаев на универсальном ассемблере AT&T. Ассемблер этот был разработан в той же фирме, где была создана ОС Unix, и для целей поддержки программирования на нижнем уровне для практически любого процессора. Этот ассемблер создавался как многоплатформенный, но, конечно, для каждого типа процессора должна быть использована модификация такого ассемблера, ориентированная на конкретную систему команд и организацию памяти.

125

Для выполнения компиляции рассматриваемым ассемблером служит исполняемый файл as, вызываемый для обработки исходного файла prim в командной строки в виде

as prim

Исходный файл ассемблера для Unix традиционно имеет односимвольное расширение s, так что типичным именование исходного файла будет prim.s и т.п. При вызове для компиляции исходного ассемблерного файла с типовым расширением последнее можно опускать. По стандартным допущениям в Unix результирующий объектный файл получается с постоянным стандартным именем a.out. В большинстве случаев для современных программистов это кажется не очень удобным, поэтому целесообразно задать типовое расширение объектного файла, изображаемое в Unix одним символом o, и используя для этого опцию -o. Для получения листинга служит опция -a, а для запрета сообщений о предупреждениях - опция -W. Поэтому вызов компиляции исходного файла prim.s с описанными только что режимами следует задавать в виде prim.s

as -o prim.o prim.s -Wa > prim.lst

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

as -o "$1".o "$1".s -Wa >"$1".lst

Здесь макропеременная $1 соответствует первому или единственному параметру вызова сценария. Если этот сценарий как файл назван именем fasm, то для нашего примера вызов должен производиться в виде

fasm prim

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

ld -o prim.exe prim.o

который формирует исполняемый файл prim.exe. В общем случае удобно построить файл сценариев обработки для последовательной компиляции и компоновки, назвав его, например, asm2exe и заполнив двумя строками определения действий

as -o "$1".o "$1".s -Wa >"$1".lst ld -o "$1".exe "$1".o

Использования этого файла сценариев для получения исполняемого файла exmpl.exe из исходного файла exmpl.s достигается вызовом в командной строке

asm2exe exmpl

Заметим, что оба рассмотренных файла fasm и asm2exe сценариев должны быть заданы с атрибутами исполнения, что можно достигнут выполнив команды

chmod +x fasm chmod +x asm2exe

Ассемблер AT&T использует сокращенные директивы описания сегментов. Они подобны директивам сокращенного описания сегментов в макроассемблерах MASM, TASM и т.п. Задаются эти директивы служебными словами .data , .text.

126

Стек явно, также как и для ОС MS Windows NT, не описывается. Точка входа в программу (метка начала ее выполнения при запуске) имеет стандартное наименования, отклонятся от которого не дозволяется. Это наименование совпадает с аналогичным наименованием точки входа в ассемблере NASM и задается как _start. Но здесь оно обязательно задается с помощью завершающего двоеточия. Таким образом метка запуска программы на ассемблере на выполнение имеет вид

_start:

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

_start:

.global _start

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

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

Все непосредственные операнды (константы в качестве операндов) должны обязательно предваряться служебным символом $. Сами числовые константы задаются в том же виде, как и в языке Си. Так что вызов прерывания с десятичным номером 128 должно задаваться командой

int $0x80

Вкомандах, способных выполнять действия с данными различного размера, конкретный размер обрабатываемых данных обязательно задается последним (и дополнительным относительно синтаксиса Intel) символом, в качестве которого допускаются буквы b, w и l. Они обозначают, соответственно, размер в байт, слово (16 битов) и длинное слово (32 бита).

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

movl $12, %edx

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

Для задания текстовых строк предназначены два основных вида директив: .ascii

и.asciz. Вторая из них автоматически добавляется при трансляции в конец заданного в ней текста служебный символ окончания текста ('\0' равносильный нулевому содержимому байта). Таким образом, задание обозначаемого именем msg текста Привет мир! можно задать директивой

msg: .ascii "Привет мир!"

127

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

msg: .asciz "Привет мир!" или в виде

msg: .ascii "Привет мир!\0"

Альтернативой директиве .ascii служит директива .string, которая имеет ту же форму операндов.

Последовательность байтов задается директивой .byte, 16-тиразрядных слов - директивой .word, последовательность из 32-битных слов дается директивой .int или

.long. В качестве операндов этих директив может использоваться одно или более числового значения, разделенные запятыми. Кроме того, могут использоваться обозначения кодов отдельных символов, помещенные в апострофы. Например,

xyz: .byte 'A',10,13,'$'

Заполнение последовательности байтов нулями задается директивой

.space число

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

.zero число_байтов

Для задания эквивалентностей (именований выражений) предназначена директива

.equ символ, выражение

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

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

movl $3, %eax movl $0, %ebx movl $buffer, %ecx movl $число, %edx int $0x80

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

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

movl $4, %eax movl $1, %ebx movl $txt, %ecx movl $число, %edx int $0x80

128

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

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

movl $1, %eax int $0x80

которая задает выполнение функции exit(0) для нормального завершения программы.

Следующий пример более подробно демонстрирует описанные средства и возможности ассемблера AT&T.

.data

buffer: .zero 80

.global _start _start:

movl $3, %eax movl $0, %ebx movl $buffer, %ecx movl $80, %edx int $0x80

movl %eax, %edx lea buffer,%ebx movb $'!',1(%ebx)

movb $10,1(%ebx,%edx)

movl $4, %eax movl $1, %ebx movl $buffer, %ecx int $0x80

movl $1, %eax int $0x80

Этот пример задает ввод не более 80-ти символов произвольного текста, число которых заноситься в регистр edx. Затем на место второго символа введенного текста (со смещением +1 от начала текста в буфере buffer) заносится символ восклицательного знака, а после всех введенных символов дополнительно приписывается управляющий символ перевода строки (символ \n с числовым значением 10). Этот управляющий символ пишется в буфер со смещением от его начала, равным сумме содержимого регистра EDX и дополнительного смещения 1.

Последний пример демонстрирует также особенности записи более сложных способов адресации, чем непосредственный и регистровый. Именно, в общем слу-

129

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

смещение(базовый_регистр, индексный_регистр)

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

смещение(, индексный_регистр)

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

смещение(базовый_регистр)

Например Intel-ассемблерная запись [ebx+10] должна записываться здесь в виде 10(%ebx), запись [esi] в виде 0(, %esi) или (, %esi). Впрочем, в 32-битной архитектуре регистры ESI и EDI можно использовать и как базовые, а поэтому последний операнд записывать и в виде (%esi).

Упражнения.

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

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

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

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

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

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

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

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

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

130

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