КаталогИндекс раздела
НазадОглавлениеВперед


Редактор связей

СОДЕРЖАНИЕ

1. Основные понятия
    1.1. Конфигурация памяти
    1.2. Секции
    1.3. Адреса
    1.4. Связывание
    1.5. Объектный файл

2. Управляющий язык редактора связей
    2.1. Выражения
    2.2. Оператор присваивания
    2.3. Описание конфигурации памяти
    2.4. Предложения определения секций
        2.4.1. Спецификации файлов
        2.4.2. Указание адреса загрузки секции
        2.4.3. Выравнивание выходной секции
        2.4.4. Группировка выходных секций
        2.4.5. Создание пустот в выходных секциях
        2.4.6. Создание и определение имен при редактировании связей
        2.4.7. Размещение секций в именованных областях памяти
        2.4.8. Инициализация пустот и секций .bss

3. Прочие возможности редактора связей
    3.1. Определение точки входа
    3.2. Использование библиотек объектных файлов
    3.3. Обход неконфигурируемых областей памяти
    3.4. Алгоритм размещения
    3.5. Инкрементальное редактирование связей
    3.6. Секции DSECT, COPY, INFO и OVERLAY
    3.7. Выравнивание секций в выходном файле
    3.8. Ненастраиваемые входные файлы

4. Синтаксис управляющего языка редактора связей


 

1. ОСНОВНЫЕ ПОНЯТИЯ

В статье ld(1) Справочника пользователя перечислены опции командной строки редактора связей, некоторые из которых можно указывать также при вызове C-компилятора cc(1). В настоящей публикации рассматривается управляющий язык редактора связей.

Управляющий язык редактора связей предоставляет следующие возможности:

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

Предложения управляющего языка редактора связей помещаются в файл, имя которого указывается в командной строке ld(1). Если файл указан в командной строке и не опознан в качестве объектного модуля или библиотеки, то предполагается, что он содержит предложения управляющего языка.

В следующих разделах определяются основные понятия, знакомство с которыми необходимо для использования управляющего языка.

1.1. Конфигурация памяти

Для целей размещения программ и данных виртуальная память целевого компьютера подразделяется на конфигурируемую и неконфигурируемую. По умолчанию вся память считается конфигурируемой, то есть допускается ее использование редактором связей. В микропроцессорных приложениях, однако, области памяти, расположенные по разным адресам, зачастую неоднородны. Например, с нулевого адреса может располагаться ППЗУ размером 3K, а с адреса 20K - ПЗУ на 8K. Память в диапазоне от 3K до 20K-1 целесообразно сделать неконфигурируемой, то есть запретоть редактору связей ld(1) использовать ее. Ничто и никогда не может быть связано с неконфигурируемой памятью. Иными словами, указание того, что некоторая область памяти неконфигурируема, делает соответствующие адреса некорректными или несуществующими с точки зрения редактора связей. Конфигурацию памяти, отличную от подразумеваемой, необходимо специфицировать явно.

Если не оговорено противное, все дальнейшие рассуждения о памяти, адресах и т.д. относятся к конфигурируемым областям адресного пространства.

1.2. Секции

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

1.3. Адреса

Применительно к редактированию внешних связей термин физический адрес трактуется нестандартным образом. Физический адрес секции или имени определяется как смещение относительно начала (нулевого адреса) адресного пространства. Физический адрес объекта не обязательно совпадает с тем адресом, по которому объект будет помещен во время выполнения. Так, в системах со страничной виртуальной памятью адрес есть смещение относительно нулевого адреса виртуальной памяти, которое затем преобразуется аппаратурой и/или операционной системой.

1.4. Связывание

Часто бывает необходимо, чтобы начало секции приходилось на определенный, заранее известный адрес. Установление соответствия между объектом и начальным адресом называется связыванием. в таком случае говорят, что объект связан с определенным адресом. Обычно связыванию подвергаются выходные секции, однако имеется возможность связывать с адресами и некоторые абсолютные глобальные имена, для чего используется оператор присваивания управляющего языка ld(1).

1.5. Объектный файл

Объектные файлы генерирует как ассемблер (обычно вызываемый каким-либо компилятором), так и редактор внешних связей ld(1). Редактор связей, получая на вход настраиваемые объектные файлы, создает выходной объектный файл, который может требовать, а может и не требовать дополнительной настройки. При определенных условиях объектные файлы, подаваемые на вход ld(1), могут быть и абсолютными.

