Что такое частичное связывание в GNU Linker?
Лучшее объяснение, которое мне удалось найти, было из официального документа:
-r --relocateable Генерировать перемещаемый вывод - т.е. генерировать выходной файл, который, в свою очередь, может служить вводом для ld. Это часто называют частичной связью. Как побочный эффект, в средах, которые поддерживают стандартные магические числа Unix, эта опция также устанавливает магическое число выходного файла в OMAGIC. Если эта опция не указана, создается абсолютный файл. При связывании программ на C++ эта опция не разрешает ссылки на конструкторы; чтобы сделать это, используйте -Ur. Эта опция делает то же самое, что и `-i'.
Мне особенно интересно знать, что происходит с символами, присутствующими во входных данных компоновщика. Возьмите конкретный случай, когда у меня есть статическая библиотека libstatic.a, которая содержит один объектный файл component.o. Теперь я хочу создать еще одну статическую библиотеку libfinal.a, которая будет работать в качестве интерфейса к libstatic.a. Я использую эту команду для ее создания:
ld -r -o libfinal.a wrapper.o -L. -lstatic
Где wrapper.o предоставляет эксклюзивные API для вызова функций, определенных в libstatic.a
Будет ли libfinal.a быть просто объединенным архивом, содержащим wrapper.o и component.o, или все ссылки, которые можно разрешить между wrapper.o и component.o, будут разрешены (связать), а затем помещены в libfinal.a?
Edit_1: обновление вопроса на основе достигнутого прогресса: objdump библиотеки компонентов libstatic.a
(objdump -D libstatic.a
) показывает .text
разделы отдельно для каждой функции (как и ожидалось). Тогда как в объединенной библиотеке libfinal.a
, который был создан путем частичного связывания (-r
флаг) есть только один .text
раздел. Я предполагаю, что это означает, что внутренняя ссылка имела место, и это не просто создание простого архива.
1 ответ
Минимальный исполняемый пример
Здесь я приведу минимальный пример и скомпилирую его двумя способами для получения функционально идентичных исполняемых файлов:
- один в сочетании
f12.c
файл без частичной ссылки на ссылку вf12.o
- два отдельных
f1.c
а такжеf2.c
которые сначала частично связаны вf12_r.o
main.c
#include <assert.h>
#include <stdlib.h>
int f_1_2(void);
int f_2_1(void);
int main(void) {
assert(f_1_2() + f_2_1() == 5);
return EXIT_SUCCESS;
}
f1.c
#include "f1.h"
f2.c
#include "f2.h"
f12.c
#include "f1.h"
#include "f2.h"
f1.h
int f_2(void);
int f_1_2(void) {
return f_2() + 1;
}
int f_1(void) {
return 1;
}
f2.h
int f_1(void);
int f_2_1(void) {
return f_1() + 1;
}
int f_2(void) {
return 2;
}
run.sh
#!/usr/bin/env bash
set -eux
cflags='-ggdb3 -std=c99 -O0 -fPIE -pie'
gcc $cflags -c -o f1.o f1.c
gcc $cflags -c -o f2.o f2.c
gcc $cflags -c -o f12.o f12.c
ld -o f12_r.o -r f1.o f2.o
gcc $cflags -c -o main.o main.c
gcc $cflags -o main.out f12.o main.o
gcc $cflags -o main_r.out f12_r.o main.o
./main.out
./main_r.out
Если мы попробуем то же самое, но без ld -r
тогда мы получим последние предупреждения:
+ ld -o f12_r.o f1.o f2.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
+ gcc -ggdb3 -std=c99 -O0 -fPIE -pie -o main_r.out f12_r.o main.o
/usr/bin/ld: error in f12_r.o(.eh_frame); no .eh_frame_hdr table will be created
ни один из них не заставляет инструмент выходить не равным 0, а финальный исполняемый файл все еще выполняется, поэтому я не уверен, насколько он плох. ТОДО понимаю.
Бинарный анализ
Если вы не знакомы с перемещением, сначала прочтите это: что делают компоновщики?
Ключевой вопрос заключается в том, как частичная ссылка может ускорить ссылку. Единственное, о чем я мог думать, - это разрешать ссылки в предварительно связанных файлах. Я сосредоточился на этом сейчас.
Тем не менее, он не делает этого, как указано в разделе: Разрешить относительные перемещения в частичном соединении, поэтому я ожидал бы, что это не значительно ускорит соединение.
Я подтвердил это:
objdump -S f12.o
objdump -S f12_r.o
оба из которых производят идентичные выходные данные, которые содержат:
int f_1_2(void) {
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
return f_2() + 1;
4: e8 00 00 00 00 callq 9 <f_1_2+0x9>
9: 83 c0 01 add $0x1,%eax
}
c: 5d pop %rbp
d: c3 retq
Итак, мы видим, что призыв к f_1_2
еще не разрешен ни в одном случае, потому что адрес относительного смещения по-прежнему равен 0: e8 00 00 00 00
(e8
это код операции).
Это также научило меня, что GCC не разрешает вызовы функций до окончательной ссылки, либо обоснование TODO, возможно ли заставить его разрешить?
эталонный тест
Я сравнил LD и GOLD по адресу: замена ld золотом - есть опыт? поэтому я решил использовать его повторно, чтобы увидеть, приводит ли частичное связывание к какому-либо ускорению ссылки.
Я сгенерировал тестовые объекты с помощью этого скрипта:
./generate-objects 100 1000 100
и затем я начал с самого экстремального варианта из возможных ссылок: предварительно связать все, кроме основного файла, а затем сравнить конечную ссылку:
mv main.o ..
ld -o partial.o -r *.o
time gcc partial.o ../main.o
time gcc -fuse-ld=gold partial.o ../main.o
Результаты настенных часов в секундах были следующими:
No partial link Partial link
No Gold 6.15 5.756
Gold 4.06 4.457
Следовательно:
- разница во времени существует, но не очень значительная
- без золота он шел быстрее, а с золотом - медленнее!
Поэтому, исходя из этого эксперимента, кажется, что частичное связывание может вообще не ускорить ваше соединение, и я бы просто порекомендовал вам вместо этого попробовать GOLD.
Дайте мне знать, если вы можете привести конкретный пример, где добавочное связывание приводит к значительному ускорению.
Пример из практики: ядро Linux
Ядро Linux является одним из примеров большого проекта, который использовал инкрементные ссылки, поэтому, возможно, мы сможем чему-то научиться из этого.
С тех пор он переехал в ar T
тонкие архивы, как показано на: https://unix.stackexchange.com/questions/5518/what-is-the-difference-between-the-following-kernel-makefile-terms-vmlinux-vml/482978
Первоначальная фиксация и обоснование находятся по адресу: a5967db9af51a84f5e181600954714a9e4c69f1f (включено в v4.9
) сообщение о фиксации которого гласит:
ld -r is an incremental link used to create built-in.o files in build
subdirectories. It produces relocatable object files containing all
its input files, and these are are then pulled together and relocated
in the final link. Aside from the bloat, this constrains the final
link relocations, which has bitten large powerpc builds with
unresolvable relocations in the final link.
это также упомянуто в Documentation / process / changes.rst:
Binutils
--------
The build system has, as of 4.13, switched to using thin archives (`ar T`)
rather than incremental linking (`ld -r`) for built-in.a intermediate steps.
This requires binutils 2.20 or newer.
TODO: узнайте, когда было введено инкрементное связывание, и посмотрите, есть ли минимальный тестовый пример, который мы можем использовать, чтобы увидеть его ускорение.: https://unix.stackexchange.com/questions/491312/why-does-the-linux-kernel-build-system-use-incremental-linking-or-ar-t-thin-arch, встроенная система использование инкрементного сшивания или ар-трет-тонкие арки
Протестировано на Ubuntu 18.10, GCC 8.2.0, ноутбуке Lenovo ThinkPad P51, процессоре Intel Core i7-7820HQ (4 ядра / 8 потоков), 2x оперативной памяти Samsung M471A2K43BB1-CRC (2x 16 ГБ), Samsung MZVLB512HAJQ-000L7 SSD (3000 МБ / с)).
ld
создает исполняемые файлы и разделяемые библиотеки, а не архивы объектных файлов (файлы.a).
ar
создает и изменяет архивы объектных файлов.
-r, --relocateable
Эта опция полезна, когда вы хотите разрешить определенные (неразрешенные) символы .so
и произвести другое .so
,