Construyo una biblioteca compartida "libMyLibrary.so" con una clase "MyClass" que contiene una variable estática de tipo "MyClass". Luego construyo un "MyLibraryTest" ejecutable que vinculo con "libMyLibrary.so". El programa principal usa "dlopen" para cargar dinámicamente un ".so" dado como argumento.

Mientras compila, la biblioteca y el ejecutable se generan en un directorio, por ejemplo, "buildDir / bin". Luego instalo la biblioteca en "installDir / lib" y el ejecutable en "installDir / bin" (eliminando la ruta de tiempo de ejecución del ejecutable).

Cuando ejecuto "buildDir / MyLibraryTest buildDir / MyLibrary.so" con LD_LIBRARY_PATH = buildDir, todo está bien.

Pero cuando ejecuto "buildDir / MyLibraryTest installDir / lib / MyLibrary.so" con LD_LIBRARY_PATH = installDir / lib, ocurre algo muy extraño: - El constructor de la variable estática se llama dos veces (una vez antes de dlopen, una vez durante dlopen) - Al final de la ejecución, se llama al destructor dos veces, y esto resulta en un bloqueo.

Aquí está mi código :

MyClass.h

#ifndef _MyClass_h__
#define _MyClass_h__

#include <string>

class MyClass
{
private:
    static MyClass myStaticObjOfMyClass;
public:
    MyClass(const std::string& name, bool trace);
    virtual ~MyClass();
private:
    std::string myName;
    bool myTrace;
};

#endif // _MyClass_h__

MyClass.cpp

#include "MyClass.h"
#include <iostream>

MyClass MyClass::myStaticObjOfMyClass("myStaticObjOfMyClass", true);

MyClass::MyClass(const std::string& name, bool trace) : myName(name), myTrace(trace)
{
    if (myTrace) std::cout << "MyClass::MyClass(name=" << myName << ", address=" << this << ")" << std::endl;
}

MyClass::~MyClass()
{
    if (myTrace) std::cout << "MyClass::~MyClass(name=" << myName << ", address=" << this << ")" << std::endl;
}

MyLibraryTest.cpp

#include <MyClass.h>
#include <iostream>
#include <string>
#include <dlfcn.h>

int main(int argc, char* argv[])
{
    const std::string sharedLibraryFullName((const char*)argv[1]);

    // std::cout << "Try to load library " << sharedLibraryFullName << std::endl;
    void* handle = NULL;
    std::cout << "dlopen(" << sharedLibraryFullName << ")" << std::endl;
    handle = dlopen(sharedLibraryFullName.c_str(), RTLD_LAZY | RTLD_GLOBAL);
    if (handle == NULL)
    {
        std::cout << "ERROR : Could not load shared library " << sharedLibraryFullName << std::endl;
    }
    else
    {
        std::cout << "OK, shared library " << sharedLibraryFullName << " is now loaded" << std::endl;
    }
}

Aquí están los comandos de compilación y enlace:

/usr/local/bin/g++  -DMyLibrary_DEFINED -DMyLibrary_EXPORTS  -O3 -DNDEBUG -fPIC   -o CMakeFiles/MyLibrary.dir/MyClass.cpp.o -c MyClass.cpp
/usr/local/bin/g++ -fPIC -O3 -DNDEBUG  -shared -Wl,-soname,libMyLibrary.so -o ../bin/libMyLibrary.so CMakeFiles/MyLibrary.dir/MyClass.cpp.o

Y, por último, esto es lo que sucede en el segundo caso (inicialización duplicada de la variable estática):

