В чем разница между --start-group и --whole-archive в ld - PullRequest
0 голосов
/ 21 сентября 2018

Если честно, я думаю, что этот вопрос должен быть легким, если смотреть на man ld.Однако, читая man-страницу и читая код, созданный другими, я обнаружил, что люди используют их взаимозаменяемо или одновременно, когда они думают, что может быть проблема с порядком библиотек, передаваемых компоновщику.

IМне интересно, в чем разница между этими двумя вариантами и каков наилучший способ их использования.

Спасибо!

Ссылки по теме:

1 Ответ

0 голосов
/ 21 сентября 2018

На момент написания статьи вики-тег Stackoverflow для статических библиотек сообщает нам:

Статическая библиотека - это архив объектных файлов.Используемый в качестве входных данных компоновщика, компоновщик извлекает объектные файлы, необходимые ему для связи.

Необходимые объектные файлы - это те, которые предоставляют компоновщику определения для символов, которые он находит, используются без определения вдругие входные файлы.Необходимые объектные файлы и никакие другие извлекаются из архива и вводятся в связывание точно так, как если бы они были отдельными входными файлами в команде связывания, а статическая библиотека вообще не упоминалась.

...

Обычно компоновщик поддерживает опцию (GNU ld: --whole-archive, MS link: / WHOLEARCHIVE), чтобы переопределить обработку статических библиотек по умолчанию и вместо этого связать все содержащиеся в нем объектные файлы, если они необходимыили нет.

Статическая библиотека ничего не вносит в связь, кроме объектных файлов, которые извлекаются из нее, которые могут различаться в разных связях.Он должен быть противопоставлен совместно используемой библиотеке, другому типу файла, с совершенно другой ролью в связывании.

Это должно прояснить, что делает --whole-archive.Область действия --whole-archive продолжается до конца командной строки компоновщика или до появления --no-whole-archive 1 .

По умолчанию компоновщик будет проверять статическую библиотеку только в каждой точке, где этоБиблиотека встречается в последовательности командной строки входов компоновщика.Он не пойдет назад, чтобы пересмотреть статическую библиотеку ради разрешения ссылок на символы, которые он обнаруживает в последующих входных данных.

Пара параметров --start-group ... --end-group изменяет это поведение по умолчанию.Он предписывает компоновщику проверять статические библиотеки, упомянутые в ... несколько раз , в указанном порядке, пока это приводит к любым новым разрешениям новых ссылок на символы.--start-group ... --end-group не влияет на выбор по умолчанию компоновщиком объектных файлов из статических библиотек в ....Он будет извлекать и связывать только те объектные файлы, которые ему нужны , если только --whole-archive не также в действии.

Подводя итог: -

--start-group lib0.a ... libN.a --end-group сообщает компоновщику: Продолжайте искать в lib0.a ... libN.a необходимые вам объектные файлы, пока не найдете больше .

--whole-archive lib0.a ... libN.a --no-whole-archive подсказоккомпоновщик: Забудьте о том, что вам нужно.Просто свяжите все объектные файлы во всех lib0.a ... libN.a.

Тогда вы можете видеть, что любая связка, которую вы можете установить, преуспеет с --start-group lib0.a ... libN.a --end-group, также преуспеет с --whole-archive lib0.a ... libN.a --no-whole-archive, потому что последний будет связыватьвсе необходимые объектные файлы и все ненужные, не удосужившись заметить разницу.

Но обратное неверно.Вот тривиальный пример:

xc

#include <stdio.h>

void x(void)
{
    puts(__func__);
}

yc

#include <stdio.h>

void y(void)
{
    puts(__func__);
}

main.c

extern void x(void);

int main(void)
{
    x();
    return 0;
}

Скомпилировать все исходные файлы:

$ gcc -Wall -c x.c y.c main.c

Создать статическую библиотеку, архивируя x.o и y.o:

ar rcs libxy.a x.o y.o

Попыткасвязать программу с main.o и libxy.a в неправильном порядке :

$ gcc -o prog libxy.a main.o
main.o: In function `main':
main.c:(.text+0x5): undefined reference to `x'
collect2: error: ld returned 1 exit status

Не удалось, поскольку ссылка на x в main.o была обнаружена толькокомпоновщик слишком поздно, чтобы найти определение x в libxy.a(x.o).Он достиг libxy.a first и не нашел объектных файлов, которые ему нужны.На тот момент он еще не связывал никакие объектные файлы с программой, поэтому было необходимо 0 ссылок на символы, которые нужно было разрешить.Рассмотрев libxy.a и не найдя для него никакой пользы, он больше его не рассматривает.

Правильная связь, конечно, такова:

$ gcc -o prog main.o libxy.a

, но если вы не понимаете,что у вас просто есть порядок связи задом наперед, вы можете получить связь, чтобы преуспеть с помощью --whole-archive:

$ gcc -o prog -Wl,--whole-archive libxy.a -Wl,--no-whole-archive main.o
$ ./prog
x

Очевидно, что вы не можете добиться успеха с помощью

$ gcc -o prog -Wl,--start-group libxy.a -Wl,--end-group main.o
main.o: In function `main':
main.c:(.text+0x5): undefined reference to `x'
collect2: error: ld returned 1 exit status

