Глобальные переменные доступны косвенно, через глобальную таблицу смещений.
- При компиляции программы компилятор генерирует код, который выполняет
косвенный доступ и испускает информацию о перемещении с указанием
используемая запись в глобальной таблице смещений.
- Компоновщик выполняет эти перемещения при создании финала
динамически загружаемый объект, в результате чего получается машинный код, который не
необходимо дальнейшее исправление во время загрузки.
Чтобы увидеть это в действии, рассмотрим следующий фрагмент кода.
int v1;
int f(void) { return !v1; }
Функция f
ссылается на глобальный v1
. Сгенерированный машинный код
для функции выглядит следующим образом (на i386):
% gcc -c -fpic a.c
% objdump --disassemble --reloc a.o
[snip]
Disassembly of section .text:
00000000 <f>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: e8 fc ff ff ff call 4 <f+0x4>
4: R_386_PC32 __i686.get_pc_thunk.cx
8: 81 c1 02 00 00 00 add $0x2,%ecx
a: R_386_GOTPC _GLOBAL_OFFSET_TABLE_
e: 8b 81 00 00 00 00 mov 0x0(%ecx),%eax
10: R_386_GOT32 v1
14: 8b 00 mov (%eax),%eax
16: 85 c0 test %eax,%eax
18: 0f 94 c0 sete %al
1b: 0f b6 c0 movzbl %al,%eax
1e: 5d pop %ebp
1f: c3 ret
Disassembly of section .text.__i686.get_pc_thunk.cx:
00000000 <__i686.get_pc_thunk.cx>:
0: 8b 0c 24 mov (%esp),%ecx
3: c3 ret
Проход по машинному коду:
- (смещения 0x0 и 0x1) Стандартный пролог функции.
- (смещение 0x3) Звонок на
__i686.get_pc_thunk.cx
готовится к
Относительная к ПК адресация путем загрузки адреса инструкции
после звонка в регистр %ecx
.
- (смещение 0x8) Значение в
%ecx
настроено так, чтобы указывать на начало
глобальной таблицы смещений. Эта корректировка сигнализируется
запись о перемещении типа R_386_GOTPC
.
- (смещение 0xE) Получен глобальный адрес
v1
.
Перемещение R_386_GOT32
обеспечивает смещение записи v1
от
база таблицы глобальных смещений.
- (смещение 0x14) Значение в
v1
извлекается в регистр %eax
.
- (смещения 0x16--0x1F) Остальные вычисления для функции
f
.
В конечном общем объекте компоновщик исправляет код функции
следующее:
% gcc -shared -o a.so a.o
% objdump --disassemble a.so
...snip...
0000044c <f>:
44c: 55 push %ebp
44d: 89 e5 mov %esp,%ebp
44f: e8 18 00 00 00 call 46c <__i686.get_pc_thunk.cx>
454: 81 c1 a0 1b 00 00 add $0x1ba0,%ecx
45a: 8b 81 f8 ff ff ff mov -0x8(%ecx),%eax
460: 8b 00 mov (%eax),%eax
462: 85 c0 test %eax,%eax
...snip...
- Если предположить, что объект загружается со смещением O в память,
инструкция вызова со смещением 0x44F будет загружать O + 0x454 + 0x1BA0, т.е.
O + 0x1FF4 в
%ecx
.
- Инструкция со смещением 0x45A вычитает 8 из
%ecx
чтобы получить адрес слота для v1
в глобальной таблице смещений,
то есть слот для v1
находится по смещению 0x1FEC от начала
общий объект.
Рассматривая записи динамического перемещения для общего объекта, мы
увидеть запись о перемещении, дающую указание загрузчику времени выполнения сохранить
фактический адрес для v1
со смещением 0x1FEC.
% objdump -R a.so
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
...snip...
00001fec R_386_GLOB_DAT v1
...snip...
Дальнейшее чтение: