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

Основы программирования. Борисенко

.pdf
Скачиваний:
1534
Добавлен:
09.04.2015
Размер:
9.31 Mб
Скачать

2.4 RTL: машинно-независимый Ассемблер

81

и управление передается по адресу возврата. Результат функции / передается через нулевой регистр.

R0

:=

результат функции

S P

:=

S P + 12

pop

 

F P

return

Вызывающая программа удаляет из стека фактические значения ар¬ гументов x и y, помещенные в стек перед вызовом функции / .

2.4.RTL: машинно-независимый Ассемблер

Каждый процессор имеет свои специфические команды, наборы

регистров и режимы

адресации,

поэтому программу на Ассембле¬

ре невозможно

перенести с одной аппаратной платформы на

дру¬

гую. Д л я того

чтобы

не зависеть

от конкретного процессора,

часто

используют язык описания команд R T L , от англ. Register Transfer Language — язык перемещения регистров. Фактически R T L пред¬ ставляет собой Ассемблер, не зависящий от конкретного процессо¬ ра. Многие компиляторы, например, gcc, не переводят программу с

языка высокого

уровня сразу на язык машинных

команд, а снача¬

ла транслируют

ее на язык R T L . Затем на уровне

R T L выполняется

оптимизация кода, которая составляет 99% работы компилятора. И

лишь на последнем этапе программа

c языка R T L переводится на

язык команд конкретного процессора.

Поскольку R T L максимально

приближен к Ассемблеру, трансляция

из R T L в конкретный Ассем¬

блер не представляет никакого труда.

 

Такой подход позволяет сделать

компилятор с языка высокого

уровня практически независимым от конкретной архитектуры. Зави¬ сим лишь модуль, осуществляющий перевод с R T L в Ассемблер, но его реализация требует минимальных усилий.

М ы будем использовать

R T L для записи примеров несложных

программ в кодах вместо какого-либо конкретного

Ассемблера.

В R T L имеется неограниченное число регистров

общего назначе¬

ния

 

 

R0,

R1, R2, R3, . . .

 

и несколько выделенных регистров:

82

 

2.4. RTL: машинно-независимый Ассемблер

1)

счетчик команд

P C ;

 

2)

указатель стека

SP;

 

3)

регистр флагов

C C 0 (от слов

Conditional Codes), иногда добав¬

 

ляют т а к ж е дополнительные

регистры флагов C C 1 , C C 2 , . . . ;

4)

указатель кадра

FP .

 

Слово памяти с адресом a обозначается в R T L через m[a]. Вы¬ ражение a может быть константой, регистром или суммой регистра и константы, что соответствует абсолютному, косвенному и относи¬ тельному режимам адресации. Примеры:

 

 

m[1000], m[R0],

m[FP

- 4].

Байт

с адресом

a обозначается через

mb [a],

короткое (двухбайтовое)

слово

— через

ms [a].

 

 

Арифметические команды, такие, как сложение или умножение,

записываются в R T L в естественном

виде, например, команда сложе¬

ния двух регистров R0 и R1, помещающая результат в R2, записы¬

вается в R T L в

виде

 

 

 

 

R2 := R 0 + R 1

 

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

L:

goto L;

Ветвление в R T L реализуется с помощью команд условного пере¬ хода, которые в зависимости от состояния различных битов или их

2.4. RTL: машинно-независимый Ассемблер

83

комбинаций

в регистре

флагов C C 0 либо осуществляют переход на

указанную

метку, либо

ничего на делают. Например, команда

i f (eq) goto L;

 

 

осуществляет переход на метку L в случае, когда результат

предыду¬

щей команды равен нулю (eq — от слова equal), т.е. в регистре C C 0 установлен бит z (от слова zero). Большинство арифметических ко¬ манд автоматически устанавливают биты-признаки результата в ре¬ гистре флагов C C 0 . Очень часто требуется просто сравнить два чис¬

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

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

(от слова compare). Логически команду сравнения следует понимать

как вычитание двух чисел,

при этом результат как бы

помещается

в регистр флагов. На самом

деле, в регистре флагов от

результата

остаются лишь биты-признаки

(равен ли он нулю, больше нуля и

т.п.). В R T L команда сравнения

двух регистров R0 и R1 записывает¬

ся следующим образом:

 

C C 0 := R0 - R1;

 

Результат как бы помещается в регистр флагов C C 0 .

Вкомандах условного перехода, таких как

