Материал: Архитектура и программирование MIPS-процессоров. Разинкин К.А

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

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

Рис. 2.22. Этапы трансляции и запуска программы

Пример Компиляция программы

Код на языке высокого уровня int f, g, y; // global variables

int main(void)

{

f = 2; g = 3;

y = sum(f, g); return y;

96

}

 

 

int sum(int a, int b) {

return (a + b);

 

}

 

 

Код на языке ассемблера MIPS

.data

 

f:

 

 

g:

 

 

y:

 

 

.text

 

 

main:

 

addi $sp, $sp, −4 # make stack frame

sw

$ra, 0($sp)

# store $ra on stack

addi $a0, $0, 2

# $a0 = 2

sw

$a0, f

# f = 2

addi $a1, $0, 3

# $a1 = 3

sw $a1, g

 

# g = 3

jal sum

# call sum function

sw

$v0, y

# y = sum(f, g)

Iw

$ra, 0($sp)

# restore $ra from stack

addi $sp, $sp, 4

# restore stack pointer

jr

$ra

# return to operating system

sum:

 

 

add

$v0, $a0, $a1 # $v0 = a + b

jr $ra

 

# return to caller

Этап 1: Компиляция Компилятор транслирует код высокого уровня в код на

языке ассемблера. В примере кода вышеизложенного кода показана простая программа на языке высокого уровня, содержащая три глобальные переменные и две функции, а также ассемблерный код, сгенерированный типичным компилятором. Ключевые слова .data и .text – это ассемблерные директивы, указывающие на начало сегментов данных и кода соответственно. Для обозначения глобальных переменных f, g и y использу-

97

ются метки. Места для их хранения будут определены ассемблером. На данный момент они остаются в коде в виде символов.

Этап 2: Трансляция Ассемблер транслирует код на языке ассемблера в объ-

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

0x00400000 main: addi $sp, $sp, −4

0x00400004

sw

$ra, 0($sp)

0x00400008

addi $a0, $0, 2

0x0040000C

sw

$a0, f

0x00400010

addi $a1, $0, 3

0x00400014

sw

$a1, g

0x00400018

jal sum

0x0040001C

sw

$v0, y

0x00400020

lw

$ra, 0($sp)

0x00400024

addi $sp, $sp, 4

0x00400028

jr

$ra

0x0040002C sum: add $v0, $a0, $a1 0x00400030 jr $ra

Имена и адреса символов хранятся в таблице символов. Таблица символов для нашего примера приведена в табл. 2.2. Адреса символов заполняются после первого прохода, когда адреса меток уже известны.

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

98

Адреса глобальных переменных и меток берутся из табл. 2.2 символов.

Код на машинном языке и таблица символов сохраняются в объектном файле.

 

 

Таблица 2.2

 

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

Символ

 

Адрес

f

 

0x10000000

g

 

0x10000004

y

 

0x10000008

main

 

0x00400000

sum

 

0x0040002C

Этап 3: Компоновка Большие программы обычно содержат много файлов.

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

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

В нашем примере только один объектный файл, поэтому никакого перемещения не требуется. На рис. 2.23 показан полученный исполняемый файл.

99

Рис.2.23. Исполняемый файл

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

На рисунке рядом с машинным кодом показаны команды в виде, удобном для восприятия и интерпретации человеком. Исполняемый файл содержит только машинные команды. Сегмент данных задает адреса всех глобальных переменных. Доступ к глобальным переменным осуществляется при помощи базовой адресации относительно адреса, определяемого глобальным указателем $gp.

Например, первая команда sw $a0, 0x8000 ($gp) присваивает значение 2 глобальной переменной f, которая размещена в памяти по адресу 0x10000000. Помните, что смещение 0x8000

– это 16-битное число со знаком, которое после знакового расширения до 32 битов прибавляется к базовому адресу, находящемуся в регистре $gp. Таким образом, $gp + 0x8000 =

100

Источник: https://studfile.net/preview/16568788/