Pregunta

I'm playing with dylib multiple loads and try to understand what makes the symbols differents.

Here are my steps:

  • Build lib_a.dylib with the following entry point:

    FactoryA : IFActory () {}
    extern "C" IFactory* GetFactory () { return new FactoryA(); }
    
  • Copy lib_a.dylib to lib_b.dylib
  • Load GetFactory from lib_a.dylib and lib_b.dylib

    void * module=dlopen(fileName,RTLD_LAZY);
    void * proc = (void *)dlsym(module, "GetFactory");        
    

When loading the second dylib (lib_b.dylib), GetFactory is considered as already defined by lib_a.dylib.
Effectively, nm output has the same result.

But I thought the compilation tag -two_level_namespace garantee that the 2 dylib are in a kind of different namespace, am I wrong ?

What could I change to have my two dylib loaded ?


Below is my test.

myclass.h :

#include <stdio.h>  
class IFactory {  
public:  
    virtual int GetCount() = 0;  
};  
extern "C"  
{
    extern IFactory* GetFactory ();
}

myclass.cpp

#include <stdio.h>
#include "myclass.h"

class MyFactory : public IFactory {
public:
    virtual int GetCount() { mCount++; return mCount; }
    static int mCount;
};
int MyFactory::mCount = 0;


IFactory* GetFactory () {
    return new MyFactory;
}

mytest.cpp

#include <stdio.h>
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include "myclass.h"

typedef IFactory* (*factoryPtr)();

int main()
{  
    void* handleA = dlopen("libmylib.dylib", RTLD_LAZY);
    factoryPtr functionA = (IFactory*(*)())dlsym(handleA, "GetFactory");
    IFactory*  factoryA = (*functionA)();
    fprintf(stderr, "Handle A: %p\tFunction A: %p\t Count: %d\n", handleA, functionA, factoryA->GetCount());

    // Reload same library
    void* handleB = dlopen("libmylib.dylib", RTLD_LAZY);
    factoryPtr functionB = (IFactory*(*)())dlsym(handleB, "GetFactory");
    IFactory*  factoryB = (*functionB)();
    fprintf(stderr, "Handle B: %p\tFunction B: %p\t Count: %d\n", handleB, functionB, factoryB->GetCount());

    // Load copy of first library (just rename)
    void* handleC = dlopen("libmylib_copy.dylib", RTLD_LAZY);
    factoryPtr functionC = (IFactory*(*)())dlsym(handleC, "GetFactory");
    IFactory*  factoryC = (*functionC)();
    fprintf(stderr, "Handle C: %p\tFunction C: %p\t Count: %d\n", handleC, functionC, factoryC->GetCount());

    return 0;
}   

Command :

clang++ -dynamiclib myclass.cpp -o libmylib.dylib  
cp libmylib.dylib libmylib_copy.dylib  
clang++ mytest.cpp -o mytest  
./mytest

Output :

Handle A: 0x7fe5dac039b0    Function A: 0x106d49d30  Count: 1    
Handle B: 0x7fe5dac039b0    Function B: 0x106d49d30  Count: 2    
Handle C: 0x7fe5dac03e00    Function C: 0x106d7cd30  Count: 3  

Why do we have count = 3 at the end ?


The attribute -fvisibility=hidden -fvisibility-inlines-hidden allow to do the same.

Modified myclass.h :

#include <stdio.h>  
#define EXPORT_FACTORY  __attribute__ ((visibility ("default")))
class IFactory {  
public:  
    virtual int GetCount() = 0;  
};   
extern "C"  
{
    extern EXPORT_FACTORY IFactory* GetFactory ();
}

Build :

clang++ -dynamiclib myclass.cpp  -o libmylib.dylib  -fvisibility=hidden -fvisibility-inlines-hidden
cp libmylib.dylib libmylib_copy.dylib
clang++ mytest.cpp -o mytest
./mytest

Output :

Handle A: 0x7fe078c039b0    Function A: 0x1076e1c00  Count: 1
Handle B: 0x7fe078c039b0    Function B: 0x1076e1c00  Count: 2  
Handle C: 0x7fe078c03e20    Function C: 0x107714c00  Count: 1
¿Fue útil?

Solución

The underlying issue here is the visibility of the symbols within your library. You can see via nm -m that there are several external symbols besides the GetFactory function you intend to export:

