Подтвердить что ты не робот

Вопрос ld linker: опция -whole-archive

Единственное реальное использование опции компоновщика --whole-archive, которую я видел, заключается в создании разделяемых библиотек из статических. Недавно я наткнулся на Makefile (ы), которые всегда используют этот параметр при связывании с внутренними статическими библиотеками. Это, конечно, заставляет исполняемые файлы излишне тянуть за код объекта без ссылок. Моя реакция на это заключалась в том, что это просто неправильно, я что-то упустил?

Второй вопрос, который у меня есть, связан с тем, что я прочитал относительно опции "весь архив", но не смог разобрать. Что-то, что опция --whole-archive должна использоваться при связывании со статической библиотекой, если исполняемый файл также связывается с общей библиотекой, которая, в свою очередь, имеет (частично) тот же объектный код, что и статическая библиотека. Это общая библиотека, а статическая библиотека перекрывается с точки зрения объектного кода. Использование этой опции приведет к тому, что все символы (независимо от использования) будут разрешены в исполняемом файле. Это должно избегать дублирования объектного кода. Это сбивает с толку, если символ реферируется в программе, он должен быть однозначно разрешен во время соединения, что это за дело о дублировании? (Простите меня, если этот абзац не является воплощением ясности)

Спасибо

4b9b3361

Ответ 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.