Заставить компоновщик сбой с ошибкой с множественным определением, даже если включить --все архив
Этот пример состоит из нескольких файлов:
// baz.cxx
int wat = 0;
int counter = ++wat;
// foo.cxx (empty)
// bar.cxx (empty)
// main.cxx
#include <iostream>
extern int wat;
int main() {
std::cout << wat << '\n';
}
// makefile
run : main.cxx foo.so bar.so
g++ -std=c++11 $^ -o [email protected]
baz.a : baz.cxx
g++ -std=c++11 -c $^ -o baz.o -fPIC
ar rcs [email protected] baz.o
%.so : %.cxx baz.a
g++ -std=c++11 $< -Wl,--whole-archive baz.a -Wl,--no-whole-archive -o [email protected] -shared -fPIC
As-is, если вы просто запустите make && LD_LIBRARY_PATH=. ./run
, все с компиляцией, сборкой, ссылкой, запуском и выпуском 2
. Это связано с тем, что foo.so
и bar.so
предоставляют wat
, а инициализация для counter
выполняется дважды.
Есть ли способ каким-то образом заставить run
не связываться с ошибкой множественного определения в этом случае, сохраняя при этом, что оба foo.so
и bar.so
имеют определение для wat
?
Ответы
Ответ 1
Вы можете использовать сценарии компоновщика libfoo.so
и libbar.so
со статической частью, которая создает конфликт символов (и устанавливать фактические DSO в некотором неочевидном месте, так что -lfoo
и -lbar
не выбирают их вверх).
Что-то вроде этого:
Это приводит к:
g++ -std=c++11 main.cxx -o run -L. -lfoo -lbar
conflict.o:(.bss+0x0): multiple definition of `conflict'
conflict.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status
Это приведет к обнаружению конфликтов только в том же запуске редактора ссылок. Вы все равно можете связать два разных DSO с -lfoo
и -lbar
и связать эти два в приложении, без ошибок в редакторе ссылок.
Аналогичная проблема решается аннотациями ABI, но для этого нужны конкретные изменения ld
.
Если проверка времени выполнения является приемлемой, у вас может быть список слабых символов, определяемых каждым DSO, который должен конфликтовать, и реализовать конструктор ELF, который прерывает процесс, если определено более одного из них. Я не знаю ни о чем, что достигает чего-то столь же надежного в статическом времени ссылки с помощью инструментальной цепочки GNU. Возможно, это стоит ошибка RFE против binutils.
Ответ 2
Я думаю, что единственный правильный способ решить проблему - переместить содержимое решения bar.a
в некоторую разделяемую библиотеку и, таким образом, решить проблему наследования алмазов... но я думаю, что это выходит за рамки этой проблемы.
Единственное, что я могу думать для вас, это создать "настраиваемый" валидатор, выполненный непосредственно перед связыванием окончательного исполняемого файла. Stg вот так:
nm *.so | \ # get all symbols
grep " [B|T] " | \ # only exported ones
egrep -v "__bss_start|_end|_fini|_init" | \ # filter out some commons, probably to be extended
awk '{print $3}' | \ # get only symbol name
sort | uniq -c | \ # sort and count
egrep -v "^[ ]+1 " # get only those that have multiple definitions
Это печатает все сильные (экспортированные) символы из библиотек, которые определены более одного раза. Вы можете легко обернуть его в script, который возвращает код состояния ошибки, если вывод не пуст со значимым сообщением.
Экспериментальная версия вашего исправленного make файла будет выглядеть так:
run: main.cxx foo.so bar.so
! nm foo.so bar.so | grep " [B|T] " | egrep -v "__bss_start|_end|_fini|_init" | awk '{print $3}' | sort | uniq -c | egrep -v "^[ ]+1 "
g++ -std=c++11 $^ -o [email protected]
(обратите внимание !
на обратный код завершения финальной grep, который ищет любой вывод uniq -c
, который не начинается с голого 1)
Я знаю, что это действительно хакерское, уродливое и непереносимое решение, но я подумал, что это может иметь значение в таких случаях, как ваши.