if (eq) goto L;

можно использовать следующие условия:

eq

результат

равен нулю (equal)

ne

результат не равен нулю (not equal)

g

результат

больше

нуля

(greater)

l

результат

меньше

нуля

(less)

ge

результат больше или равен нулю (greater ОГ equal)

le

результат

меньше

или равен нулю (less or equal)

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

84

2.4. RTL: машинно-независимый Ассемблер

и "ниже" (above и below):

aпервое число выше второго (above)

bпервое число ниже второго (below)

ae

первое

число

выше

или равно

второму

(above ОГ

equal)

be

первое

число

ниже

или равно

второму

(below ОГ

equal)

Приведем простой пример реализации конструкции "если":

если R0 == R1

| то R2 := 77; конец если

На R T L этот фрагмент реализуется так:

CC0 := R0 - R1; i f (ne) goto L; R2 := 77;

L:

Отметим, что в команде условного перехода используется отрицание условия после слова "если", т.е. фактически условие обхода фраг¬ мента кода после слова "то".

2.4.1.Примеры программ на RTL и Ассемблере Intel 80x86

Рассмотрим несколько простых примеров программ на "виртуаль¬ ном Ассемблере" R T L и на конкретном Ассемблере дл я процессора Intel 80x86.

Вычисление наибольшего общего делителя

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

2.4.1. Примеры программ на RTL и Ассемблере

85

int gcd(int

x, i n t y) { // цел алг. gcd(цел x, цел y)

int a, b, r;

//

| цел a, b, r;

a = x; b

= y;

//

|

a := x; b := y;

while

(b

!= 0) {

//

| цикл пока b != 0

r

= a % b;

// 1 1 r := a % b;

a

= b;

//

1

| a

:= b;

b

= r;

//

1

| b

:= r;

}

a;

//

|

конец

цикла

return

//

|

ответ

:= a;

}

 

 

// конец

алгоритма

Вместо Н О Д мы назвали функцию "gcd" (от слов greatest common divizor), поскольку в языке Си русские буквы в именах функций и переменных использовать нельзя. Запишем эту программу на языке R T L . Переменные a, b, r мы будем хранить в регистрах R0, R1 , R2.

L1:

L2:

push FP;

// Вход

в функцию:

// сохраним значение FP в стеке;

FP

:= SP;

// определим новое значение FP;

push R1;

// сохраним значения регистров R1

push R2;

//

 

и R2

R0

:= m[FP+8];

//

:= x;

// a

R1

:= m[FP+12];

// b

:= y;

CC0

:= R1 - 0;

// метка начала цикла

//

сравнить b с нулем

i f

(eq) goto L2;

//

если результат равен нулю,

//

 

то перейти на метку L2

R2

:= R0 % R1;

//

r := a % b;

//

a

:= b;

R0

:= R1;

//

b

:= r;

R1

:= R2;

//

перейти на метку L1

goto L1

// метка конца цикла

//ответ уже содержится в R0

//выход из функции:

pop

R2;

// восстановим

значения R2

pop

R1;

//

и R1

pop FP;

// восстановим

значение FP

return;

// вернемся в вызывающую программу

86

2 4 RTL: машинно-независимый Ассемблер

Эта программу

можно переписать на конкретный Ассемблер, на­

пример, на Ассемблер "Masm" фирмы Microsoft дл я процессоров Intel 80x86. Первое, что надо сделать при переводе с R T L на Ассемблер — это распределить регистры, т.е. задать отображение виртуальных ре¬ гистров R0, R1, . . . на конкретные регистры данного процессора. У процессоров серии Intel 80x86 есть всего 8 общих регистров: это регистры

E A X ,

E B X , E C X , E D X , ESI, EDI , E B P , ESP .

Процессор Intel

сконструирован

таким образом, что к а ж д ы й ре¬

гистр выполняет

в определенных

командах свою особую роль (Intel

80x86 — это CISC-процессор; в RISC-процессорах все регистры рав¬ ноправны). В частности, команда деления всегда использует в каче¬ стве делимого длинное восьмибайтовое целое число, содержащееся в паре регистров ( E D X , E A X ) , где старшие байты в регистре E D X . В результате выполнения команды деления вычисляется как част¬ ное, так и остаток от деления: частное помещается в регистр E A X , остаток — в регистр E D X .

В данной программе на языке R T L остаток от деления

помещает¬

