Основы программирования. Борисенко
.pdf2.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.Ф у н к ц и и передается адрес массива целых чисел и его длина. Вычислить значение максимального элемента массива.