Сборка программ
|
Он был ловкий и весь такой собранный джентльмен,
и одет — в самые лучшие и дорогие одежды; и все у него было подобрано
и пригнано, даже части тела.
А. Тутуола |
В предыдущем разделе шла речь о типах исполняемых модулей,
но не говорилось ни слова о том, каким образом эти модули получаются.
Вообще говоря, способ создания загружаемого модуля различен в различных
ОС, но в настоящее время во всех широко распространенных системах этот
процесс выглядит примерно одинаково. Это связано, прежде всего, с тем,
что эти системы используют одни и те же языки программирования и правила
межмодульного взаимодействия, в которых явно или неявно определяют логику
раздельной компиляции и сборки.
В большинстве современных языков программирования программа состоит из
отдельных слабо связанных модулей. Как правило, каждому такому модулю
соответствует отдельный файл исходного текста. Эти файлы независимо обрабатываются
языковым процессором (компилятором), и для каждого из них генерируется
отдельный файл, называемый объектным модулем.
Затем запускается программа,- называемая редактором
связей, компоновщиком или линкером (linker
— тот, кто связывает), которая формирует из заданных объектных модулей
цельную программу.
Объектный модуль отчасти похож по структуре на перемещаемый загрузочный
модуль. Дело в том, что сборку программы из нескольких модулей можно уподобить
загрузке в память нескольких программ. При этом возникает та же задача
перенастройки адресных ссылок, что и при загрузке относительного загрузочного
файла (рис. 3.8). Поэтому объектный модуль должен в той или иной форме
содержать таблицу перемещений. Можно, конечно, потребовать, чтобы весь
модуль был позиционно-независимым, но это, как говорилось выше, накладывает
очень жесткие ограничения на стиль программирования, а на многих процессорах
(например Intel 8085) просто невозможно.
Кроме ссылок на собственные метки, объектный модуль имеет право ссылаться
на символы, определенные в других модулях. Типичный пример такой ссылки
— обращение к функции, которая определена в другом файле исходного текста
(рис. 3.9 и 3.10).
Рис. 3.8. Сборка программы
Для разрешения внешних ссылок мы должны создать две
таблицы: в одной перечислены внешние объекты, на которые ссылается модуль,
в другой — объекты, определенные внутри модуля, на которые можно ссылаться
извне. Обычно с каждым таким объектом ассоциировано имя, называемое глобальным
символом. Как правило, это имя совпадает с именем соответствующей
Функции или переменной в исходном языке.
Для каждой ссылки на внешний символ мы должны уметь определить, является
эта ссылка абсолютной или относительной, либо это вообще должна быть разность
или сумма двух или даже более адресов, и т. д. Для определения объекта,
с другой стороны, мы должны уметь указать, что это абсолютный Или перемещаемый
символ, либо что он равен другому символу плюс заданное смещение, и т.
д.
Рис. 3.9. Разрешение внешних ссылок (объектный модуль)
Рис. 3.10. Разрешение внешних ссылок (собранная программа)
Кроме того, в объектных файлах может содержаться отладочная информация,
формат которой может быть очень сложным. Следовательно, объектный файл
представляет собой довольно сложную и рыхлую структуру. Размер собранной
программы может оказаться в два или три раза меньше суммы длин объектных
модулей.
Типичный объектный модуль содержит следующие структуры данных.
- Таблицу перемещений, т. е. таблицу ссылок на перемещаемые
объекты внутри модуля.
- Таблицу ссылок на внешние объекты. Иногда это называется
таблицей или списком импорта.
- Таблицу объектов, определенных в этом модуле, на которые
можно ссылаться из других модулей. В некоторых случаях ее называют списком
экспорта. Иногда таблицы экспорта и импорта объединяют и называют
все это таблицей глобальных символов. В
этом случае для каждого символа приходится указывать, определен он в
данном модуле или нет, а если определен, то как.
- Различную служебную информацию, такую, как имя модуля,
программу, которая его создала (например, строка "gcc compiled").
- Отладочную информацию.
- Собственно код и данные модуля.
Как правило, код и данные разбиты на именованные секции. В masm/tasm
(MASM — Microsoft Assembler, Tasm — Turbo Assembler) такие секции называются
сегментами, в DЕС'овских и UNIX'oвых ассемблерах — программными
секциями (psect). В готовой программе весь код или данные, описанный
в разных модулях, но принадлежащий к одной секции, собирается вместе.
Например, в системах семейства Unix программы, написанные на языке С,
состоят из минимум трех программных секций:
- .text — исполняемый код (современные
компиляторы иногда помещают в эту секцию и данные, описанные как const);
- .data — статически инициализированные
данные;
- .bss — неинициализированные
данные.
В качестве упражнения читателю предлагается найти эти секции в примере
3.7.
Некоторые форматы объектных модулей, в частности ELF (Executable and Linking
Format — формат исполняемых и собираемых [модулей], используемый современными
системами семейства Unix), предоставляют особый тип глобального символа
— слабый (weak) символ (пример 3.8). При сборке
программы компоновщик не выдает сообщения об ошибке, если обнаруживает
Два различных определения такого символа, при условии, что одно из определений
является слабым — таким образом, слабый символ может быть легко переопределен
при необходимости. Особенно полезен этот тип при помещении объектного
модуля в библиотеку.
Пример 3.8. Структуры данных объектного модуля
ELF (цитируется по elf.h из поставки Linux 2.2.16, перевод комментариев
автора)
' Заголовок файла ELF. Находится в начале каждого
файла ELF. */
#define El NIDENT (16)
typedef struct
unsigned char e_ident[EI_NIDENT]; /* Магическое число и другая информация
*/
Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr
e_entry; Elf32_0ff e_phoff;
Elf32_0ff e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32__Half
e_phentsize;
Elf32_Half e_phnum; /* Elf32_Half e_shentsize; Elf32_Half e_shnum; /*
Elf32_Half e^shstrndx; ков секций */
} Elf32 Ehdr;
/* Тип объектного файла */
/* Архитектура */
/* Версия объектного файла */
/* Виртуальный адрес точки входа */
/* Смещение таблицы заголовка программы */
/* в файле */
/* Смещение таблицы заголовков секций в файле */
/* Процессорно-зависимые флаги */
/* Размер заголовка ELF в байтах */
/* Размер элемента */
/* таблицы заголовка программы */
Счетчик элементов таблицы заголовка программы */
/* Размер элемента таблицы заголовков секций */
Счетчик элементов таблицы заголовков программ */
/* Индекс таблицы имен секций в таблице заголов-
/* Поля в массиве e_indent. Макросы Е1_* суть индексы в этом массиве.
Макросы, следующие за ка'ждым определением Е1_*, суть значения, которые
соответствующий байт может принимать. */
#define EI_MAGO 0 /* Индекс нулевого байта сигнатуры1 */
fdefine ELFMAGO 0x7f /* Значение нулевого байта сигнатуры */
#define EI_MAG1 I /* Индекс первого байта сигнатуры */
#define ELFMAG1 'Е' /* Значение первого байта сигнатуры */
fdefine EI_MAG2 2 /* Индекс второго байта сигнатуры */
#define ELFMAG2 'L' /* Значение второго байта сигнатуры */
#define EI_MAG3 3 /* Индекс третьего байта сигнатуры */
#define ELFMAG3 'F' /* Значение третьего байта сигнатуры */
1 В данном случае — это "магическое число", код, размещаемый
в определенном месте (обычно в начале) файла и подтверждающий, что это
файл данного формата.
/* объединение идентификационных байтов, для сравнения по словам */ #define
ELFMAG "\177ELF" ((define SELFMAG 4
((define EI_CLASS 4 /* Индекс байта, указывающего класс файла */
((define ELFCLASSNONE 0 /* Не определено */
((define ELFCLASS32 1 /* 32-разрядные объекты */
((define ELFCLASS64 2 /* 64-разрядные объекты */ ((define ELFCLASSNUM
3
tdefine EI_DATA 5 /* Индекс байта кодировки данных */
((define ELFDATANONE 0 /* Не определена кодировка данных */
((define ELFDATA2LSB 1 /* Двоичные дополнительные, младший байт первый
*/
#define ELFDATA2MSB 2 /* Двоичные дополнительные, старший байт первый
*/
tdefine ELFDATANUM 3
#define EI_VERSION 6 /* Индекс байта версии файла */ /* Значение должно
быть EV__CURRENT */
#define EIJDSABI 7 /*. идентификатор OS ABI */
tdefine ELFOSABI_SYSV 0 /* UNIX System V ABI */
«define ELFOSABI_HPUX 1 /* HP-UX */
tdefine ELFOSABI_ARM 97 /* ARM */
#define ELFOSABI_STANDALONE 255 /* Самостоятельное (встраиваемое) приложение
* /
#define EI_ABIVERSION 8 /* версия ABI */
#define EI_PAD 9 /* Индекс байтов выравнивания */
/* Допустимые значения для e_type (тип объектного файла). */
#define ETJTONE 0 /* Не указан тип */
#define ET_REL I /* Перемещаемый файл */
#define ET_EXEC 2 /* Исполнимый файл */
#define ET_DYN 3 /* Разделяемьй объектньй файл */
Define ET_CORE 4 /* Образ задачи */
'define ET_NUM 5 /* Количество определенных типов */
e ET_LOPROC OxffOO /* Специфичный для процессора */
ttdefine ET_HIPROC Oxffff /* Специфичный для процессора */ /* Допустимые
значения для e_machine (архитектура). */
ttdefine EM_NONE 0 /* Не указана машина */
ttdefine ЕМ_М32 1 /* AT&T WE 32100 */
ttdefine EM_SPARC 2 /* SUN SPARC */
ttdefine EM_386 3 /* Intel 80386 */
ttdefine EM_68K 4 /* Motorola m68k family */
ttdefine EM_88K 5 /* Motorola m88k family */
ttdefine EM__486 6 /* Intel 80486 */
ttdefine EM_860 7 /* Intel 80860 */
ttdefine EM_MIPS 8 /* MIPS R3000 big-endian */
ttdefine EM_S370 9 /* Amdahl */
ttdefine EM_MIPS_RS4_BE 10 /* MIPS R4000 big-endian */
ttdefine EM RS6000 11 /* RS6000 */
#define EM_PARISC 15 ttdefine EM_nCUBE 16 Idefine EM VPP500 17
/* HPPA */
/* nCUBE */
/* Fujitsu VPP500 */
ttdefine EM_SPARC32PLUS 18 /* Sun's "vSplus" */ ttdefine EM_960
19 /* Intel 80960 */ ttdefine EM PPC 20 /*. PowerPC */
ttdefine ttdefine ttdefine ttdefine ttdefine ttdefine ttdefine ttdefine
ttdefine ttdefine ttdefine ttdefine ttdefine ttdefine ttdefine
EM_V800 36 /* NEC V800 series */ EM_FR20 37 /* Fujitsu FR20 */ EM_RH32
38 /* TRW RH32 */ EM_MMA 39 /* Fujitsu MMA */ EM^ARM 40 /* ARM */ EM_FAKE_ALPHA
41 /* Digital Alpha J
EM_SH 42 EM_SPARCV9 43 EMJTRICORE 44 EM_ARC 45 EM_H8_300 46 EM_H8_300H
47 EM_H8S 48 EM_H8_500 49 EM IA 64 50
/* Hitachi SH */ /* SPARC v9 64-bit */ /* Siemens Tricore */ /* Argonaut
RISC Core */ /* Hitachi H8/300 */ /* Hitachi H8/300H */ /* Hitachi H8S
*/ /* Hitachi H8/500 */ /* Intel Merced */
«define EM_MIPS_X 51 /*
Stanford MIPS-X */
•define EM^COLDFIRE 52 /* Motorola Coldfire */
«define EM_68HC12 53 /* Motorola M68HC12 */ ((define EM_NUM 54
/* Если необходимо вьщелить неофициальное значение для ЕМ_*, пожалуйста,
выделяйте большие случайные числа (0x8523, Oxa7f2, etc.), чтобы уменьшить
вероятность пересечения с официальными или не-GNU неофициальными значениями.
*/
((define EM_ALPHA 0x9026
/* Допустимые значения для e_version (версия). */
Idefine EV_NONE 0 /* Недопустимая версия ELF */ #define EV_CURRENT I /*
Текущая версия */ (tdefine EV_NUM 2
/* Элемент таблицы символов. */
typedef struct f
Elf32_Word st_name; /* Имя символа (индекс в таблице строк) */
Elf32_Addr st_value; /* Значение символа */
Elf32_Word st_size; /* Размер символа */
unsigned char st_info; /* Тип и привязка символа */
unsigned char st_other; /* Значение не определено, 0 */
Elf32_Section st_shndx; /* Индекс секции */ ) Elf32_Sym;
'* Секция syminfo, если присутствует, содержит дополнительную информацию
о каждом динамическом символе. */
typedef struct I
Elf32_Half si_boundto;/* Прямая привязка, символ, к которому привязан
*/ Elf32_Half si_flags; /* Флаги символа */
> Elf32 Syminfo;
/*
Допустимые значения для si boundto. */
#define SYMINFO_BT_SELF Oxffff /* tdefine SYMINFO_BT_PARENT Oxfffe /*
ttdefine SYMINFO_BT_LOWRESERVE OxffOO /*
/* Возможные битовые маски для si_flags #define SYMINFO_FLG_DIRECT 0x0001
/*
tfdefine SYMINFO_FLG_PASSTHRU 0x0002 /* тора */
tfdefine SYMINFO_FLG_COPY 0x0004 /* tdefine SYMINFO_FLG_LAZYLOAD 0x0008
/*
/* Значения версии Syminfo. */ #define SYMINFO_NONE 0 ttdefine SYMINFO_CURRENT
1 #define SYMINFO NUM 2
Символ привязан к себе */ Символ привязан к родителю */ Начало зарезервированных
записей */
Прямо привязываемый символ *•/ Промежуточный символ для трансля-
Символ предназначен для перемещения копированием */ Символ привязан к
объекту с отложенной загрузкой */
/* Как извлекать информацию из и включать ее в поле st_info. */
•
#define ELF32_ST_BIND(val) (((unsigned char) (val)) » 4) tfdefine ELF32_ST_TYPE(val)
((val) & Oxf)
ttdefine ELF32_ST_INFO(bind, type) (((bind) « 4) + ((type) & Oxf))
/* Допустимые значения для подполя STJ3IND поля st_info (привязка символов).
*/
#define STB_LOCAL О ttdefine STB_GLOBAL 1 tdefine STB_WEAK 2 ttdefine
STB_NUM 3 #define STB_LOOS 10 #define STB HIOS 12
/* Локальный символ */
/* Глобальный символ */
/* Слабый символ */
/* Кол-во определенных типов. */
/* Начало ОС-зависимых значений */ _ /* Конец ОС-зависимых значений */
#define STB_LOPROC 13 /* Начало процессорно-зависимых значений */ tdefine
STB_HIPROC 15 /* Конец процессорно-зависимых значений */
/* Допустимые значения для подполя ST TYPE поля st info (тип символа).
*/
#define STT_NOTYPE 0 #define STT_OBJECT 1 #define STT FUNC 2
He указан */
Символ — объект данных */
Символ — объект кода */
STT_SECTION 3 /* Символ связан с секцией */
4define STT_FILS 4 /* Имя символа — имя файла */
*define ^тт NUM ^ /* Кол-во определенных типов */
»define STT LOOS 1^ /* Начало ОС-зависимых значений */
«define STT_HIOS 12 /* Конец ОС-зависимых значений */
*define STT_LOPROC 13 /* Начало процессорно-зависимых значений */
«define STT_HIPROC 15 /* Конец процессорно-зависимых значений */
/* Индексы таблицы символов размещены в группах и цепочках хэша в секции
кэш-таблицы символов. Это специальное значение индекса указывает на конец
цепочки, и означает, что в этой группе более нет символов. */
((define STNJJNDEF 0 /* Конец таблицы. */
/* Элемент таблицы перемещений без добавочного значения (в секциях типа
SHT_REL). */
typedef struct (
Elf32_Addr r_offset; /* Адрес */
Elf32_Word r_info; /* Тип перемещения и индекс символа */ } Elf32_Rel;
/* Элемент таблицы перемещений с добавочным значением (в секциях типа
SHT_RELA). */
typedef struct (
Elf32_Addr r_offset; /* Адрес */
Elf32_Word r_info; /* Тип перемещения и индекс символа */
Elf32_Sword r_addend; /* Добавочное значение */ ) Elf32_Rela;
'•* Как извлекать информацию из и включать ее в поле r_info. */
#define ELF32_R_SYM(val) ((val) » 8)
Define ELF32_R_TYPE(val) ((val) & Oxff)
#define ELF32_R_INFO(sym, type) (((sym) « 8) + ((type) & Oxff))
/* Типы перемещений для 1386 (формулы взяты из
[docs.sun.com 816-0559-10] - авт.)
А — добавочное значение, используемое при вычислении значения перемещаемого
поля.
В — базовый адрес, начиная с которого разделяемый объект загружается в
память при исполнении [программы]. Обычно разделяемый объект строится
с базовым виртуальным адресом, равным О, но адрес при исполнении иной.
G — смещение записи в глобальной таблице смещений, где адрес перемещаемого
символа находится во время исполнения. GOT — адрес глобальной таблицы
смещений.
L — местоположение (смещение в секции или адрес) записи символа в процедурной
таблице связывания (PLT). PLT перенаправляет вызов функции по настоящему
адресу. Редактор связей создает начальную таблицу, а редактор связей времени
исполнения модифицирует записи во время исполнения.
Р — местоположение (смещение в секции или адрес) перемещаемого элемента
памяти (вычисляется с использованием r_offset).
S — значение символа, индекс которого находится в" элементе таблицы
перемещений. */
#define R_386_NONE 0 /* Не перемещать */
ttdefine R__386_32 I /* Прямое 32-разрядное - S + А */
#define R_386_PC32 2 /* 32-разрядное относительно PC-S+A-PV
^define R_386_GOT32 3 /* 32-разрядный элемент GOT - G + А */
#define R_386_PLT32 4 /* 32-разрядный адрес PLT - L + А - Р */
ttdefine R_386_COPY 5 /* Копировать символ при исполнении */
#define R_386_GLOB_DAT 6 Л Создать запись GOT - S*/
#define R_386_JMP_SLOT 7 /* Создать запись PLT - S */
tfdefine R_386_RELATIVE 8 /* Сдвинуть относительно базы программы -
В + А */
tdefine R_386_GOTOFF 9 /* 32-разрядное смещение GOT - S + А - GOT */ ^define
R_386_GOTPC 10 /*' 32-разрядное смещение GOT относительно
PC - S + А - GOT */ /* Должна быть последняя запись. */ #define R 386
NUM 11
|