ся в регистр R2. Поэтому регистр

R2 удобно отобразить

на

регистр

E D X , это позволит избежать

лишних пересылок

результата

из одно¬

го регистра

в другой. Итак,

зафиксируем следующее распределение

регистров:

 

 

 

 

 

 

 

 

R0 — E A X

 

 

 

 

 

R1 — E B X

 

 

 

 

 

R2 -

E D X

 

 

 

 

 

F P — E B P

 

 

 

 

 

SP -

E S P

 

 

 

После того

как распределены регистры, остается

только

переписать

к а ж д у ю строку R T L программы на конкретный Ассемблер. Д л я этого необходимо знать ограниченный набор команд, реализующих опера¬

ции языка R T L в конкретном Ассемблере.

Например,

в нашем слу¬

чае операция пересылки из одного регистра

в другой

или из памяти

в регистр реализуется командой mov, операция деления реализует¬ ся командой div и т.д. Программа на языке Ассемблера Intel 80386 записывается следующим образом:

2.4.1. Примеры программ на RTL и

Ассемблере

 

 

 

 

 

 

87

.386

f l a t ,

s t d c a l l

 

 

 

 

 

 

 

 

 

 

.model

 

 

 

 

 

 

 

 

 

 

.code

 

 

 

 

 

 

 

 

 

 

 

 

 

gcd:

 

EBP

 

;

Вход

в

функцию:

значение

EBP

push

 

;

сохраним

старое

mov

 

EBP, ESP

;

определим

новое

значение

EBP

push

EBX

 

;

сохраним

значения

EBX

 

 

push

EDX

 

;

 

 

 

 

 

и

EDX.

 

mov

 

EAX,

[EBP+8]

;

EAX

:= x

 

 

 

 

 

 

mov

 

EBX,

[EBP+12]

;

EBX

:= y

 

цикла

 

 

 

L1:

 

EBX,

0

; метка

начала

 

 

 

cmp

 

;

сравнить EBX с нулем

 

j e

 

L2

 

;

если

результат

равен нулю,

mov

 

EDX,

0

;

 

 

то перейти

на метку L2

 

;

EDX

:= EAX

% EBX

 

 

 

div

 

EBX

EBX

 

 

 

mov

 

EAX,

;

EAX

:=

EBX

 

 

 

 

 

mov

 

EBX,

EDX

;

EBX

:=

EDX

 

 

L1

 

 

L2:

 

L1

 

;

перейти

на метку

 

 

 

 

 

; метка

конца

цикла

 

 

 

 

 

 

 

; ответ уже содержится в EAX

pop

EDX

 

 

;

выход

из функции:

 

EDX

 

 

 

;

восстановим

значения

 

pop

EBX

 

 

;

восстановим

 

 

и

EBX

 

pop

EBP

 

 

;

значение

EBP

 

ret

 

 

 

;

возврат из функции

 

 

 

public gcd

 

 

 

 

 

 

 

 

 

 

 

 

end

 

 

 

 

 

 

 

 

 

 

 

 

 

Суммирование массива

 

 

 

 

 

 

 

 

 

 

Рассмотрим еще один простой пример

программы

на

Ассембле­

ре. Требуется

вычислить сумму

элементов

целочисленного

массива

заданной длины. Прототип этой функции на языке Си выглядит сле¬ дующим образом:

88

 

 

 

 

 

2.4. RTL: машинно-независимый

Ассемблер

int

 

sum(int a [ ] , i n t

n);

 

 

 

 

Ф у н к ц и и передается

(как обычно, через аппаратный стек) адрес на­

чала целочисленного

массива

а и его длина n . На R T L функция sum

записывается следующим

образом:

 

 

 

 

push FP;

 

// Вход в

функцию:

 

 

 

// сохраним старое значение FP;

 

FP

:= SP;

 

// определим новое значение FP;

 

push

R1;

 

// сохраним значения регистров R1,

push

R2;

 

//

 

 

 

 

 

и

R2

push

R3;

 

//

 

 

 

 

 

R3.

 

 

 

 

//

 

 

 

 

 

 

 

R0

:= 0;

 

//

обнулим

сумму

 

 

 

R1

:= m[FP+8];

//

R1

:=

a

(адрес начала массива)

R2

:= m[FP+12]; //

R2

:=

n

(число эл-тов массива)

L1:

 

:= R2

- 0;

// метка

начала

цикла

 

 

CC0

 

//

 

