Позиционно-независимый код
За всеми этими разговорами мы чуть было не забыли о третьем способе формирования адреса в программе. Это относительная адресация, когда адрес получается сложением адресного поля команды и адреса самой этой команды – значения счетчика команд. Код, в котором используется только такая адресация, можно загружать с любого адреса без всякой перенастройки. Такой код называется позиционно-независимым (position-independent).
Позиционно-независимые программы очень удобны для загрузки, но, к сожалению, при их написании следует соблюдать довольно жесткие ограничения, накладываемые на используемые в программе методы адресации. Например, нельзя пользоваться статически инициализованными переменными указательного типа, нельзя делать на ассемблере фокусы, вроде того, который был приведен в примере 3.5, и т. д. Возникают сложности при сборке программы из нескольких модулей.
К тому же, на многих процессорах, например, на Intel 8080/8085 или многих современных RISC-процессорах, описанная выше реализация позиционно-независимого кода вообще невозможна, так как эти процессоры не поддерживают соответствующий режим адресации для данных. На процессорах гарвардской архитектуры адресовать данные относительно счетчика команд вообще невозможно – команды находятся в другом адресном пространстве.
Поэтому такой стиль программирования используют только в особых случаях. Например, многие вирусы для MS DOS и драйверы для RT-I1 написаны именно таким образом.
Любопытное наблюдение
В эпоху RT-11 хакеры писали драйверы. Сейчас они пишут вирусы. Еще любопытнее, что для некоторых персональных платформ, например, для Amiga, вирусов почти нет. Хакеры считают более интересным писать игры или демонстрационные программы для Amiga. Похоже, общение с IBM PC порождает у программиста какие-то агрессивные комплексы. Наблюдение это принадлежит не автору: см. [КомпьютерПресс 1993].
Позиционно-независимый код в современных Unix-системах
Компиляторы современных систем семейства UNIX – GNU С или стандартный С-компилятор UNIX SVR4 имеют ключ – f PIC (Position-Independent Code). Впрочем, код, порождаемый при использовании этого ключа, не является позиционно-независимым в указанном выше смысле: этот код все-таки содержит перемещаемые адресные ссылки. Задача состоит не в том, чтобы избавиться от таких ссылок полностью, а лишь в том, чтобы собрать все эти ссылки в одном месте и разместить их, по возможности, отдельно от кода. Какая от этого польза, мы поймем несколько позже, в разд. "Разделяемые библиотеки", а сейчас обсудим технические приемы, используемые для решения этой задачи.
Код, генерируемый GNU С, использует базовую адресацию: в начале функции адрес точки ее входа помещается в один из регистров, и далее вся адресация других функций и данных осуществляется относительно этого регистра. На процессоре х86 используется регистр %ebx, а загрузка адреса осуществляется командами, вставляемыми в пролог каждой функции (пример 3.6).
На процессорах, где разрешен прямой доступ к счетчику команд, соответствующий код выглядит проще, но принцип сохраняется: компилятор занимает один регистр и благодаря этому упрощает работу загрузчику.
Как мы видим в примере 3.7, на самом деле адресация происходит не относительно точки входа в функцию, а относительно некоторого объекта, называемого GOT или GLOBAL_OFFSET_TABLE. Счетчик команд используется для вычисления адреса этой таблицы, а не сам по себе. Подробнее мы разберемся с логикой работы этого кода (и заодно с тем, что означает еще один непонятный символ – PLT) в разд. "Разделяемые библиотеки".
Компилированный таким образом код предназначен в первую очередь для разделяемых библиотек формата ELF (Executable and Linking Format, формат исполняемых и собираемых [модулей], используемый большинством современных систем семейства Unix).
Пример 3.6. Получение адреса точки входа в позиционно-независимую подпрограмму:
call L4 L4: popl %ebx