У исполняемого файла, применяемого при динамической компоновке, должен быть один элемент PT_INTERP заголовка программы. Во время выполнения exec (базовой операционной системой) система получает значение пути из сегмента PT_INTERP и создает начальный образ процесса из сегментов файла интерпретатора. Таким образом, вместо использования исходного образа сегментов исполняемого файла, система составляет в памяти образ для интерпретатора. Затем интерпретатор получает от системы управление и создает среду для выполнения прикладной программы.
Как указано в разделе Инициализация процесса в Главе 32 документации по процессору, интерпретатор получает управление двумя способами. Во-первых, он может получить дескриптор исполняемого файла, установленный на его начало. Затем с помощью этого дескриптора интерпретатор считывает сегменты файла в память. Во втором способе, в зависимости от формата исполняемого файла, система загружает исполняемый файл в память, не передавая интерпретатору его дескриптор. В большинстве случаев начальное состояние процесса интерпретатора соответствует данным, полученным исполняемым файлом. Для самого интерпретатора второй интерпретатор может не потребоваться. Интерпретатор может находиться как в общем объекте, так и в исполняемом файле.
При создании исполняемого файла с динамической компоновкой компоновщик добавляет к нему элемент заголовка программы типа PT_INTERP. Этот элемент указывает, что в качестве интерпретатора программы нужно вызвать динамический компоновщик.
Примечание: Расположение системного динамического компоновщика зависит от процессора.
Exec (базовая операционная система) и динамический компоновщик совместно создают образ процесса для программы. При этом выполняются следующие действия:
Компоновщик также создает различные данные, помогающие динамическому компоновщику при работе с исполняемыми и общими файлами. Как показано в Заголовок программы, эти данные находятся в загружаемых сегментах, доступных во время выполнения. (Содержимое сегментов зависит от процессора. Более подробная информация приведена в документации по процессору.)
Поскольку совместимая с ABI программа импортирует основные системные службы из библиотеки общих объектов [см. раздел Системная библиотека в Главе 32], динамический компоновщик принимает участие в выполнении каждой программы, совместимой с ABI.
Как описано в разделе Загрузка программы в документации по процессору, общие объекты могут занимать адреса виртуальной памяти, отличающиеся от адресов, указанных в таблице заголовков программы. Динамический компоновщик перемещает образ в памяти, обновляя абсолютные адреса перед передачей управления программе. Хотя абсолютные адреса будут гарантированно правильными, если библиотека будет загружена по адресам, указанным в таблице заголовков программы, обычно этого не происходит.
Если среда процесса [см. exec (базовая операционная система)] содержит переменную LD_BIND_NOW с ненулевым значением, динамический компоновщик выполняет все необходимые перемещения перед передачей управления программе. Например, такое поведение будет вызвано следующими переменными:
В противном случае, LD_BIND_NOW либо не указано в файле, либо имеет нулевое значение. Динамический компоновщик может выполнять компоновку процедур отложенно и не выполнять преобразование имен и перемещение сегментов для процедур, которые не вызываются. Более подробные сведения приведены в разделе Таблица компоновки процедур этой главы.
Если объектный файл используется при динамической компоновке, в таблице заголовков программы будет присутствовать элемент типа PT_DYNAMIC. Этот элемент содержит раздел .dynamic. Этот раздел помечен специальным именем _DYNAMIC и содержит массив следующих структур:
Динамические структуры
typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; } d_un; } Elf32_Dyn; extern Elf32_Dyn _DYNAMIC[]; typedef struct { Elf64_Sxword d_tag; union { Elf64_Xword d_val; Elf64_Addr d_ptr; } d_un; } Elf64_Dyn; extern Elf64_Dyn _DYNAMIC[];
Для каждого объекта этого типа d_tag управляет интерпретацией d_un.
В целях совместимости в файлах не применяются записи перемещения, исправляющие адреса в динамических структурах.
Для того чтобы упростить интерпретацию содержимого записей динамического раздела, значение каждого тега, за исключением содержащихся в двух специальных разделах, определяет способ интерпретации объединения d_un. Теги с четными значениями указывают на запись динамического раздела, использующую d_ptr. Теги с нечетными значениями указывают на запись динамического раздела, использующую d_val или не использующую ни d_ptr, ни d_val. Эти правила не относятся к тегам, значения которых меньше специального значения DT_ENCODING, и тегам, значения которых находятся между DT_HIOS и DT_LOPROC.
В следующей таблице описаны требования к тегам в исполняемых и общих объектных файлах. Если тег помечен как обязательный, массив динамической компоновки для файлов, совместимых с ABI, должен иметь запись этого типа. В противном случае, запись для данного тега может отсутствовать.
Теги динамических массивов, d_tag
Имя | Значение | d_un | Исполняемый | Общий объектный |
---|---|---|---|---|
DT_NULL | 0 | игнорируется | обязательный | обязательный |
DT_NEEDED | 1 | d_val | необязательный | необязательный |
DT_PLTRELSZ | 2 | d_val | необязательный | необязательный |
DT_PLTGOT | 3 | d_ptr | необязательный | необязательный |
DT_HASH | 4 | d_ptr | обязательный | обязательный |
DT_STRTAB | 5 | d_ptr | обязательный | обязательный |
DT_SYMTAB | 6 | d_ptr | обязательный | обязательный |
DT_RELA | 7 | d_ptr | обязательный | необязательный |
DT_RELASZ | 8 | d_val | обязательный | необязательный |
DT_RELAENT | 9 | d_val | обязательный | необязательный |
DT_STRSZ | 10 | d_val | обязательный | обязательный |
DT_SYMENT | 11 | d_val | обязательный | обязательный |
DT_INIT | 12 | d_ptr | необязательный | необязательный |
DT_FINI | 13 | d_ptr | необязательный | необязательный |
DT_SONAME | 14 | d_val | игнорируется | необязательный |
DT_RPATH* | 15 | d_val | необязательный | игнорируется |
DT_SYMBOLIC* | 16 | игнорируется | игнорируется | необязательный |
DT_REL | 17 | d_ptr | обязательный | необязательный |
DT_RELSZ | 18 | d_val | обязательный | необязательный |
DT_RELENT | 19 | d_val | обязательный | необязательный |
DT_PLTREL | 20 | d_val | необязательный | необязательный |
DT_DEBUG | 21 | d_ptr | необязательный | игнорируется |
DT_TEXTREL* | 22 | игнорируется | необязательный | необязательный |
DT_JMPREL | 23 | d_ptr | необязательный | необязательный |
DT_BIND_NOW* | 24 | игнорируется | необязательный | необязательный |
DT_INIT_ARRAY | 25 | d_ptr | необязательный | необязательный |
DT_FINI_ARRAY | 26 | d_ptr | необязательный | необязательный |
DT_INIT_ARRAYSZ | 27 | d_val | необязательный | необязательный |
DT_FINI_ARRAYSZ | 28 | d_val | необязательный | необязательный |
DT_RUNPATH | 29 | d_val | необязательный | необязательный |
DT_FLAGS | 30 | d_val | необязательный | необязательный |
DT_ENCODING | 32 | не указан | не указан | не указан |
DT_PREINIT_ARRAY | 32 | d_ptr | необязательный | игнорируется |
DT_PREINIT_ARRAYSZ | 33 | d_val | необязательный | игнорируется |
DT_LOOS | 0x6000000D | не указан | не указан | не указан |
DT_HIOS | 0x6ffff000 | не указан | не указан | не указан |
DT_LOPROC | 0x70000000 | не указан | не указан | не указан |
DT_HIPROC | 0x7fffffff | не указан | не указан | не указан |
* Указывает запись уровня 2.
За исключением элемента DT_NULL в конце массива и относительного порядка элементов DT_NEEDED, порядок следования записей не регламентируется. Значения тегов, не показанные в таблице, зарезервированы.
Имя | Значение |
---|---|
DF_ORIGIN | 0x1 |
DF_SYMBOLIC | 0x2 |
DF_TEXTREL | 0x4 |
DF_BIND_NOW | 0x8 |
Когда компоновщик обрабатывает архивную библиотеку, он извлекает элементы библиотеки и копирует их в выходной объектный файл. Эти статические функции могут применяться во время выполнения и не требуют обращения к динамическому компоновщику. Кроме того, функции содержатся в общих объектах, и для их вызова динамический компоновщик должен подключить соответствующий общий объектный файл к образу процесса.
Когда динамический компоновщик создает сегменты памяти для объектного файла, система определяет на основании зависимостей (в записях DT_NEEDED динамической структуры), какие общие объекты нужны для реализации функций программы. Последовательно подключая все зависимые объекты, компоновщик создает полный образ процесса. Во время преобразования ссылок на имена динамический компоновщик просматривает таблицы имен методом поиска "в ширину". Сначала просматривается таблица имен самой исполняемой программы, затем - таблицы имен записей DT_NEEDED (в порядке их следования), затем записи DT_NEEDED второго уровня и т.п. У процесса должны быть права доступа на чтение общих объектов, другие права не требуются.
Примечание: Даже если на общий объект встречается несколько ссылок, динамический компоновщик включит в процесс только одну копию.
Имена в списке зависимостей - это копии либо строк DT_SONAME, либо путей к общим объектам, применявшимся при создании объектного файла. Например, если компоновщик создает исполняемый файл с помощью одного общего объекта, имеющего запись DT_SONAME lib1, и другого общего объекта с именем /usr/lib/lib2, то в списке зависимостей исполняемого файла будут указаны записи lib1 и /usr/lib/lib2.
Если в имени пути есть символы косой черты (/), например /usr/lib/lib2 или directory/file, то динамический компоновщик принимает эту строку за полное имя файла. Если в имени нет символа косой черты, например, lib1, то полный путь определяется на основе трех факторов.
Набор каталогов, заданный записью DT_RUNPATH, применяется только для поиска объектов, зависимых напрямую от исполняемого или общего объекта, содержащего запись DT_RUNPATH. Таким образом, он применяется только для поиска зависимостей, непосредственно указанных в записях DT_NEEDED динамической структуры, содержащей запись DT_RUNPATH. Запись DT_RUNPATH не влияет на поиск других зависимостей объекта.
Следующие значения эквивалентны значениям, показанным в предыдущем примере:
Хотя некоторые программы (например, редактор компоновки) обрабатывают списки до и после точки с запятой по-разному, динамический компоновщик не делает различия между такими списками. Тем не менее, динамический компоновщик распознает показанный выше формат, в котором разделителем является точка с запятой.
Все каталоги LD_LIBRARY_PATH просматриваются перед каталогами из DT_RUNPATH.
Если при поиске общих объектов будет найден файл ELF с неверными атрибутами, это не считается ошибкой. Для того чтобы убедиться, что нужного объекта не существует, динамический компоновщик просматривает все каталоги. При этом просматриваются атрибуты, хранящиеся в следующих полях заголовка ELF:
e_ident[EI_DATA], e_ident[EI_CLASS] , e_ident[EI_OSABI], e_ident[EI_ABIVERSION] , e_machine, e_type, e_flags и e_version.
Примечание: В целях безопасности динамический компоновщик игнорирует программы из LD_LIBRARY_PATH с флагами setuid и setgid. Однако он просматривает каталоги DT_RUNPATH и каталоги по умолчанию. Это ограничение также относится к процессам, которым предоставлено больше прав доступа, чем минимально требуется в системах с расширенной защитой.
Примечание: Четвертый объект для поиска, тег динамического массива DT_RPATH, перемещен на уровень 2 ABI. В этом теге хранится список разделенных двоеточиями каталогов для поиска. Каталоги, указанные в DT_RPATH, просматриваются перед каталогами из LD_LIBRARY_PATH.
Если в динамическом массиве одного объекта есть как DT_RPATH, так и DT_RUNPATH , то динамический компоновщик обрабатывает только запись DT_RUNPATH.
В строках, которые заданы тегами DT_NEEDED и DT_RUNPATH и содержат списки имен каталогов, передаваемых в качестве параметров процедуре dlopen(), последовательность подстановки начинается с символа ($). Эта последовательность состоит из знака доллара, за которым следует либо имя, либо имя, заключенное в фигурные скобки ({) и (}). Имя - это последовательность байтов, начинающаяся с буквы английского алфавита или символа подчеркивания, за которым могут следовать другие буквы, цифры и символы подчеркивания. Если за символом доллара не указано обычное или заключенное в скобки имя, то поведение динамического компоновщика будет непредсказуемо.
Если указано имя ORIGIN, то последовательность подстановки заменяется динамическим компоновщиком на полное имя каталога, в котором находится объект, содержащий последовательность подстановки. В полном имени каталога не применяются символьные связи и знаки . или .. (точка и две точки). Если имя не равно (ORIGIN), поведение динамического компоновщика будет непредсказуемо.
При загрузке объекта с переменной $ORIGIN динамический компоновщик должен вычислить имя каталога, в котором находится объект. Так как такие вычисления могут быть достаточно сложными, в конкретной реализации динамического компоновщика значение $ORIGIN может не вычисляться для объектов, где эта переменная не используется. Если объект вызывает dlopen() со строкой, содержащей $ORIGIN, и не использует $ORIGIN в записях динамического массива, то динамический компоновщик может не вычислять имя каталога до тех пор, пока явно не встретится вызов dlopen(). Приложение может изменить рабочий каталог перед вызовом dlopen(), поэтому результат вычисления может оказаться неправильным. Для того чтобы избежать этой ошибки, объект может заранее объявить свое намерение использовать $ORIGIN, указав флаг DF_ORIGIN. Динамический компоновщик может отклонить попытку использования $ORIGIN в вызове dlopen() для объекта, который не установил флаг DF_ORIGIN и не использовал $ORIGIN в динамическом массиве.
Примечание: В целях безопасности динамический компоновщик не позволяет использовать $ORIGIN в программах с флагами setuid и setgid. Для последовательностей, указанных в записях DT_RUNPATH динамического массива, список каталогов для поиска, содержащий последовательность $ORIGIN, игнорируется (остальные списки в той же строке обрабатываются обычным образом). Последовательности $ORIGIN в записях DT_NEEDED или каталоги, переданные в качестве параметра функции dlopen(), рассматриваются как ошибочные. Это ограничение также относится к процессам, которым предоставлено больше прав доступа, чем минимально требуется в системах с расширенной защитой.
Примечание: В этом разделе приведена информация, зависящая от процессора. Более подробная информация приведена в документации по ABI System V и процессору.
Примечание: В этом разделе приведена информация, зависящая от процессора. Более подробная информация приведена в документации по ABI System V и процессору.
Хэш-таблица объектов Elf32_Word поддерживает доступ к таблице имен. Для 32- и 64-разрядных классов файлов применяется один и тот же формат таблицы. Ниже приведены метки, помогающие понять структуру хэш-таблицы, однако эти метки на являются частью спецификации.
Хэш имен
nbucket |
nchain |
bucket[0]
. . . bucket[nbucket-1] |
chain[0]
. . . chain[nchain-1] |
Массив bucket состоит из записей nbucket, а массив chain - из записей nchain; индексы начинаются с 0. В bucket и chain хранятся индексы таблицы имен.
Записи таблицы chain соответствуют таблице имен. Число записей таблицы имен должно равняться nchain, поэтому индексы таблицы имен также указывают на записи таблицы chain. Хэш-функция (показанная ниже) получает имя и возвращает значение, которое может применяться для вычисления индекса bucket.
Например, если хэш-функция возвращает для какого-либо имени значение x, то bucket[x%nbucket] представляет индекс y как в таблице имен, так и в таблице chain.
Если найдена неверная запись таблицы имен, chain[y] вернет следующую запись таблицы с тем же хэш-значением.
Ссылки chain будут просматриваться до тех пор, пока либо не
будет найдена нужная запись таблицы имен, либо пока не встретится запись
chain со значением STN_UNDEF.
Хэш-функция
unsigned long elf_hash(const unsigned char *name) { unsigned long h = 0, g; while (*name) { h = (h << 4) + *name++; if (g = h & 0xf0000000) h ^= g >> 24; h &= ~g; } return h; } |
После того как динамический компоновщик создаст образ процесса и выполнит все необходимые перемещения, каждому исполняемому или общему файлу предоставляется возможность вызвать функции инициализации. Функции инициализации общих объектов выполняются перед передачей управления исполняемому файлу.
Перед вызовом функций инициализации для объекта A вызываются функции инициализации для всех объектов, от которых зависит объект А. Объект А зависит от объекта В, если В указан в списке необходимых объектов объекта А (в записях DT_NEEDED динамической структуры). Порядок инициализации циклических зависимостей не определен.
Инициализация объектов выполняется при рекурсивном просмотре необходимых объектов каждого объекта. Функции инициализации для объекта вызываются после обработки всех необходимых объектов. Последовательность обработки записей в одном списке необходимых объектов не определена.
Примечание: Каждый процессор может накладывать дополнительные ограничения на алгоритм инициализации. Однако эти ограничения не должны конфликтовать с описанными выше спецификациями.
Ниже приведен пример двух способов просмотра списка NEEDED. В этом примере a.out зависит от b, d и e. b зависит от d и f, а d зависит от e и g. На основании этих зависимостей можно составить схему. Описанный выше алгоритм допускает следующие последовательности инициализации.
Пример последовательности инициализации У общих и исполняемых файлов могут также быть функции завершения, которые вызываются функцией atexit (базовой операционной системы) после того, как базовый процесс начнет последовательность завершения. Функции завершения для объекта А должны быть вызваны перед вызовом функций завершения для объектов, от которых зависит объект А. Объект А зависит от объекта В, если В указан в списке необходимых объектов объекта А (в записях DT_NEEDED динамической структуры). Порядок обработки функций завершения для циклических зависимостей не определен.
У исполняемых файлов также могут быть функции предварительной инициализации. Эти функции вызываются после создания динамическим компоновщиком образа процесса и выполнения всех необходимых перемещений, но до вызова функций инициализации. Функции предварительной инициализации не применяются в общих объектных файлах.
Примечание: В момент выполнения предварительной инициализации системные библиотеки еще могут быть не инициализированы, поэтому некоторые системные функции будут недоступны. В общем случае, код функций предварительной инициализации можно считать переносимым, если в нем не используются системные библиотеки.
Динамический компоновщик выполняет функции предварительной инициализации, инициализации и завершения не более одного раза.
Код инициализации и завершения общих объектов может указываться двумя способами. Во-первых, можно указать адрес выполняемой функции с помощью записей DT_INIT и DT_FINI динамической структуры, описанной в разделе Динамический раздел.
Во-вторых в объектах может быть указан адрес и размер массива указателей на функции. Каждый элемент массива - это указатель на функцию, которую должен выполнить динамический компоновщик. Размер каждого элемента массива определяется программной моделью объекта, содержащего массив. Адрес массива указателей на функции инициализации указан в записи DT_INIT_ARRAY динамической структуры. Аналогично, адрес массива функций предварительной инициализации указан в записи DT_PREINIT_ARRAY, а адрес функций завершения - в записи DT_FINI_ARRAY. Размер каждого массива указан записями DT_INIT_ARRAYSZ, DT_PREINIT_ARRAYSZ и DT_FINI_ARRAYSZ.
Функции, адреса которых указаны в массивах DT_INIT_ARRAY и DT_PREINIT_ARRAY, выполняются динамическим компоновщиком в том же порядке, в котором их адреса следуют в массиве; функции из массива DT_FINI_ARRAY выполняются в обратном порядке.
Если в объекте есть записи DT_INIT и DT_INIT_ARRAY, то функции, указанные в DT_INIT, обрабатываются до функций массива DT_INIT_ARRAY. Если в объекте есть записи DT_FINI и DT_FINI_ARRAY, то функции, указанные в DT_FINI_ARRAY, обрабатываются до функций из массива DT_FINI.
Примечание: Хотя при нормальном завершении процесса должна вызываться функция базовой системы atexit, в случае сбоя процесса это не гарантируется. Процесс не выполняет завершающую обработку, если была вызвана функция _exit или если процесс получил сигнал, который не был обработан или проигнорирован.
В документации по конкретному процессору указано, должен ли динамический компоновщик вызывать функции инициализации исполняемого файла и регистрировать функции завершения с помощью функции atexit. Функции завершения, указанные пользователями с помощью atexit, должны быть вызваны перед функциями завершения общих объектов.