Мне было интересно, что именно хранится в .o или .so файле, который получается в результате компиляции программы на C ++. Эта почта дает довольно хороший обзор процесса компиляции и функции файла .o в нем, и, насколько я понимаю из эта почта, .Файлы a и .so — это просто несколько файлов .o, объединенных в один файл, который связан статическим (.a) или динамическим (.so) способом.
Но я хотел проверить, правильно ли я понимаю, что хранится в таком файле. После компиляции следующего кода
void f();
void f2(int);
const int X = 25;
void g() {
f();
f2(X);
}
void h() {
g();
}
Я ожидаю найти следующие элементы в .o файле:
g()
, содержащий некоторые адреса-заполнители, где f()
а также f2(int)
называются.h()
без заполнителейX
, который будет просто число 25
g()
, h()
а также X
может быть найденf()
а также f2(int)
, которые должны быть решены во время ссылки.Тогда программа, как nm
будет перечислять все имена символов из обеих таблиц.
Я полагаю, что компилятор может оптимизировать вызов f2(X)
позвонив f2(25)
вместо этого, но ему все равно нужно будет хранить символ X в файле .o, поскольку нет способа узнать, будет ли он использоваться из другого файла .o.
Будет ли это правильно? То же самое для файлов .a и .so?
Спасибо за вашу помощь!
Вы в значительной степени правы в общей идее для объектных файлов. В «таблице, которая указывает, по каким адресам в файле» я бы заменил «адреса» на «смещения», но это только формулировка.
.файлы — это просто архивы (старый формат, предшествующий tar, но делающий то же самое). Вы можете заменить .a файлы на tar, если вы научили компоновщика распаковывать их и просто ссылаться на все содержащиеся в них .o файлы (более или менее, есть немного больше логики, чтобы не связываться с объектными файлами в архив, который не нужен, но это просто оптимизация).
.так что файлы разные. Они ближе к окончательному двоичному файлу, чем к объектному файлу. Файл .so со всеми разрешенными символами, по крайней мере, теоретически может быть запущен как программа. Фактически, с PIE (позиционно-независимыми исполняемыми файлами) разница между разделяемой библиотекой и программой составляет (по крайней мере в теории) всего несколько битов в заголовке. Они содержат инструкции для динамического компоновщика о том, как загрузить библиотеку (более или менее те же инструкции, что и в обычной программе), и таблицу перемещения, которая содержит инструкции, указывающие динамическому компоновщику, как разрешать внешние символы (опять же, то же самое в программе). , Все неразрешенные символы в динамической библиотеке (и программе) доступны через таблицы косвенного обращения, которые заполняются во время динамического связывания (запуска программы или dlopen
).
Если мы сильно упростим это, разница между объектами и разделяемыми библиотеками заключается в том, что в общей библиотеке было проделано гораздо больше работы, чтобы не перемещать текст (это не является строго необходимым и обязательным, но это общая идея). Это означает, что в объектных файлах ассемблер генерирует только заполнители для адресов, которые затем заполняет компоновщик, для общей библиотеки адреса заполняются адресами для перехода по таблицам, так что текст библиотеки не нужно менять, только ограниченный трамплин.
Btw. Я говорю на эльфе. В старых форматах было больше различий между программами и библиотеками.
То, что вы описали в своем вопросе (машинный код для функций, данные инициализации и таблицы перемещения), в значительной степени в точности совпадает с тем, что находится внутри файлов .o (объект) и .so (общий объект).
.a (архивы) — это, по сути, несколько .o (объектных) файлов, сгруппированных вместе для более удобного использования ссылок. («Ссылка на библиотеки»)
.Итак, файлы (общих объектов) содержат некоторые дополнительные метаданные, например, Другой .so будет необходимо связать в. (xyz.so может ссылаться на некоторые функции, которые находятся в abc.so, и информацию, с которой нужно было бы связать abc.so, плюс, необязательно, путь, где найти abc.so (RPATH). ), должны быть закодированы в xyz.so.)
Windows .dll (динамически подключаемая библиотека) файлы — это общие объекты (.so) с другим именем.
Отказ от ответственности: Это значительно упрощает ситуацию, но достаточно близко к «Правде (тм)», чтобы служить повседневным потребностям разработчиков.