MyClass::MyClass(name=myStaticObjOfMyClass, address=0x7fa710cabb40)
dlopen(/tmp/Install/MyLibraryTest/lib/libMyLibrary.so)
MyClass::MyClass(name=myStaticObjOfMyClass, address=0x7fa710cabb40)
OK, shared library /tmp/Install/MyLibraryTest/lib/libMyLibrary.so is now loaded
MyClass::~MyClass(name=myStaticObjOfMyClass, address=0x7fa710cabb40)
MyClass::~MyClass(name=��ObjOfMyClass, address=0x7fa710cabb40)
*** glibc detected *** /tmp/Build/MyLibraryTest/Release/bin/MyLibraryTest: double free or corruption (fasttop): 0x0000000000cfb330 ***
======= Backtrace: =========
/lib64/libc.so.6[0x322f275dee]
/lib64/libc.so.6[0x322f278c3d]
/lib64/libc.so.6(__cxa_finalize+0x9d)[0x322f235d2d]
/tmp/Build/MyLibraryTest/Release/bin/libMyLibrary.so(+0x1076)[0x7fa710aab076]
======= Memory map: ========
00400000-00402000 r-xp 00000000 fd:00 1325638                            /tmp/Build/MyLibraryTest/Release/bin/MyLibraryTest
00601000-00602000 rw-p 00001000 fd:00 1325638                            /tmp/Build/MyLibraryTest/Release/bin/MyLibraryTest
00ce9000-00d1b000 rw-p 00000000 00:00 0                                  [heap]
322ee00000-322ee20000 r-xp 00000000 fd:00 545634                         /lib64/ld-2.12.so
322f020000-322f021000 r--p 00020000 fd:00 545634                         /lib64/ld-2.12.so
322f021000-322f022000 rw-p 00021000 fd:00 545634                         /lib64/ld-2.12.so
322f022000-322f023000 rw-p 00000000 00:00 0 
322f200000-322f38a000 r-xp 00000000 fd:00 545642                         /lib64/libc-2.12.so
322f38a000-322f58a000 ---p 0018a000 fd:00 545642                         /lib64/libc-2.12.so
322f58a000-322f58e000 r--p 0018a000 fd:00 545642                         /lib64/libc-2.12.so
322f58e000-322f590000 rw-p 0018e000 fd:00 545642                         /lib64/libc-2.12.so
322f590000-322f594000 rw-p 00000000 00:00 0 
322fa00000-322fa02000 r-xp 00000000 fd:00 545709                         /lib64/libdl-2.12.so
322fa02000-322fc02000 ---p 00002000 fd:00 545709                         /lib64/libdl-2.12.so
322fc02000-322fc03000 r--p 00002000 fd:00 545709                         /lib64/libdl-2.12.so
322fc03000-322fc04000 rw-p 00003000 fd:00 545709                         /lib64/libdl-2.12.so
3230600000-3230683000 r-xp 00000000 fd:00 545684                         /lib64/libm-2.12.so
3230683000-3230882000 ---p 00083000 fd:00 545684                         /lib64/libm-2.12.so
3230882000-3230883000 r--p 00082000 fd:00 545684                         /lib64/libm-2.12.so
3230883000-3230884000 rw-p 00083000 fd:00 545684                         /lib64/libm-2.12.so
7fa70c000000-7fa70c021000 rw-p 00000000 00:00 0 
7fa70c021000-7fa710000000 ---p 00000000 00:00 0 
7fa7102e7000-7fa7102e9000 r-xp 00000000 fd:00 1320668                    /tmp/Install/MyLibraryTest/lib/libMyLibrary.so
7fa7102e9000-7fa7104e8000 ---p 00002000 fd:00 1320668                    /tmp/Install/MyLibraryTest/lib/libMyLibrary.so
7fa7104e8000-7fa7104e9000 rw-p 00001000 fd:00 1320668                    /tmp/Install/MyLibraryTest/lib/libMyLibrary.so
7fa7104e9000-7fa7104ed000 rw-p 00000000 00:00 0 
7fa7104ed000-7fa710503000 r-xp 00000000 fd:00 708322                     /usr/local/lib64/libgcc_s.so.1
7fa710503000-7fa710702000 ---p 00016000 fd:00 708322                     /usr/local/lib64/libgcc_s.so.1
7fa710702000-7fa710703000 rw-p 00015000 fd:00 708322                     /usr/local/lib64/libgcc_s.so.1
7fa710703000-7fa710704000 rw-p 00000000 00:00 0 
7fa710704000-7fa710883000 r-xp 00000000 fd:00 708539                     /usr/local/lib64/libstdc++.so.6.0.21
7fa710883000-7fa710a83000 ---p 0017f000 fd:00 708539                     /usr/local/lib64/libstdc++.so.6.0.21
7fa710a83000-7fa710a8d000 r--p 0017f000 fd:00 708539                     /usr/local/lib64/libstdc++.so.6.0.21
7fa710a8d000-7fa710a8f000 rw-p 00189000 fd:00 708539                     /usr/local/lib64/libstdc++.so.6.0.21
7fa710a8f000-7fa710a94000 rw-p 00000000 00:00 0 
7fa710aa8000-7fa710aaa000 rw-p 00000000 00:00 0 
7fa710aaa000-7fa710aac000 r-xp 00000000 fd:00 1325633                    /tmp/Build/MyLibraryTest/Release/bin/libMyLibrary.so
7fa710aac000-7fa710cab000 ---p 00002000 fd:00 1325633                    /tmp/Build/MyLibraryTest/Release/bin/libMyLibrary.so
7fa710cab000-7fa710cac000 rw-p 00001000 fd:00 1325633                    /tmp/Build/MyLibraryTest/Release/bin/libMyLibrary.so
7fa710cac000-7fa710cad000 rw-p 00000000 00:00 0 
7fff2fc61000-7fff2fc76000 rw-p 00000000 00:00 0                          [stack]
7fff2fde5000-7fff2fde6000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
./test_dyn_libs.sh: line 21: 30880 Abandon                 (core dumped) ${BuildDir}/MyLibraryTest ${InstallDir}/lib/libMyLibrary.so
--- End of tests

