Допустим, у нас есть:
f1.c
#include static int x = 10; void f1() { printf("f1.c : %d\n", x); }
main.c
extern void f1(); int main(int argc, char **argv) { f1(); return 0; }
мы скомпилируем и прочитаем два символьных символа ELF (rel. ELF и exec ELF):
$> gcc -c *.c $> readelf -s f1.o | grep x Num: Value Size Type Bind Vis Ndx Name 5: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 x $> gcc *.o $> readelf -s a.out | grep x Num: Value Size Type Bind Vis Ndx Name 38: 0000000000601038 4 OBJECT LOCAL DEFAULT 25 x
Я вижу, что Value
(также называемое адресом), в котором глобальная статическая переменная x
составляет 0000000000000000
от чтения перемещаемого объектного файла f1.o
Это означает, что мы еще не инициализировали его, поскольку он все еще является rel. Объект ELF и компоновщик позаботятся об этом.
Итак, мой вопрос заключается в том, если компоновщик – это тот, который устанавливает x
в значение 10 по известному адресу после ссылки 0000000000601038
, как это сделать? Где линкер получает информацию, чтобы установить значение 10 и кто дает эту информацию ( f1.o
?)?
Значение 0000000000000000
(в объектном файле f1.o
) является относительным адресом (статической переменной), равно как и смещение, и этот файл также содержит связанные с ним директивы о перемещении . Код для получения аргумента x
для печати также имеет некоторое перемещение на нем (в некоторой команде загрузки машины).
В этом объектном файле, вероятно, есть раздел .data
. Этот раздел должен начинаться со слова (с 0 смещением, которое вы наблюдали в f1.o
), содержащим 10.
Узнайте больше о линкерах (я рекомендую книгу Линкеров и загрузчиков Левина). Процесс связывания (для получения исполняемого файла ELF ) обрабатывает директивы перемещения. Читайте также больше о формате ELF, начиная с эльфа (5) (прочитав ELF wikipage). Изучите также спецификации ABI (для Linux x86-64 см. Здесь из этого ответа), в котором подробно описаны директивы о перемещении.
Вы можете захотеть скомпилировать ваш f1.c
с помощью gcc -Wall -S -fverbose-asm -O1 f1.c
затем просмотреть испущенный файл ассемблера f1.s
Вы также можете просмотреть объектный файл f1.o
и исполняемый файл ELF с помощью различных инструментов, таких как readelf (1) и objdump (1) . Оба принимают множество опций (в частности, параметр -r
для objdump
для отображения директив перемещения).
Динамическое связывание (в стандартной библиотеке libc.*.so
) вводит некоторую дополнительную сложность в исполняемом элементе ELF. См. Также ld-linux (8) (который выполняет некоторое задание связывания в начале выполнения) и vdso (7) . Вы также можете прочитать статью Drepper How To Write Shared Libraries .
Свободно ansible учебник Операционные системы: три простых пьесы также могут быть полезны для чтения (он объясняет, что такое процесс и как его исполнение продолжается).
Этот сегмент, где хранятся переменные продолжительности статического хранения с определенными значениями, известен как .data
(это имя используется стандартом ELF, но другие линкеры имеют тенденцию использовать одно и то же имя).
Как эти переменные установлены, зависит от целевой системы.
.data
инициализируется заранее как часть исполняемого файла и загружается в ОЗУ вместе с программой. .data
не могут быть инициализированы заранее. Вместо этого он копируется с ПЗУ в ОЗУ, прежде чем main () вызывается некоторым кодом запуска («CRT»). Таким образом, он фактически устанавливается во время выполнения, что означает, что всегда есть задержка при запуске программы в таких системах. Чтобы избавиться от задержки, часто используется нестандартная альтернатива запуска («минимальная»), которая полностью игнорирует инициализацию статических переменных продолжительности хранения.