Файлы, которые создаются компиляторами, могут среди прочих содержать секции с именами .text и .data. Секция .text содержит выполняемые команды, а секция .data - инициализированные данные. Пусть, например, программа на языке C содержит глобальное, то есть не входящее в функцию, описание и оператор присваивания: int i=100; . . . i=0;

Команды, сгенерированные для оператора присваивания, попадут в секцию .text, а место для переменной i будет отведено в секции .data.

 

2. УПРАВЛЯЮЩИЙ ЯЗЫК РЕДАКТОРА СВЯЗЕЙ

2.1. Выражения

В выражениях могут использоваться глобальные имена, константы и большинство основных операций языка C (см. Синтаксис управляющего языка редактора связей). Как и в языке C, числовые константы считаются десятичными, если только им не предшествует 0 для восьмеричных и 0x для шестнадцатеричных. Все числа трактуются как длинные целые. Имена могут содержать прописные и строчные буквы, цифры и символ подчеркивания, _. Если имя появляется внутри выражения, то в качестве значения используется его адрес. Редактор связей не просматривает таблицу имен и не пытается выяснить значения переменных, размерности массивов и т.п.

Для распознавания имен, чисел, операций и т.п. редактор связей использует сканер, сгенерированный с помощью утилиты lex(1). Ниже перечислены слова, которые сканер считает зарезервированными, и которые нельзя поэтому использовать в качестве имен или названий секций:

       ADDR    BLOCK    GROUP    NEXT     RANGE     SPARE
       ALIGN   COMMON   INFO     NOLOAD   REGIONS   PHY
       ASSIGN  COPY     LENGTH   ORIGIN   SECTIONS  TV
       BIND    DSECT    MEMORY   OVERLAY  SIZEOF
       
           addr    block    length   origin   sizeof
           align   group    next     phy      spare
           assign  l        o        range
           bind    len      org      s

В следующей таблице приведены, в порядке убывания приоритета, знаки допустимых операций:

Знак операции
  ! ~ - (унарный минус) 
  * / % 
  + - (бинарный минус) 
  >> << 
  == != > < <= >= 
  & 
  | 
  && 
  ||  
  = += -= *= /= 

Перечисленные операции имеют тот же смысл, что и в языке C. Операции, знаки которых находятся на одной строке, имеют одинаковый приоритет.

2.2. Оператор присваивания

Оператор присваивания позволяет определять внешние имена и связывать их с определенными адресами. Оператор присваивания может записываться одним из следующих двух способов:

       имя = выражение;
       
       имя операция= выражение;

Здесь операция - это один из знаков +, -, * или /. Оператор присваивания должен оканчиваться точкой с запятой.

Все операторы присваивания (за исключением одного случая, который описывается в следующем абзаце), выполняются после того, как произведено размещение всех объектов, определенных во входных файлах, но перед перед настройкой ссылок из команд и данных. Поэтому, при вычислении выражения в правой части оператора присваивания, значениями встречающихся там имен будут их адреса в выходном объектном файле. Ссылки же из команд или данных на имя, которое посредством оператора присваивания связывается с новым адресом, будут настроены на этот новый адрес. Операторы присваивания обрабатываются в том порядке, в котором они поступают на вход ld(1).

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

Функция align используется для выравнивания по границе n байт, где n есть степень двойки. Следующие два выражения равносильны:

       ALIGN (n)
       
       (. + n - 1) & ~(n - 1)

Псевдофункции SIZEOF и ADDR в качестве аргумента получают имя некоторой секции, а возвращают, соответственно, ее длину и адрес. Они могут использоваться в определениях имен вне пределов определения секций.

Выражения редактора связей могут иметь либо абсолютные, либо перемещаемые (настраиваемые) значения. Если ld(1) создает новое имя в результате обработки оператора присваивания, то тип имени будет совпадать с типом значения выражения из правой части. Этот тип может быть определен при помощи следующих правил:

Примечание

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

2.3. Описание конфигурации памяти

Предложение MEMORY используется для указания:

Если предложения MEMORY отсутствуют, вся память считается конфигурируемой. Подразумеваемый размер памяти зависит от целевого компьютера.

Предложение MEMORY позволяет дать любому диапазону виртуальных адресов произвольное имя длиной не более восьми символов, которое в дальнейшем можно использовать для связывания выходных секций с адресами из поименованного диапазона. Имена областей памяти могут состоять из прописных и строчных букв, цифр и специальных символов $, ., _. Эти имена используются только в процессе работы редактора связей и не включаются ни в заголовки выходного объектного файла, ни в его таблицу имен.

