Относительная загрузка

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


Рис. 3.3. Перемещение кода, использующего абсолютную адресацию

Рис. З.4. Перемещение кода, самостоятельно перезагружающего базовые регистры

Сложность здесь в том, что если абсолютные адресные поля можно найти анализом кодов команд (деассемблированием), то значение в адресный регистр может загружаться задолго до собственно адресации, причем, как мы видели в примерах кода для процессора SPARC, Формирование значения регистра может происходить и по частям. Без помощи программиста или компилятора (в этой главе мы не будем различать написанный на ассемблере или компилированный код, а того, кто генерировал код, будем называть программистом) решить вопрос о том, какая из команд загружает в регистр скалярное значение, а какая -будущий адрес или часть адреса, невозможно. Та же проблема возникает в случае, если мы используем в качестве указателя ячейку статически инициализованных данных (пример 3.3).

Пример 3.3. Примеры статически инициализованных указателей в С

int buf[20], *bufptr=buf;
char * message="No message defined yet\n";
void do_nothing_hook(int);
void (*hook)(int)=do_nothing_hook;

Довольно легко построить и пример кода, в котором адресация происходит вообще без явного использования каких-либо регистров, во всяком случае, без загрузки в них значений (пример 3.4).

Пример 3.4. Реализация косвенного перехода по адресу dst_seg:dst_offs

push dst seg ; Это и будет ссылкой на абсолютный адрес
push dst_offs
retf

На практике содействие программиста загрузчику состоит в том, что программист старается без необходимости не использовать в адресных полях и в качестве значений адресных регистров произвольные значения (необходимость в этом может возникать при адресации системных структур данных или внешних устройств, расположенных по фиксированным адресам). Вместо этого, программист применяет ассемблерные символы, соответствующие адресам.
Ассемблер при каждой ссылке на такой символ генерирует не только "заготовку" адреса в коде, но и запись в таблице перемещений (relocation table). Эта запись хранит место ссылки на такой символ в коде или данных. Если в ссылке используется только часть адреса, как в командах sethi %10, %hi(addr) процессора SPARC, или move ax, segment addr Процессора 8086, мы запоминаем и этот факт.
В качестве "заготовки" адреса обычно используется смешение адресуемого объекта от начала программы. При настройке программы на реальный адрес загрузки нам, таким образом, необходимо пройти по всем объектам, перечисленным в таблице перемещений, и переместить каждую из ссылок — сформировать из заготовки адрес.
Файл, содержащий таблицу перемещений, гораздо сложнее абсолютного загружаемого модуля и носит название относительного или перемещаемого загрузочного модуля. Именно такой формат имеют ехе-файлы в системе MS DOS (пример 3.5).

Пример 3.5. Заголовок ЕХЕ-файла MS DOS. Цитируется по WINT.H из поставки | ; MS Visual C++ v6.0 (перевод комментариев автора)

#define IMAGE_DOS_SIGNATURE Ox4D5A // MZ
typedef struct _IMAGE_DOS_HEADER { // Заголовок DOS .EXE
WORD e_magic; // Магическое число (сигнатура)
WORD e_cblp; // Длина последней страницы файла в байтах
WORD e_cp; // Количество страниц в файле
WORD e_crlc; // Количество перемещений
WORD e_cparhdr; // Размер заголовка в параграфах
WORD ejrainalloc; // Минимальное количество дополнительных параграфов
ORD e_maxalloc; // Максимальное количество дополнительных параграфов
WORD e ss; // Начальное (относительное) значение SS
WORD e_sp; // Начальное значение SP
WORD e_csum; // Контрольная сумма
WORD e_ip; // Начальное значение IP
WORD e_cs; // Начальное (относительное) значение CS
WORD e_lfarlc;// Адрес таблицы перемещений в файле
WORD e_ovno;// Номер перекрытия
WORD e_res[4];// Зарезервировано
WORD e_oemid; // OEM идентификатор (для e_oeminfo)
WORD e_oeminfo;// Информация OEM; специфично для e_oemid
WORD e_res2[10];// Зарезервировано
LONG e_lfanew; }// Адрес следующего заголовка в файле
}IMAGE DOS HEADER, РIMAGE DOS HEADER;

Наиболее поучительна в этом отношении система RT-11, в которой существуют загружаемые модули обоих типов. Обычные программы имеют расширение sav, представляют собой абсолютные загружаемые модули и грузятся всегда с адреса 01000. Ниже этого магического адреса находятся векторы прерываний и стек программы. Сама операционная система вместе с драйверами размещается в верхних адресах памяти. Естественно, вы не можете загрузить одновременно два sav-файла.
Однако, если вам обязательно нужно исполнять одновременно две программы, вы можете собрать вторую из них в виде относительного модуля: файла с расширением rel. Такая программа будет загружаться в верхние адреса памяти, каждый раз разные, в зависимости от конфигурации ядра системы, количества загруженных драйверов устройств и других rel-модулей (рис. 3.5).

Рис. 3.5. Распределение памяти в RT-11 с одним загруженным sav-файлом и двумя rel-файлами