$ nm -m libmylib.dylib
0000000000000f30 (__TEXT,__text) external _GetFactory
0000000000001068 (__DATA,__common) external __ZN9MyFactory6mCountE
0000000000000f50 (__TEXT,__text) weak external __ZN9MyFactory8GetCountEv
0000000000001038 (__DATA,__data) weak external __ZTI8IFactory
0000000000001050 (__DATA,__data) weak external __ZTI9MyFactory
0000000000000f91 (__TEXT,__const) weak external __ZTS8IFactory
0000000000000f86 (__TEXT,__const) weak external __ZTS9MyFactory
0000000000001020 (__DATA,__data) weak external __ZTV9MyFactory
                 (undefined) external __ZTVN10__cxxabiv117__class_type_infoE (from libc++)
                 (undefined) external __ZTVN10__cxxabiv120__si_class_type_infoE (from libc++)
                 (undefined) weak external __Znwm (from libc++)
                 (undefined) external dyld_stub_binder (from libSystem)

It is the symbols marked as weak external that are the cause of your problem.

Running your test application with DYLD_PRINT_BINDINGS=YES reveals:

$ DYLD_PRINT_BINDINGS=YES ./mytest
[ … output showing initialization of libstdc++.dylib and libmylib.dylib omitted …]
Handle A: 0x7fc729c03810    Function A: 0x102a51ee0  Count: 1
Handle B: 0x7fc729c03810    Function B: 0x102a51ee0  Count: 2
dyld: bind: libmylib_copy.dylib:0x102A85038 = libc++abi.dylib:__ZTVN10__cxxabiv117__class_type_infoE, *0x102A85038 = 0x7FFF7CA67B50 + 16
dyld: bind: libmylib_copy.dylib:0x102A85050 = libc++abi.dylib:__ZTVN10__cxxabiv120__si_class_type_infoE, *0x102A85050 = 0x7FFF7CA67BD0 + 16
dyld: bind: libmylib_copy.dylib:0x102A85018 = libstdc++.6.dylib:__Znwm, *0x102A85018 = 0x7FFF938F0325
dyld: bind: libmylib_copy.dylib:0x102A85000 = libdyld.dylib:dyld_stub_binder, *0x102A85000 = 0x7FFF9084E878
dyld: weak bind: libmylib_copy.dylib:0x102A85030 = libmylib.dylib:__ZN9MyFactory8GetCountEv, *0x102A85030 = 0x102A51F00
dyld: weak bind: libmylib_copy.dylib:0x102A85060 = libmylib.dylib:__ZTI8IFactory, *0x102A85060 = 0x102A52038
dyld: weak bind: libmylib_copy.dylib:0x102A85028 = libmylib.dylib:__ZTI9MyFactory, *0x102A85028 = 0x102A52050
dyld: weak bind: libmylib_copy.dylib:0x102A85040 = libmylib.dylib:__ZTS8IFactory, *0x102A85040 = 0x102A51F41
dyld: weak bind: libmylib_copy.dylib:0x102A85058 = libmylib.dylib:__ZTS9MyFactory, *0x102A85058 = 0x102A51F36
dyld: weak bind: libmylib_copy.dylib:0x102A85010 = libmylib.dylib:__ZTV9MyFactory, *0x102A85010 = 0x102A52020
dyld: weak bind: libmylib_copy.dylib:0x102A85018 = libstdc++.6.dylib:__Znwm, *0x102A85018 = 0x7FFF938F0325
Handle C: 0x7fc729c03c20    Function C: 0x102a84ee0  Count: 3

If you look at the weak bind lines you'll see that the symbols we saw earlier that were marked as weak external are being resolved to the symbols of the same name in libmylib.dylib. I believe that this behavior relates to C++'s One Definition Rule ("Every program shall contain exactly one definition of every non-inline function or object that is used in that program"). Since you have multiple definitions of the same non-inline function in your program, the linker is attempting to coalesce the symbols at load time so only a single function is used.

The best solution here would be for each library to place its symbols inside a different namespace. Since you intend to export only C factory function, an anonymous namespace would be sufficient. This also has the effect of marking the remainder of the symbols as being non-external, which can be beneficial for library load time.

Alternatively, you can cheat a little bit on the One Definition Rule by only exporting the symbols that the clients of your library need:

$ clang++ -Wl,-exported_symbol -Wl,_GetFactory -dynamiclib myclass.cpp -o libmylib.dylib
$ cp libmylib.dylib libmylib_copy.dylib  
$ ./mytest
Handle A: 0x7fc593403910    Function A: 0x1009e4e90  Count: 1
Handle B: 0x7fc593403910    Function B: 0x1009e4e90  Count: 2
Handle C: 0x7fc593403b10    Function C: 0x1009e7e90  Count: 1

This causes the weak external symbols that we saw earlier to be marked as private, and therefore they will only resolve to symbols within the same image.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top