При наличии предложений MEMORY вся явно не описанная в них память считается неконфигурируемой. Редактор связей не использует для размещения неконфигурируемые области памяти, поэтому с ними могут быть связаны только DSECT-секции.

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

В настоящее время возможные атрибуты таковы:

  R   Память, допускающая чтение.
  W   Память, допускающая запись.
  Х   Память, в которой могут размещаться выполняемые команды.
  I   Инициализируемая память. Например, память, отводимая под стек, обычно не инициализируется.

В дальнейшем, если понадобится, будут добавлены новые атрибуты. Если атрибуты не указаны в предложениях MEMORY, либо этих предложений нет вообще, то по умолчанию области памяти получают атрибуты R, W, Х и I.

Cинтаксис предложения MEMORY таков:

       MEMORY {
           имя1 (атрибуты) : origin = n1, length = n2
           имя2 (атрибуты) : origin = n3, length = n4
         .  .  .
       }

После ключевого слова origin (которое можно сократить до org или o) указывается начальный адрес области памяти. После ключевого слова length (или, короче, len или l) указывается размер области. Операнд слова origin должен быть допустимым виртуальным адресом. Значения начального адреса и длины указываются в виде десятичной, восьмеричной или шестнадцатеричной константы, записанной по правилам языка C. Предложения MEMORY, а также спецификации origin и length внутри них, должны отделяться друг от друга пробелами, символами табуляции, переводами строк или запятыми.

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

       MEMORY {
         valid: org = 0x10000, len = 0xFE0000
       }

2.4. Предложения определения секций

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

По умолчанию, если предложения SECTIONS отсутствуют, все одноименные входные секции объединяются в выходной секции с тем же именем. Если редактируются внешние связи двух объектных файлов, из которых первый содержит секции s1 и s2, а второй - секции s3 и s4, то выходной объектный файл будет содержать четыре секции: s1, s2, s3 и s4. Порядок этих секций будет зависеть от порядка, в котором редактор связей будет просматривать входные файлы.

Синтаксис предложений SECTIONS таков:

       SECTIONS {
           имя_выходной_секции_1 : {
               спецификации_файлов,
               операторы_присваивания
           }
           имя_выходной_секции_2 : {
               спецификации_файлов,
               операторы_присваивания
           }
           .  .  .
       }

В оставшейся части этого раздела обсуждаются различные виды предложений определения секций.

2.4.1. Спецификации файлов

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

       имя_файла (имя_секции)
       
       имя_файла (имя_секции1 имя_секции2 ...)

Имена секций входного файла, как и сами спецификации файлов, должны отделяться друг от друга пробелами, символами табуляции, переводами строк или запятыми.

Чтобы указать все неинициализированные и неразмещаемые глобальные объекты из данного файла, можно использовать запись

       имя_файла [COMMON]

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

       SECTIONS {
         outsec1: {
           file1.o (sec1)
           file2.o
           file3.o (sec1, sec2)
         }
       }

В результате выполнения этого предложения порядок размещения входных секций в выходной секции outsec1 будет таким:

Если в других входных файлах окажутся секции с именем outsec1, то они будут следовать после секций, перечисленных в приведенном выше определении выходной секции outsec1. Если в файле file3.o или в файле file1.o окажутся, кроме перечисленных выше, еще какие-нибудь входные секции, то они будут помещены в одноименные выходные, - в том, разумеется, случае, если пользователь не включит их в другие спецификации файлов.

Для спецификации всех не размещенных ранее входных секций с определенным именем (независимо от имени входного файла) используется запись вида

       * (имя_секции)

2.4.2. Указание адреса загрузки секции

Связывание выходной секции с определенным адресом виртуальной памяти достигается посредством следующей разновидности предложения SECTIONS:

       SECTIONS {
           имя_выходной_секции_1  адрес : {
               . . .
           }
           . . .
       }

Адрес, с которым выполняется связывание, записывается в виде константы языка C. Редактор связей выдает соответствующее сообщение об ошибке, если указанные выходная_секция и адрес не могут быть связаны (например, из-за конфигурации памяти или из-за перекрытия с другими секциями). В качестве адреса можно также использовать слово BIND, за которым должно следовать выражение в скобках. В этом выражении могут встречаться псевдофункции SIZEOF, ADDR и NEXT. Аргументом SIZEOF и ADDR должна быть ранее определенная секция, а аргументом NEXT - константа. Псевдофункция NEXT возвращает минимальный адрес конфигурируемой памяти, по которому еще ничего не размещено, кратный аргументу.

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

