Позиционно-независимый код
За всеми этими разговорами мы чуть было не забыли о
третьем способе формирования адреса в программе. Это относительная адресация,
когда адрес получается сложением адресного поля команды и адреса самой
этой команды — значения счетчика команд. Код, в котором используется только
такая адресация, можно загружать с любого адреса без всякой перенастройки.
Такой код называется позиционно-независимым (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
Пример 3.7. Позиционно-независимый код, порождаемый
компилятором GNU С
/* strerror.c (emx+gcc) — Copyright (с) 1990-1996 by Eberhard
Mattes */
#include <stdlib.h>
#include <string.h>
#include <emx/thread.h>
char *strerror (int errnum)
{ (
if (errnum >= 0 && errnum < _sys_nerr)
return (char *)_sys_errlist [errnum];
else
{
static char msg[] = "Unknown error ";
#if defined ( _ MT _ )
struct _thread *tp = _thread ( ) ;
#define result (tp->_th_error)
#else
static char result [32];
#endif
memcpy (result, msg, sizeof (rasg) — 1) ;
_itoa (errnum, result + sizeof (msg) — 1, 10) ;
return result;
}
}
gcc -f PIC -S strerror.c
. file "strerror"
gcc2_compiled. :
_ gnu_compiled_c :
.data
_msg.2:
.ascii "Unknown error \0"
.Icomm _result.3,32
.text
.align 2, 0x90
. globl _strerror
__strerror:
pushl %ebp
movl %esp, %ebp
pushl %ebx
call L4
L4:
popl %ebx
addl $_GLOBAL_OFFSET_TABLE_+ [ . -L4 ] , %ebx
cmpl $0,8 (%ebp)
jl L2
movl _ sys_nerr@GOT (%ebx) , %eax
movl 8 (%ebp) , %edx
cmpl %edx, (%eax)
jle L2
movl 8(%ebp),%eax
movl %eax,%edx
leal 0(,%edx,4),%eax
movl __sys_errlist@GOT(%ebx) , %edx
movl (%edx,%eax),%eax jmp LI
.align 2,0x90 jmp L3 .align 2,0x90
L2:
pushl $14
leal _msg.2@GOTOFF(%ebx),%edx
movl %edx,%eax
pushl %eax
leal _result.3@GOTOFF(%ebx) ,%edx
movl %edx,%eax
pushl %eax
call _memcpy@PLT
addl $12,%esp
pushl $10
leal _result.3@GOTOFF(%ebx) , %edx
leal 14(%edx),%eax
pushl %eax
movl 8(%ebp),%eax
pushl %eax
call __itoa@PLT
addl $12,%esp
leal _result.3@GOTOFF(%ebx) ,%edx
movl %edx,%eax
jmp LI
.align 2,0x90
L3:
LI:
movl -4 (%ebp),%ebx
leave
ret |