сравнить R2

с нулем

 

 

i f

(le) goto L2; //

если

результат <= 0,

 

R3

:= m[R1];

//

 

 

то перейти на метку L2

 

//

 

R3 := очередной элемент массива

R0

:= R0

+ R3;

//

 

прибавим очередной эл-т к сумме

R1

:= R1

+ 4;

//

 

увеличим адрес очер. эл-та на 4

R2

:= R2

- 1;

//

 

уменьшим счетчик необр. эл-тов

goto

L1

 

//

 

перейти на метку L1

 

 

L2:

 

 

 

// метка

конца цикла

 

 

 

 

 

 

// ответ уже содержится в R0

 

 

 

 

 

// выход

из функции:

 

 

pop

 

R3;

 

//

восстановим значения

R3,

 

pop

 

R2;

 

//

 

 

 

 

и

R2

 

pop

 

R1;

 

//

 

 

 

 

R1

 

pop

 

FP;

 

// восстановим старое значение FP

return;

 

// вернемся

в вызывающую

программу

В этой программе адрес очередного элемента массива содержится в регистре R1. Сумма просмотренных элементов массива накапливает­ ся в регистре R0. Регистр R2 содержит число еще не обработанных элементов массива. В начале программы в регистр R1 записывается адрес начала массива, а в R2 — число элементов массива. В теле

2.4.1. Примеры программ на RTL и Ассемблере

89

цикла очередной элемент массива читается из памяти и

помещается

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

Д л я переписывания программы на Ассемблер Intel 80386 зафик­ сируем следующее распределение виртуальных регистров:

R0 — E A X

R1 — E B X

R2 — E O X

R3 - E D X

F P — E B P

SP — E S P

Программа переписывается таким образом:

.386

.model f l a t , s t d c a l l

.code

sum: push mov push push push

mov mov mov

L1:

cmp j l e

mov add

EBP

 

 

 

Вход

в функцию:

значение

EBP

 

ESP

 

;

сохраним

старое

 

EBP,

 

определим

новое

значение

EBP

EBX,

EBX

 

 

 

сохраним

значения регистров

ECX

 

 

 

 

 

 

 

 

и

ECX

EDX

 

 

 

 

 

 

 

 

EDX.

EAX,

0

 

; EAX

:= 0

 

 

 

 

 

EBX,

[EBP+8] ;

EAX

:= a

 

 

 

 

 

ECX,

[EBP+12];

ECX

:= n

 

 

 

 

 

ECX,

0

;

 

метка начала

цикла

 

 

 

сравнить

ECX

с нулем

 

 

L2

 

 

 

если результат

<= 0,

 

 

EDX,

[EBX]

;

EDX

то перейти на метку L2

 

:= очередной эл-т массива

EAX,

EDX

 

;

EAX

:= EAX+EDX

 

 

 

90

 

 

2.4. RTL: машинно-независимый Ассемблер

add

EBX, 4

;

EBX

:= EBX+4 (адрес след. эл-та)

dec

ECX

;

ECX

:= ECX-1 (счетчик)

jmp

L1

;

перейти на метку L1

 

L2:

 

; метка

конца

цикла

 

 

 

; ответ содержится в регистре EAX

pop

EDX

; выход

из функции:

EDX,

; восстановим

значения

pop

ECX

;

 

 

 

ECX

pop

EBX

;

 

 

и EBX.

pop

EBP

; восстановим

значение

EBP

ret

 

; вернемся в вызывающую

программу

public sum end

Отметим, что мы использовали команду уменьшения значения реги­ стра на единицу dec (от слова decrement) дл я реализации следующей строки R T L :

R2 := R2 - 1; // уменьшим счетчик необр. эл-тов

В Ассемблере Intel 80386 она записывается как

dec ECX

; ECX := ECX-1

Команда увеличения регистра на единицу обычно записывается как inc (от слова increment). Эти команды, как правило, присутствуют в наборе инструкций любого процессора.

2.4.2.Задачи по теме «Программирование на Ассемблере»

Во всех задачах требуется написать на R T L или на Ассембле­

ре Intel

80386 функцию, которая вызывается из программы

на Си.

Ф у н к ц и я должна возвращать целочисленный ответ в регистре R0 (в

регистре E A X в

случае Ассемблера Intel 80386). Аргументы

пере¬

даются

функции

через стек в соответствии с соглашениями

языка

Си.

 

 

 

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