Предложения SECTIONS могут подаваться на вход редактора связей в произвольном порядке, если только не используются псевдофункции SIZEOF и ADDR.

Редактор связей не обеспечивает автоматически ни четного размера секции, ни четного адреса ее начала, в отличие от ассемблера, который гарантирует, что длина секции в байтах будет делиться на четыре. Используя предложения управляющего языка, можно добиться того, что секция будет начинаться с нечетного адреса, однако делать это не рекомендуется. Если секция начинается с нечетного байта, то либо она будет неправильно выполняться, либо неправильно будет осуществляться доступ к данным, находящимся в этой секции. Если пользователь все же указал нечетную границу, ld(1) выдаст предупреждение.

2.4.3. Выравнивание выходной секции

Можно потребовать, чтобы начальный виртуальный адрес выходной секции был бы выравнен на границу n байт, где n есть степень 2. Это достигается использованием в предложении SECTIONS, на месте адреса, функции ALIGN. Следующие две формы записи адреса начала секции эквивалентны:

       ALIGN (n)
       
       (. + n - 1) & ~(n - 1)
Рассмотрим пример.
       SECTIONS {
         outsec ALIGN (0x20000): {
            . . .
         }
         . . .
       }

Здесь выходной секции outsec не назначается никакой заранее определенный адрес, но она будет размещена по некоторому адресу, кратному 0x20000 (например, может быть назначен адрес 0x0, 0x20000, 0x40000, 0x60000 и т.д.).

2.4.4. Группировка выходных секций

Подразумеваемый алгоритм размещения секций для редактора связей ld(1) таков:

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

       SECTIONS {
           .text размер_заголовков : { *(.init) *(.text) }
           }
           GROUP BIND (NEXT (граница_выравнивания) +
                       (SIZEOF (.text) + ADDR (.text)) % 0x2000) : {
               .data : { }
               .bss : { }
           }
       }

где граница_выравнивания есть машинно-зависимая константа. Предложение GROUP обеспечивает группировку, то есть последовательное размещение, двух выходных секций, .data и .bss. Связывание с конкретным адресом и выравнивание по определенной границе производится для группы в целом, а не для отдельных входящих в нее секций. Секции, образующие группу, размещаются последовательно в том порядке, в котором они указываются в предложении GROUP.

Если необходимо сгруппировать секции .text, .data и .bss, следует использовать такое предложение SECTIONS:

       SECTIONS {
         GROUP: {
           .text: {}
           .data: {}
           .bss: {}
         }
       }

При этом выходной файл будет по-прежнему содержать три различные секции (.text, .data и .bss), однако теперь они будут размещаться в последовательных участках виртуальной памяти.