Cualquier ayuda sería muy apreciada!

5
ConanLord 17 ene. 2018 a las 21:09

3 respuestas

La mejor respuesta

Gracias a todos por sus respuestas o comentarios. Como dijo Todd, olvidé incluir el comando de enlace para MyLibraryTest. Aquí está :

/usr/local/bin/g++  -O3 -DNDEBUG  -rdynamic CMakeFiles/MyLibraryTest.dir/MyLibraryTest.cpp.o  -o ../bin/MyLibraryTest -Wl,-rpath,/tmp/Build/MyLibraryTest/Release/bin: ../bin/libMyLibrary.so -ldl

El problema estaba relacionado con la RPATH. Si ya no uso la opción -Wl, -rpath, ¡la prueba funciona bien!

Utilizo CMake para construir mis proyectos, y encontré esto: https://cmake.org/Wiki/CMake_RPATH_handling. Ahora uso el siguiente comando en mi CMakeLists.txt, que elimina la opción de enlace -Wl, -rpath.

set (CMAKE_SKIP_RPATH ON)
0
ConanLord 22 ene. 2018 a las 12:33

Como comentó Todd Fleming, creo que probablemente haya vinculado por error su MyLibrary.so al ejecutable de su prueba cuando compila el ejecutable de la prueba.

Puede usar ldd para verificar si su ejecutable se ha vinculado a la biblioteca.

He probado su código en mi ubuntu linux con exactamente las mismas opciones de compilación. Para el ejecutable de prueba, MyLibrary.so no debe estar vinculado, por lo que no lo vinculé. Resulta que el comportamiento extraño no ocurrió. Cuando vinculé el ejecutable de prueba con MyLibrary.so, el resultado es exactamente lo que dijiste (incluido el volcado de glibc). No es demasiado sorprendente, porque cuando instala su biblioteca en otra ruta, el cargador la considerará como otra biblioteca totalmente irrelevante, por lo tanto, con doble carga.

PD: Lo que me sorprendió es que estos 2 singletons se colocan en la misma ubicación de memoria, es algo increíble. También lo probé (el caso cuando el ejecutable está vinculado a la biblioteca) en mi Mac OS X, resulta que en OS X, estas 2 instancias tienen una ubicación de memoria diferente. Es más razonable para mi.

ACTUALIZACIÓN:

Acerca de por qué estas 2 instancias tienen la misma ubicación de memoria, en realidad es una característica de la resolución de símbolos en Linux. Por favor, vea los comentarios para más detalles. Gracias n.m. por señalar

3
llllllllll 18 ene. 2018 a las 10:25

Creo que el problema de inicialización estática se resuelve si ocultas tu variable estática en un captador estático. Entonces, en el encabezado, reemplaza la variable de clase estática

static MyClass myStaticObjOfMyClass;

Con una función de clase estática

static MyClass& getInstance();

E implementarlo en el cpp:

static MyClass& getInstance()
{
    static MyClass instance("myStaticObjOfMyClass", true);
    return instance;
}

El objeto estático se inicializa cuando se llama a getInstance() por primera vez.

0
pschill 18 ene. 2018 a las 10:38