Вопрос ld linker: опция -whole-archive
Единственное реальное использование опции компоновщика --whole-archive
, которую я видел, заключается в создании разделяемых библиотек из статических. Недавно я наткнулся на Makefile (ы), которые всегда используют этот параметр при связывании с внутренними статическими библиотеками. Это, конечно, заставляет исполняемые файлы излишне тянуть за код объекта без ссылок. Моя реакция на это заключалась в том, что это просто неправильно, я что-то упустил?
Второй вопрос, который у меня есть, связан с тем, что я прочитал относительно опции "весь архив", но не смог разобрать. Что-то, что опция --whole-archive
должна использоваться при связывании со статической библиотекой, если исполняемый файл также связывается с общей библиотекой, которая, в свою очередь, имеет (частично) тот же объектный код, что и статическая библиотека. Это общая библиотека, а статическая библиотека перекрывается с точки зрения объектного кода. Использование этой опции приведет к тому, что все символы (независимо от использования) будут разрешены в исполняемом файле. Это должно избегать дублирования объектного кода. Это сбивает с толку, если символ реферируется в программе, он должен быть однозначно разрешен во время соединения, что это за дело о дублировании? (Простите меня, если этот абзац не является воплощением ясности)
Спасибо
Ответы
Ответ 1
Существует законное использование --whole-archive
при связывании исполняемого файла со статическими библиотеками. Одним из примеров является создание кода C++, где глобальные экземпляры "регистрируются" в своих конструкторах (предупреждение: непроверенный код):
handlers.h
typedef void (*handler)(const char *data);
void register_handler(const char *protocol, handler h);
handler get_handler(const char *protocol);
handlers.cc (часть libhandlers.a)
typedef map<const char*, handler> HandlerMap;
HandlerMap m;
void register_handler(const char *protocol, handler h) {
m[protocol] = h;
}
handler get_handler(const char *protocol) {
HandlerMap::iterator it = m.find(protocol);
if (it == m.end()) return nullptr;
return it->second;
}
http.cc (часть libhttp.a)
#include <handlers.h>
class HttpHandler {
HttpHandler() { register_handler("http", &handle_http); }
static void handle_http(const char *) { /* whatever */ }
};
HttpHandler h; // registers itself with main!
main.cc
#include <handlers.h>
int main(int argc, char *argv[])
{
for (int i = 1; i < argc-1; i+= 2) {
handler h = get_handler(argv[i]);
if (h != nullptr) h(argv[i+1]);
}
}
Обратите внимание, что в http.cc
нет символов, которые нужны main.cc
. Если вы связываете это как
g++ main.cc -lhttp -lhandlers
вы не получите обработчик http, связанный с основным исполняемым файлом, и не сможете вызвать handle_http()
. Сравните это с тем, что происходит, когда вы ссылаетесь как:
g++ main.cc -Wl,--whole-archive -lhttp -Wl,--no-whole-archive -lhandlers
Такой же стиль "самостоятельной регистрации" возможен и в обычном C, например с расширением __attribute__((constructor))
GNU.
Ответ 2
Еще одно законное использование для --whole-archive
- это для разработчиков инструментальных средств для распространения библиотек, содержащих несколько функций в одной статической библиотеке. В этом случае провайдер понятия не имеет, какие части библиотеки будут использоваться потребителем и поэтому должны включать все.
Ответ 3
Я согласен с тем, что использование -all-архива для сборки исполняемых файлов, вероятно, не то, что вы хотите (из-за связи в ненужном коде и создании раздутого программного обеспечения). Если у них есть все основания для этого, они должны были задокументировать его в системе сборки, так как теперь вам остается гадать.
Что касается вашей второй части вопроса. Если исполняемый файл связывает как статическую библиотеку, так и динамическую библиотеку, которая имеет (частично) тот же объектный код, что и статическая библиотека, тогда -все-архив гарантирует, что во время ссылки предпочтительнее код из статической библиотеки. Обычно это то, что вы хотите, когда статичное связывание.
Ответ 4
Старый запрос, но по вашему первому вопросу ( "Почему" ), я видел - весь архив, используемый также для внутренних библиотек, в первую очередь для обхода круговых ссылок между этими библиотеками. Он, как правило, скрывает плохую архитектуру библиотек, поэтому я бы не рекомендовал его. Однако это быстрый способ получить быстрое испытание.
Для вашего второго запроса, если один и тот же символ присутствует в общем объекте и в статической библиотеке, компоновщик будет удовлетворять ссылке с той библиотекой, с которой она встречается в первую очередь.
Если общая библиотека и статическая библиотека имеют точный общий доступ к коду, все это может работать. Но там, где общая библиотека и статическая библиотека имеют разные реализации одних и тех же символов, ваша программа все равно будет компилироваться, но будет вести себя по-разному в зависимости от порядка библиотек.
Принуждение всех символов, которые будут загружаться из статической библиотеки, является одним из способов устранения путаницы в отношении того, что загружено с того места. Но в целом это звучит как решение неправильной проблемы; вам в основном не нужны одинаковые символы в разных библиотеках.
Ответ 5
Дополнительным хорошим сценарием, в котором --whole-archive
хорошо используется, является использование статических библиотек и инкрементной привязки.
Предположим, что:
-
libA
реализует функции a()
и b()
.
- Некоторая часть программы должна быть привязана только к
libA
, например. из-за некоторой обертывания функций с помощью --wrap
(классический пример malloc
)
-
libC
реализует функции c()
и использует a()
- последняя программа использует
a()
и c()
Инкрементные шаги связывания могут быть:
ld -r -o step1.o module1.o --wrap malloc --whole-archive -lA
ld -r -o step2.o step1.o module2.o --whole-archive -lC
cc step3.o module3.o -o program
Не удалось вставить - whall-archive разделил бы функцию c()
, которая в любом случае используется program
, предотвращая правильный процесс компиляции.
Конечно, это особый угловой случай, в котором необходимо выполнить инкрементную привязку, чтобы избежать обертывания всех вызовов на malloc
во всех модулях, но это случай, который успешно поддерживается --whole-archive
.