Группу выходных секций как единое целое можно связать с конкретным адресом или произвести ее выравнивание на определенную границу, просто указав нужный адрес в предложении GROUP. Так, для связывания с адресом 0xС0000 достаточно воспользоваться конструкцией

       GROUP 0xС0000: {

а для выравнивания на границу, кратную 0x10000, - конструкцией

       GROUP ALIGN (0x10000): {

Если сделать одно из этих добавлений к указанному выше примеру, то выходная секция .text будет размещена по адресу 0xС0000 (соответственно выравнена на границу 0x10000); затем остальные члены группы, в порядке их указания, размещаются по ближайшим доступным адресам.

Если предложение GROUP не используется, то каждая выходная секция рассматривается отдельно:

       SECTIONS {
         .text: {}
         .data ALIGN (0x2000): {}
         .bss: {}
       }

Секция .text связывается с виртуальным адресом 0x0 (если он находится в пределах конфигурируемой памяти). Секция .data связывается с виртуальным адресом, кратным 0x2000. Если хватит места, секция .bss будет следовать сразу за секцией .text, если же не хватит - то сразу за секцией .data. Порядок, в котором имена выходных секций появляются в предложении SECTIONS, не определяет порядка следования этих секций в выходном файле.

2.4.5. Создание пустот в выходных секциях

Специальный символ точка, ., может появляться только в предложениях определения секций и в операторах присваивания. Его появление в левой части оператора присваивания предписывает ld(1) увеличить или переустановить счетчик размещения, что приводит к образованию пустого места в выходной секции. Отметим, что под такого рода пустоты физически отводится место в выходном файле; они инициализируются с помощью заполнителя - либо подразумеваемого (0x00), либо явно указанного (см. описание опции -f в разделе Прочие возможности редактора связей и раздел Инициализация пустот и секций .bss).

Рассмотрим следующее определение секции:

       outsec: {
         . += 0x1000;
         f1.o (.text)
         . += 0x100;
         f2.o (.text)
         .  = align (4);
         f3.o (.text)
       }

Результат выполнения этого предложения будет таков:

Размещение и выравнивание в пределах одной выходной секции производится относительно ее начала, то есть так, как если бы секция начиналась с нулевого адреса. Поэтому, если в рассмотренном выше примере адрес выходной секции outsec окажется нечетным, то нечетным будет и адрес той части outsec, куда размещается входная секция f3.o (.text), даже если f3.o (.text) и была ранее выравнена на границу слова. Этого можно избежать, потребовав выравнивания выходной секции в целом:

       outsec ALIGN (4): {

Заметим, что ассемблер as(1) всегда производит выравнивание ге- нерируемых им секций на границу слова, так что можно не указывать это требование явно. То же верно и в отношении C-компилятора.

Операторы, уменьшающие счетчик размещения, являются некорректными, поскольку повторное размещение по какому-либо адресу не допускается. Как правило, для изменения счетчика размещения используются операции += и align.

2.4.6. Создание и определение имен при редактировании связей

Операторы присваивания ld(1) нужны для того, чтобы имя могло получить значение, которое вычисляется при редактировании связей. Обычно употребляются три вида операторов присваивания:

Первый вид присваиваний обсуждался в предыдущем разделе.

В операторах второго вида имени присваивается адрес, который становится известным только после размещения. Пример:

       SECTIONS {
         outsc1: { ... }
         outsc2: {
           file1.o (s1)
           s2_start = . ;
           file2.o (s2)
           s2_end = . - 1;
         }
       }

Значением s2_start будет адрес начала секции file2.o (s2), а значением s2_end - адрес последнего байта file2.o (s2).

Рассмотрим следующий пример.

       SECTIONS {
         outsc1: {
           file1.o (.data)
           mark = .;
           . += 4;
           file2.o (.data)
         }
       }

Здесь создается имя mark и его значением становится адрес байта, следующего за окончанием секции .data из file1.o. Затем резервируются четыре байта для будущего использования mark при выполнении. Тип имени mark - длинное целое (32 бита).

Значение выражения, содержащего счетчик размещения, вычисляется в процессе размещения, поэтому такие выражения могут встречаться только внутри предложений SECTIONS. В предложениях SECTIONS могут быть и операторы присваивания, не содержащие ., хотя обычно так не делается. Значения выражений из правых частей таких операторов определяются после окончания размещения. Изменять подобным образом адрес какого-либо имени опасно. Пусть, например, имя определено в секции .data, где для него отведен и проинициализирован некоторый участок памяти. Тогда, если редактируется ряд объектных файлов, содержащих ссылки на это имя, соответствующий ему элемент таблицы имен с момента присваивания будет отражать новый, измененный адрес этого имени. В то же время инициализированные данные не переписываются по новому адресу; кроме того, могут остаться ссылки на старый адрес. ld(1) выдает предупреждение при изменении значения каждого имени, ранее уже определенного. Заметим, однако, что присваивание абсолютных значений новым именам совершенно безопасно, так как с такими именами не связаны ни ссылки, ни данные.

2.4.7. Размещение секций в именованных областях памяти

Можно потребовать, чтобы секция была размещена где-либо внутри именованной области памяти, определенной ранее предложением MEMORY. По аналогии с принятым в операционной системе UNIX синтаксисом переназначения стандартного вывода, для этого применяется символ >. Пример:

       MEMORY {
         mem1:      o=0x000000 l=0x10000
         mem2 (RW): o=0x020000 l=0x40000
         mem3 (RW): o=0x070000 l=0x40000
         mem1:      o=0x120000 l=0x04000
       }
       
       SECTIONS {
         outsec1: {f1.o (.data)} > mem1
         outsec2: {f2.o (.data)} > mem3
       }

Эти предложения предписывают ld(1) разместить секцию outsec1 где-либо внутри области памяти mem1, то есть между 0x0 и 0xFFFF или между 0x120000 и 0x123FFF. Секция outsec2 будет размещена в диапазоне адресов от 0x70000 до 0xAFFFF.

2.4.8. Инициализация пустот и секций .bss

Пустоты в выходных секциях (см. пример в разделе Создание пустот в выходных секциях) редактор связей обычно заполняет нулевыми байтами. По умолчанию, секции .bss не инициализируются вовсе, то есть ни ассемблер, ни редактор связей не генерируют для них каких-либо (в том числе и нулевых) данных.

Пустоты, а равно и выходные секции .bss, можно заполнить произвольными двухбайтными значениями, указав опцию инициализации в предложении SECTIONS. Подчеркнем, что опция инициализации воздействует только на пустоты и секции .bss. Опция может понадобиться, например, если необходимо заполнить определенным образом таблицу неинициализированных данных без перекомпиляции программ, или если нужно заполнить "дыру" в секции .text командами переходе к подпрограмме обработки ошибок.

Потребовать инициализации можно как для выходной секции в целом, так и для отдельной ее части. Однако в связи с тем, что неинициализированная секция .bss физически не занимает места в выходном дайле, ее нельзя проинициализировать частично. Даже если заказана инициализация только части секции .bss, она будет проинициализирована целиком. Итак, если секция .bss объединяется с секциями .text или .data (разумеется, инициализируемыми), или если инициализируется часть секции .bss, то произойдет одно из двух:

Рассмотрим следующее определение секции:

       SECTIONS {
         sec1: {
           f1.o
           . += 0x200;
           f2.o (.text)
         } = 0xDFFF
         sec2: {
           f1.o (.bss)
           f2.o (.bss) = 0x1234
         }
         sec3: {
           f3.o (.bss)
            . . .
         } = 0xFFFF
         sec4: {f4.o (.bss)}
       }

Здесь "дыра" размером 0x200 байт в секции sec1 заполняется значениями 0xDFFF. В секции sec2 f1.o (.bss) заполняется подразумеваемым значением 0x00, а f2.o (.bss) инициализируется последовательностью 0x1234. Все секции .bss, входящие в sec3, как и пустоты, заполняются значением 0xFFFF. Секция sec4 не инициализируется, иными словами, в объектном файле не будет данных для этой секции.

 

3. ПРОЧИЕ ВОЗМОЖНОСТИ РЕДАКТОРА СВЯЗЕЙ

3.1. Определение точки входа

Вспомогательный заголовок объектных файлов обычного формата, применяемый в ОС UNIX и имеющий структуру a.out, содержит поле для (основной) точки входа этого файла. Правила заполнения это- го поля редактором связей (в порядке их применения) таковы:

Таким образом, можно задать значение точки входа, используя опцию -e или предложение управляющего языка вида

       _start  =  выражение

Когда редактор связей вызывается на выполнение командой cc(1), программа пользователя объединяется с инициализирующей программой. Эта последняя после обращения к программе пользователя выполняет системный вызов exit [см. exit(2)], чтобы закрыть файлы и осуществить другие терминирующие действия. Если пользователь вызывает редактор связей сам и/или изменяет точку входа, он должен гарантировать, что программа завершает выполнение системным вызовом exit.

3.2. Использование библиотек объектных файлов

Каждый элемент такой библиотеки (например, библиотеки libc.a) является полноценным объектным файлом. Команда ar(1) создает библиотеки из объектных файлов, генерируемых компиляторами. Библиотеки обрабатываются редактором связей избирательно: используются только те элементы, которые разрешают внешние ссылки. Библиотеки могут упоминаться как внутри предложений, определяющих секции, так и вне их. В обоих случаях объектный файл - элемент библиотеки используется для редактирования внешних связей, если выполнены следующие два условия:

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

Необходимо запомнить следующее:

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

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

Рассмотрим следующий пример:

Пусть командная строка с вызовом ld(1) выглядит следующим образом:

       ld file1.o -la file2.o -lc

Тогда ссылки на FCN разрешаются элементом 1 библиотеки liba.a, ссылка на ABC - элементом 0 библиотеки libc.a, а ссылка на XYZ остается неразрешенной, так как библиотека liba.a просматривается раньше редактирования связей файла file2.o. Если же команда ld вводится таким образом:

       ld file1.o file2.o -la -lc

то ссылки на FCN и ABC разрешаются как в предыдущем примере, а ссылка на XYZ разрешается элементом 0 библиотеки liba.a. Пусть, наконец, команда ld(1) введена так:

       ld file1.o file2.o -lc -la

Отличие от предыдущего примера выразится в том, что для разрешения ссылки на FCN будет извлечен элемент 1 библиотеки libc.a, а не liba.a.

Опция -u используется, чтобы вызвать принудительное редактирование связей тех элементов библиотек, на которые, быть может, нет реально существующих внешних ссылок. Например, в случае вызова

       ld -u rout1 -la

создается неопределенное имя rout1 и, если в каком-либо объектном файле библиотеки liba.a это имя определяется, то этот файл (а с ним, быть может, и некоторые другие) извлекается для редактирования связей. Без опции -u ld(1) не просматривал бы библиотеку вообще ввиду отсутствия неразрешенных ссылок и неопределенных имен.

3.3. Обход неконфигурируемых областей памяти

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

       MEMORY {
         mem1: о=0x00000 l=0x02000
         mem2: о=0x40000 l=0x05000
         mem3: о=0x20000 l=0x10000
       }

Пусть далее в каждом из файлов f1.o, f2.o, ..., fn.o содержатся три секции: .text, .data и .bss. Предположим также, что размер объединенной секции .text оказался бы равным 0x12000 байт. Легко видеть, что не существует конфигурируемой области памяти достаточной длины. Чтобы ld(1) мог выполнить размещение, необходимо эту секцию разделить посредством соответствующих предложений, например:

       SECTIONS {
         txt1: {
           f1.o (.text)
           f2.o (.text)
           f3.o (.text)
         }
         txt2: {
           f4.o (.text)
           f5.o (.text)
           f6.o (.text)
         }
         . . .
       }

3.4. Алгоритм размещения

Выходная секция создается в результате выполнения предложения SECTIONS, или объединения одноименных входных секций или объединения секций .text и .init в выходную секцию .text. В выход- ную секцию включаются несколько (возможно, одна или ни одной) входных. После того, как состав выходной секции определен, ей назначается для размещения участок конфигурируемой виртуальной памяти. ld(1) использует алгоритм, цель которого - минимизиро- вать фрагментацию памяти и таким образом повысить вероятность успешного размещения всех выходных секций, с учетом конфигурации памяти. Этот алгоритм заключается в следующем:

Если, как это предполагается по умолчанию, вся память образует одну непрерывную конфигурируемую область, а предложения SECTIONS отсутствуют, то выходные секции размещаются в том порядке, в котором их создает ld(1). В остальных случаях выходные секции размещаются в том порядке, в котором они определяются, или становятся известными ld(1), - в первой подходящей из доступных областей памяти.

3.5. Инкрементальное редактирование связей

Как уже отмечалось ранее, результат работы ld(1) можно исполь- зовать в качестве исходной информации для последующего редактирования связей, при условии, что сохраняется информация о настройке ссылок (то есть задана опция -r). Редактирование связей, использующее ранее полученную информацию, называется инкрементальным. Есть смысл разделять большие системы на несколько подсистем, связи внутри которых редактируются независимо, а затем, при необходимости, осуществлять пересборку системы в целом, например:

Шаг 1:

       ld -r -o outfile1 ifile1 infile1.o

       /* ifile1 */
       SECTIONS {
         ss1: {
           f1.o
           f2.o
            . . .
           fn.o
         }
       }

Шаг 2:

       ld -r -o outfile2 ifile2 infile2.o

       /* ifile2 */
       SECTIONS {
         ss2: {
           g1.o
           g2.o
            . . .
           gn.o
         }
       }

Шаг 3:

       ld -a -o final.out outfile1 outfile2

Если подсистемы формируются разумно, то после перекомпиляции нескольких файлов придется повторить лишь часть процесса редактирования связей. Рекомендуется придерживаться двух простых правил:

3.6. Секции DSECT, COPY, INFO и OVERLAY

При определении секций им может быть назначен тип. Пример:

       SECTIONS {
         name1 0x200000 (DSECT) : { file1.o }
         name2 0x400000 (COPY)  : { file2.o }
         name3 0x600000 (NOLOAD): { file3.o }
         name4          (INFO)  : { file4.o }
         name5 0x900000 (NOLOAD): { file5.o }
       }

Применение опции DSECT приводит к созданию так называемой фиктивной секции. Свойства фиктивной секции таковы:

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

Секция с опцией COPY подобна фиктивной секции с той лишь разницей, что данные COPY-секции и вся связанная с ней информация включаются в выходной файл.

Те же свойства, что и COPY, имеют INFO-секции, однако назначение последних - хранить информацию о самом объектном файле, в то время как секции COPY могут содержать действительные данные или команды.

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

Секция типа OVERLAY настраивается и записывается в выходной файл. От обычной секции она отличается тем, что не размещается и может перекрываться с другими секциями и неконфигурируемыми областями памяти.

3.7. Выравнивание секций в выходном файле

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

       SECTIONS {
         .text BLOCK (0x200): {}
         .data ALIGN (0x2000) BLOCK (0x200): {}
       }

Указанные предложения SECTIONS предписывают ld(1), чтобы каждая из секций .text и .data оказались в выходном файле со смещением от начала, кратным 0x200 - например, 0x0, 0x200, 0x400 и т.д.

Полезно рассмотреть содержимое файла /lib/default.ld, с помощью которого можно изменять подразумеваемые соглашения редактора связей:

       MEMORY {
         valid : org = 0x0, len = 0x90000000
       }
       SECTIONS {
         GROUP BLOCK(1024): {
           .text: {*(.init) *(.text) *(.fini)}
         }
         GROUP   ALIGN(1048576) BLOCK(1024): {
           .data: {}
           .bss: {}
         }
       }

3.8. Ненастраиваемые входные файлы

Если результат работы ld(1) предполагается использовать в качестве исходных данных для последующего редактирования связей, необходимо при первом вызове ld(1) указать опцию -r, чтобы сох- ранить информацию о настройке ссылок и дать возможность выполнить окончательное редактирование.

Если во входном файле ld(1) отсутствует таблица имен или информация о настройке ссылок (вследствие применения команды strip(1) либо редактирования без опции -r или с опцией -s) ld(1) продолжает работу, используя в качестве исходных данных ненастраиваемый файл.

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

Примечание

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

 

4. СИНТАКСИС УПРАВЛЯЮЩЕГО ЯЗЫКА РЕДАКТОРА СВЯЗЕЙ

Примечание

В данном разделе символы квадратных и фигурных скобок несут двойную нагрузку: Символ перевода строки в середине правила эквивалентен символу альтернативы |.
ifile ::= {cmd}

cmd ::= memory | sections | 
   assignment | filename | flags

memory ::= MEMORY {[memory_spec]}
memory_spec ::= name [attributes]:origin_spec,length_spec
attributes ::= ({ R | W | X | I })
origin_spec ::= origin=long
length_spec ::= length=long
origin ::= origin | o | org | ORIGIN
length ::= length | l | len | LENGTH

sections ::= SECTIONS{{sec_or_group}}
sec_or_group ::= section | group | library
group ::= GROUPgroup_options:{section_list}
section_list ::= section{[,]section}
section::=section_name[sec_options]:{[{statement}]}
        [fill][mem_spec]
group_options ::= [addr_2] | [align_option][block_option]
sec_options ::= [addr_2]
        [align_option][type_option][block_option]
addr_2 ::= long | bind(expr)
align_option ::= align(expr)
align ::= align | ALIGN
block_option ::= block(long)
block ::= block | BLOCK
type_option ::= (DSECT) | (NOLOAD) | (COPY) | (INFO) | (OVERLAY)
fill ::= =long
mem_spec ::=  name |  attributes
statement ::= filename[[COMMON]][fill]
        filename(name_list)[[COMMON]][fill]
        *(name_list)[[COMMON]][fill]
        assignment | library | пусто
name_list ::= section_name[{delimiter section_name}]
library ::= -lname
bind ::= bind | BIND

assignment ::= lside assign_op expr end
lside ::= name | .
assign_op ::= = | += | -= | *= | /=
end ::= ; | ,
expr ::= expr binary_op expr | term
binary_op ::= * | / | % | + | - | << | >> | == | !=
        > | < | >= | <= | &
        |
        &&
        ||
term ::= long | name | align(term) | (expr)
        unary_op term | phy(lside) | sizeof(section_name)
        next(long) | addr_4(section_name)
unary_op ::= ! | - | ~
phy ::= phy | PHY
sizeof ::= sizeof | SIZEOF
next ::= next | NEXT
addr_4 ::= addr | ADDR

flags ::= flag{wht_space flag}
flag ::= -ewht_space name | -fwht_space long | -hwht_space long
        -lname | -m | -owht_space filename | -r | -s | -t
        -uwht_space name | -z | -H | -Lpath_name | -М | -N | -S | -V
        -VSwht_space long | -a | -x
delimiter ::= wht_space | ,
name ::= произвольное допустимое имя переменной
long ::= произвольная допустимая длинная целая константа
wht_space ::= пробел, символ табуляции, перевод строки

filename ::= любое допустимое в ОС UNIX имя файла
path_name ::= любое допустимое в ОС UNIX маршрутное имя
section_name ::= любое допустимое имя секции


НазадОглавлениеВперед
КаталогИндекс раздела