Я только что отладил странную проблему, когда у меня есть две библиотеки, назовем это libA.so и libB.so
Приложение dlopens libA.so (РЕДАКТИРОВАТЬ: это не так, этосвязан с опцией -l), которая является тонкой библиотекой, которая затем загружает libB.so, который является фактической реализацией.
dlopen вызывается с использованием опции RTLD_NOW, другие опции не передаются.
И обе библиотеки используют один и тот же модуль регистрации, в котором состояние регистратора хранится в глобальной переменной, так как обе используют один и тот же регистратор и статически связаны с ними, глобальная переменная в обеих из них имеет одно и то же имя.
Когда libB загружен, две глобальные переменные находятся по одному адресу и конфликтуют. Поэтому динамический компоновщик повторно использовал адрес переменной, чтобы использовать эту же переменную в libB.
Если это важно, эта переменная определена глубоко в файле .cpp, я не уверен, что связь между C и C ++разные.
Чтение документации dlopen там написано:
RTLD_GLOBAL
Символы, определенные этой библиотекой, будутбыть доступным для разрешения символов в последующих загруженных библиотеках.
RTLD_LOCAL
Это обратное RTLD_GLOBAL и значение по умолчанию, если ни один из флагов не указан. Символы, определенные в этой библиотеке, не доступны для разрешения ссылок в впоследствии загруженных библиотеках.
Таким образом, RTLD_LOCAL должен быть значением по умолчанию, то есть символы libA не должны использоваться при разрешении символов libB. Но это все еще происходит. Почему?
В качестве обходного пути я добавил опцию видимости («скрытый») в этот глобальный объект, чтобы избежать экспорта. И поднял тикет, чтобы все символы были скрыты по умолчанию, чтобы подобные коллизии не происходили в будущем, но я все еще задаюсь вопросом, почему это происходит, когда этого не должно быть.
EDIT2:
Пример источника:
commonvar.h:
#pragma once
#include <iostream>
struct A
{
A()
{
std::cout << "A inited. Address: " << this << "\n";
}
virtual ~A() {}
};
extern A object;
struct POD
{
int x, y, z;
};
extern POD pod;
commonvar.cpp:
#include <string>
#include "commonvar.h"
A object;
POD pod = {1, 2, 3};
ах:
#pragma once
extern "C" void foo();
a.cpp:
#include <iostream>
#include "commonvar.h"
using FnFoo = void (*)();
extern "C" void foo()
{
std::cout << "A called.\n";
std::cout << "A: Address of foo is: " << &object << "\n";
std::cout << "A: Address of pod is: " << &pod << "\n";
std::cout << "A: {" << pod.x << ", " << pod.y << ", " << pod.z << "}\n";
pod.x = 42;
}
b.cpp:
#include <iostream>
#include <string>
#include "commonvar.h"
extern "C" void foo()
{
std::cout << "B called.\n";
std::cout << "B: Address of foo is: " << &object << "\n";
std::cout << "B: Address of pod is: " << &pod << "\n";
std::cout << "B: {" << pod.x << ", " << pod.y << ", " << pod.z << "}\n";
}
main.cpp:
#include <dlfcn.h>
#include <iostream>
#include <cassert>
#include "a.h"
using FnFoo = void (*)();
int main()
{
std::cout << "Start of program.\n";
foo();
std::cout << "Loading B\n";
void *b = dlopen("libb.so", RTLD_NOW);
assert(b);
FnFoo fnB;
fnB = FnFoo(dlsym(b, "foo"));
assert(fnB);
fnB();
}
Сценарий сборки:
#!/bin/bash
g++ -fPIC -c commonvar.cpp
ar rcs common.a commonvar.o
g++ -fPIC -shared a.cpp common.a -o liba.so
g++ -fPIC -shared b.cpp common.a -o libb.so
g++ main.cpp liba.so -ldl -o main
Динамические символы main:
U __assert_fail
0000000000202010 B __bss_start
U __cxa_atexit
w __cxa_finalize
U dlopen
U dlsym
0000000000202010 D _edata
0000000000202138 B _end
0000000000000bc4 T _fini
U foo
w __gmon_start__
0000000000000860 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U __libc_start_main
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
0000000000202020 B _ZSt4cout
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
Динамические символы liba.so:
0000000000202064 B __bss_start
U __cxa_atexit
w __cxa_finalize
0000000000202064 D _edata
0000000000202080 B _end
0000000000000e6c T _fini
0000000000000bba T foo
w __gmon_start__
0000000000000a30 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000202070 B object
0000000000202058 D pod
U _ZdlPvm
0000000000000dca W _ZN1AC1Ev
0000000000000dca W _ZN1AC2Ev
0000000000000e40 W _ZN1AD0Ev
0000000000000e22 W _ZN1AD1Ev
0000000000000e22 W _ZN1AD2Ev
U _ZNSolsEi
U _ZNSolsEPKv
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
0000000000201dd0 V _ZTI1A
0000000000000ed5 V _ZTS1A
0000000000201db0 V _ZTV1A
U _ZTVN10__cxxabiv117__class_type_infoE
Динамические символы libb.so:
$ nm -D libb.so
0000000000202064 B __bss_start
U __cxa_atexit
w __cxa_finalize
0000000000202064 D _edata
0000000000202080 B _end
0000000000000e60 T _fini
0000000000000bba T foo
w __gmon_start__
0000000000000a30 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000202070 B object
0000000000202058 D pod
U _ZdlPvm
0000000000000dbe W _ZN1AC1Ev
0000000000000dbe W _ZN1AC2Ev
0000000000000e34 W _ZN1AD0Ev
0000000000000e16 W _ZN1AD1Ev
0000000000000e16 W _ZN1AD2Ev
U _ZNSolsEi
U _ZNSolsEPKv
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
0000000000201dd0 V _ZTI1A
0000000000000ec9 V _ZTS1A
0000000000201db0 V _ZTV1A
U _ZTVN10__cxxabiv117__class_type_infoE
Вывод:
A inited. Address: 0x7efd6cf97070
Start of program.
A called.
A: Address of foo is: 0x7efd6cf97070
A: Address of pod is: 0x7efd6cf97058
A: {1, 2, 3}
Loading B
A inited. Address: 0x7efd6cf97070
B called.
B: Address of foo is: 0x7efd6cf97070
B: Address of pod is: 0x7efd6cf97058
B: {42, 2, 3}
Как видно, адреса переменных конфликтуют, а адрес функции - нет.
Более того, инициализация C ++ своеобразна: агрегаты переменной pod
инициализируются только после того, как выможно увидеть, что вызов foo () изменяет его, но когда загружается B, он не инициализирует его заново, а вызывает конструктор для полного объекта при загрузке libb.so.