потому что это ничем не отличается от:

$ gcc -o prog libxy.a main.o

Теперь вот пример связи, которая не работает с поведением по умолчанию, но может бытьсделано, чтобы преуспеть с --start-group ... --end-group.

ac

#include <stdio.h>

void a(void)
{
    puts(__func__);
}

bc

#include <stdio.h>

void b(void)
{
    puts(__func__);
}

ab.c

extern void b(void);

void ab(void)
{
    b();
}

ba.c

extern void a(void);

void ba(void)
{
    a();
}

abba.c

extern void ab(void);
extern void ba(void);

void abba(void)
{
    ab();
    ba();
}

main2.c

extern void abba(void);

int main(void)
{
    abba();
    return 0;
}

Скомпилируйте все источники: -

$ gcc -Wall a.c b.c ab.c ba.c abba.c main2.c

Затем создайте следующие статические библиотеки:

$ ar rcs libbab.a ba.o b.o x.o
$ ar rcs libaba.a ab.o a.o y.o
$ ar rcs libabba.a abba.o

(Примечаниечто эти старые объектные файлы x.o и y.o снова заархивированы).

Здесь libabba.a зависит от libbab.a и libaba.a.В частности, libabba.a(abba.o) делает ссылку на ab, который определен в libaba.a(ab.o);и он также ссылается на ba, который определен в libbab.a(ba.o).Таким образом, в порядке связи libabba.a должно произойти до того, как libbab.a и libaba.a

И libbab.a зависит от libaba.a.В частности, libbab.a(ba.o) делает ссылку на a, который определен в libaba(a.o).

Но libaba.a также зависит от libbab.a.libaba(ab.o) делает ссылку на b, который определен в libbab(b.o).Существует круговая зависимость между libbab.a и libaba.a.Поэтому, какой бы из них мы не разместили первым в связи по умолчанию, он потерпит неудачу с неопределенными ссылочными ошибками.Либо так:

$ gcc -o prog2 main2.o libabba.a libaba.a libbab.a
libbab.a(ba.o): In function `ba':
ba.c:(.text+0x5): undefined reference to `a'
collect2: error: ld returned 1 exit status

Или так:

$ gcc -o prog2 main2.o libabba.a libbab.a libaba.a
libaba.a(ab.o): In function `ab':
ab.c:(.text+0x5): undefined reference to `b'
collect2: error: ld returned 1 exit status

A круговая зависимость - это проблема, для которой --start-group ... --end-group является решением:

$ gcc -o prog2 main2.o libabba.a -Wl,--start-group libbab.a libaba.a -Wl,--end-group
$ ./prog2
b
a

Следовательно, --whole-archive ... --no-whole-archive также является решением:

$ gcc -o prog2 main2.o libabba.a -Wl,--whole-archive libbab.a libaba.a -Wl,--no-whole-archive
$ ./prog2
b
a

Но это решение плохо .Давайте проследим, какие из наших объектных файлов фактически связаны с программой в каждом конкретном случае.

С --start-group ... --end-group:

$ gcc -o prog2 main2.o libabba.a -Wl,--start-group libbab.a libaba.a -Wl,--end-group -Wl,--trace
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libaba.a)ab.o
(libaba.a)a.o
(libbab.a)b.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o

они:

main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libaba.a)ab.o
(libaba.a)a.o
(libbab.a)b.o

, которыеименно те, которые необходимы в программе.

с --whole-archive ... --no-whole-archive:

$ gcc -o prog2 main2.o libabba.a -Wl,--whole-archive libbab.a libaba.a -Wl,--no-whole-archive -Wl,-trace
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libbab.a)b.o
(libbab.a)x.o
(libaba.a)ab.o
(libaba.a)a.o
(libaba.a)y.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o

они:

main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libbab.a)b.o
(libbab.a)x.o
(libaba.a)ab.o
(libaba.a)a.o
(libaba.a)y.o

то же самое, что и раньше, плюс:

(libbab.a)x.o
(libaba.a)y.o

, которые являются мертвым кодом (и их могло быть больше, без ограничений).Избыточные функции x() и y() определены в образе:

$ nm prog2 | egrep 'T (x|y)'
000000000000067a T x
00000000000006ac T y

Еда на вынос

  • Используйте связывание по умолчанию, переводя входные данные в зависимостьПорядок, если вы можете.
  • Используйте --start-group ... --end-group для преодоления циклических зависимостей между библиотеками, когда вы не можете исправить библиотеки.Имейте в виду, что скорость связывания сработает.
  • Используйте --whole-archive ... --no-whole-archive только в том случае, если на самом деле нужно для связывания всех объектных файлов во всех статических библиотеках в ....В противном случае выполните одно из двух предыдущих действий.


[1] И помните, что командная строка компоновщика , когда GCC вызывает компоновщик, на самом деле оченьдольше, чем параметры связывания, которые вы явно передаете в командной строке GCC, с опционально добавленными опциями.Поэтому всегда закрывайте --whole-archive с помощью --no-whole-archive и закрывайте --start-group с помощью --end-group.
...