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

Танненбаум Е. Архітектура компютера [pdf]

.pdf
Скачиваний:
114
Добавлен:
02.05.2014
Размер:
5.59 Mб
Скачать

Процесс ассемблирования

имен содержится само имя (или указатель на него), его численное значение и ин некоторая дополнительная информация. Она может включать:

1.Длину поля данных, связанного с символом.

2.Биты перераспределения памяти (которые показывают, изменяется ли чение символа, если программа загружается не в том адресе, в котором п полагал ассемблер).

3.Сведения о том, можно ли получить доступ к символу извне процедур

Таблица 7.7. Счетчик адреса команд используется для слежения за адресами ком В данном примере операторы до MARIA занимают 100 байтов

Метка

Код операции

Операнды

Комментарии

Длина

Счетчик адреса ко

MARIA:

MOV

EAX, I

EAX=I

5

100

 

MOV

EBX,J

EBX=J

6

105

ROBERTA:

MOV

ECX,К

ECX=K

6

111

 

IMUL

EAX, EAX

EAX=I*I

2

117

 

IMUL

EBX, EBX

EBX=J*J

3

119

 

IMUL

ECX,ECX

ECX=K*K

3

122

MARILYN:

ADD

EAX,EBX

EAX=I*I+J*J

2

125

 

ADD

EAX,ECX

EAX=I*I+J*J+K*K

2

127

STEPHANY: JMP

DONE

Переход к DONE

5

129

Таблица 7.8. Таблица символьных имен для программы из табл. 7.7.

Символьное имя

Значение Прочая информация

MARIA

100

ROBERTA

111

MARILYN

125

STEPHANY

129

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

Таблица 7.9. Некоторые элементы таблицы кодов операций для ассемблера Penti

Код

Первый

Второй

Шестнадцатеричный

Длина

Класс

операции

операнд

операнд

код

команды

команды

5 3 4 Глава 7. Уровень языка ассемблера

Вкачестве примера рассмотрим код операции ADD. Если команда ADD в первого операнда содержит регистр ЕАХ, а в качестве второго — 32-битную к (immed32), то используется код операции 0x05, а длина команды составля тов. Если используется команда ADD с двумя регистрами в качестве опер длина команды составляет 2 байта, а код операции будет равен 0x01. Все к ции кодов операций и операндов, которые соответствуют данному прави отнесены к классу 19 и будут обрабатываться так же, как команда ADD регистрами в качестве операндов. Класс команд обозначает процедуру, вызывается для обработки всех команд данного типа.

Внекоторых ассемблерах можно писать команды с применением непос ной адресации, даже если соответствующей команды не существует в в языке. Такие команды с «псевдонепосредственными» адресами обрабат следующим образом. Ассемблер назначает участок памяти для непосредс операнда в конце программы и порождает команду, которая обращаетс Например, универсальная вычислительная машина IBM 3090 не имеет ком посредственными адресами. Тем не менее программист может написать

L 14.=F'5'

для загрузки в регистр 14 константы 5 размером в полное слово. Таким программисту не нужно писать директиву, чтобы разместить слово в пам дать ему значение 5, дать ему метку, а затем использовать эту метку в ко Константы, для которых ассемблер автоматически резервирует память, н ся литералами. Литералы упрощают читаемость и понимание программ значение константы очевидным в исходном операторе. При первом пр семблер должен создать таблицу из всех литералов, которые используют грамме. Все три компьютера, которые мы взяли в качестве примеров, команды с непосредственными адресами, поэтому их ассемблеры не обе ют литералы. Команды с непосредственными адресами в настоящее вре ются обычными, но раньше они рассматривались как нечто совершенно ное. Вероятно, широкое распространение литералов внушило разработч непосредственная адресация — это очень хорошая идея. Если нужны лит во время ассемблирования сохраняется таблица литералов, в которой по новый элемент всякий раз, когда встречается литерал. После первого про лица сортируется и продублированные элементы удаляются.

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

Листинг 7.5. Первый проход простого ассемблера

 

 

 

 

 

Процесс ассемблирования

53

location_counter = 0;

//ассемблирование первой команды в ячейке 0

 

 

 

imtialize_tables(),

//общая инициализация

 

 

 

 

 

 

 

while (more_input) {

 

//more_input получает значение «ложь» с помощью END

line = read_next_line();

//считывание строки

 

 

 

 

 

 

length =0;

 

 

//# байт в

команде

 

 

 

 

 

 

type =0.

 

 

//тип команды

 

 

 

 

 

 

 

if (line_isjiot_coniment(line)) {

 

 

 

 

 

 

 

 

symbol = check_for_symbol(line),

//Содержит ли строка метку?

 

 

 

if (symbol

!- null)

 

 

//если да,

то записывается

символ и

enter_new_symbol(symbol. 1ocation_counter),

 

 

 

 

 

 

literal = check_for_literal(line).

//Содержит ли строка литерал?

 

 

if (literal

!= null)

 

 

//если да,

то он

вводится в

таблицу

enter_new_literal(1itera1);

 

 

 

 

 

 

 

 

 

 

 

 

//Теперь определяем тип кода операции.

 

 

 

 

 

//-1 значит недопустимый код операции.

 

opcode = extract_opcode(line).

//определяем место кода

операции

 

 

type =search_opcode_table(opcode).

//находим формат,

например. OP REG1.REG2

if (type <

0)

 

 

 

//Если это не

код

операции,

являет

 

 

 

 

//ли

это директивой?

 

 

 

 

 

type = search_pseudo_table(opcode).

 

 

 

 

 

 

 

switch(type) {

 

 

 

//определяем

длину команды

 

case l.length=get_length_of_typel (line), break,

 

 

 

 

 

 

case 2 Iength=get_length_of_type2(line); break.

 

 

 

 

 

 

 

 

 

 

//другие случаи

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

wnte_temp_file(type, opcode, length, line),

//информация для второго прохода

 

location_counter =

location_counter + length, //обновление счетчика

адреса команд

if (type == END_STATEMENT) {

 

 

//завершился ли

ввод?

 

 

morejinput - false.

 

 

 

//если да.

то

выполняем

служеб

rewind_temp_for_pass_two().

 

 

//перематываем

файл обратно

 

sort_literal_table().

 

 

//сортируем

таблицу литералов

remove_redundant_literals();

 

 

//и удаляем из

нее

дубликаты

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

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

получения входных данных спрятаны в процедуре read_next_line. Если эти детал

536 Глава 7. Уровень языка ассемблера

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

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

Второй проход

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

Листинг 7.6. Второй проход простого ассемблера

public static void pass_two()

{

//Эта процедура - второй проход ассемблера

 

 

 

boolean morejnput = true:

 

//флаг,

который останавливает второй п

String line, opcode;

 

//поля команды

int location_counter, length, type: //переменные

 

final

int END_STATEMENT =

-2:

//сигналы

конца ввода

final

int MAX_CODE =16;

 

//максимальное количество байтов в ком

byte code[] = new byte[MAX_CODE];

//количество байтов в команде в порожденном

location_counter = 0;

while (morejnput) { type = readj:ype():

opcode = read_opcode(); length = readJengthO; line = readJineO;

if (type != 0) { switch(type) {

//ассемблирование первой команды в а

//morejnput устанавливается на «ло //считывание поля типа следующей строки //считывание поля кода операции сле //считывание поля длины в следующей //считывание самой входной строки //тип 0 указывает на строки комм

//порождение выходного кода

case l:evalj:ypel(opcode, length, line, code): break;

case 2: eval_type2(opcode, length, line, code); break;

 

Процесс ассемблирования

537

more_input = false;

// если да, то выполняем служебные операции

 

finishjjpO;

// завершение

 

}

Процедура второго прохода более или менее сходна с процедурой первого прохода: строки считываются по одной и обрабатываются тоже по одной. Поскольку мы записали в начале каждой строки тип, код операции и длину (во временном файле), все они считываются, и таким образом, нам не нужно проводить анализ строк во второй раз. Основная работа по порождению кода выполняется процедурами eval_type1, eval_type2 и т. д. Каждая из них обрабатывает определенную модель (например, код операции и два регистра-операнда). Полученный в результате двоичный код команды сохраняется в переменной code. Затем совершается контрольное считывание. Желательно, чтобы процедура write_code просто сохраняла в буфере накопленный двоичный код и записывала файл на диск большими порциями, чтобы сократить рабочую нагрузку на диск.

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

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

1.Используемый символ не определен.

2.Символ был определен более одного раза.

3.Имя в поле кода операции не является допустимым кодом операции.

4.Код операции не снабжен достаточным количеством операндов.

5.У кода операции слишком много операндов.

6.Восьмеричное число содержит 8 или 9.

7.Недопустимое применение регистра (например, переход к регистру).

8.Отсутствует оператор END.

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

Таблица символов

5 38 Глава 7. Уровень языка ассемблера

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

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

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

Предположим, что средний элемент таблицы не равен символу, кот ищем. Мы уже знаем, в какой половине таблицы он находится. Алгоритм д поиска можно применить к соответствующей половине. В результате мы ли чим совпадение, либо определим нужную четверть таблицы. Таким образо лице из п элементов нужный символ можно найти примерно за lo&n попы видно, что такой алгоритм работает быстрее, чем просто последовательный таблицы, но при этом элементы таблицы нужно сохранять в алфавитном

Совершенно другой подход — хэш-кодирование. Для этого подхода т хэш-функция, которая отображает символы (имена) в целые числа в про от 0 до к-1. Такой функцией может быть функция перемножения код всех символов в имени. Можно перемножить все коды ASCII символов с рованием переполнения, а затем взять значение по модулю к или раздели ченное значение на простое число. Фактически подойдет любая входная ф которая дает равномерное распределение значений.

Символьные имена можно хранить в таблице, состоящей из к участко к-1. Все пары (символьное имя, значение), в которых имя соответствует няются в связном списке, на который указывает слот i в хэш-таблице. Есл таблице содержится п символьных имен и к слотов, то в среднем длин будет n/k. Если мы выберем к, приблизительно равное п, то на нахождени го символьного имени в среднем потребуется всего один поиск. Путем к ровки к мы можем сократить размер таблицы, но при этом скорость пои зится. Хэш-код показан на рис. 7.1.

Связывание и загрузка

53

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

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

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

2.Связывание объектных модулей.

 

 

 

 

 

Andy

14025

0

 

 

 

 

 

 

 

Anton

31253

4

 

 

 

 

 

 

 

Cathy

65254

5

 

 

 

 

 

 

 

Dick

54185

0

 

 

 

 

 

 

 

Erik

47357

6

 

 

 

 

 

 

 

Frances

56445

3

 

 

 

 

 

 

 

Frank

14332

3

 

 

 

 

 

 

 

Gerrit

32334

4

 

 

 

 

 

 

 

Hans

44546

4

 

 

 

 

 

 

 

Henri

75544

2

 

 

 

 

 

 

 

Jan

17097

5

 

 

 

 

 

 

 

Jaco

64533

6

 

 

 

 

 

 

 

Maarten

23267

0

 

 

 

 

 

 

 

Reind

63453

1

 

 

 

 

 

 

 

Roel

76764

7

 

 

 

 

 

 

 

Willem

34544

6

 

 

 

 

 

 

 

Wiebern

34344

1

 

 

Хэш-

 

 

 

 

 

 

 

 

 

 

 

таблица

Связная таблица

 

 

 

 

 

 

 

0

Andy

|

14025

|

Maarten

|

23267

|

4+-| Dick

| 54185~

1

Reind

|

63453

 

 

Wiebern |

34344

 

 

 

2

Henri

 

75544

 

 

 

 

 

 

 

 

3

Frances

|

56445

|

Ц->\

Frank

|

14332

 

 

 

4

Hans

 

44546

 

 

Gerrit

 

32334

 

Anton

31253

5

Jan

 

17097

 

* ^ H

Cathy

|

65254

 

 

 

6

Jaco

 

64533

 

 

Willem

 

34544

 

Erjk

"r7357

7

Roel

 

76764

 

 

 

 

 

 

 

 

Рис. 7 . 1 . Хэш-кодирование: символьные имена, значения и хэш-коды, образованные

5 40 Глава 7. Уровень языка ассемблера

Трансляция исходной процедуры в объектном модуле — это переход уровень, поскольку исходный язык и выходной язык имеют разные кома пись. Однако при связывании перехода надругой уровень не происходит, программы на входе и на выходе компоновщика предназначены для од же виртуальной машины. Задача компоновщика — собрать все процеду рые транслировались раздельно, и связать их вместе, чтобы в результате исполняемый двоичный код. В системах MS-DOS, Windows 95/98 и N ные модули имеют расширение .obj, а исполняемые двоичные программ ширение .ехе. В системе UNIX объектные модули имеют расширение .о няемые двоичные программы не имеют расширения.

Исходная

 

Объектный

 

 

процедура1

 

модуль 1

 

 

Исходная

 

Объектный

 

Испо

Транслятор

Компоновщик

дв

процедура2

модуль 2

 

 

 

Исходная

 

Объектный

 

 

процедура3

 

модуль 3

 

 

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

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

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

Задачи компоновщика

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

Связываниеизагрузка 54

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

400

300

200

100

0

500

400

300

200

100

 

 

Объектный модуль В

 

еии

 

 

500

CALL С

Объектный модуль А

 

 

 

400

 

CALL В

300

MOVE Q ТО X

 

MOVE P ТО X

200

 

 

100

 

BRANCH TO 200

0

BRANCH TO 300

Объектный модуль С

 

 

CALLD

 

 

 

 

Объектный модуль D

 

300

 

MOVE R ТО X

200

MOVE S ТО X

 

100

 

BRANCH TO 200

 

BRANCH TO 200

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

Посмотрите на рис. 7.4, а. Хотя программа уже загружена в отображение ис

полняемого двоичного файла, она еще не готова для выполнения. Посмотрим, чт

5 42 Глава 7. Уровень языка ассемблера

1900

1800 MOVE S ТО X

1700

1600 BRANCH TO 200

1500 CALLD

1400

1300 MOVE R ТО X

1200

1100 BRANCH TO 200

1000

CALL С

900

800 MOVE Q ТО X

700

600

BRANCH TO 300

500

400

CALL В

300 MOVE P ТО X

200

BRANCH TO 200

100

п

 

 

1900

 

 

Объектный

1800

MOVE S ТО X

 

 

 

У

модуль D

 

 

 

 

1700

 

 

 

1600

BRANCH ТО 1800

 

 

1500

CALL 1600

 

Объектный

1400

 

 

 

 

 

модуль С

1 3 0 0

MOVE R TO X

 

 

 

 

 

1200

 

 

 

1100

BRANCH TO 1300

 

 

1000

 

 

 

 

CALL 1100

 

 

900

 

i

Объектный

800

MOVE Q TO X

/

модуль В

 

 

700

 

 

 

600

 

 

 

 

BRANCH TO 800

 

 

500

 

 

 

400

CALL 500

 

Объектный

300

MOVE P TO X

 

модуль А

 

 

200

 

BRANCH TO 300

100

0

Об /" м

V Об

/м

vОб

}м

Об

м

Соседние файлы в предмете Аппаратное обеспечение ЭВМ, средств телекоммуникаций и сетей