Search code examples
cmakeshared-librariesdynamic-linkingdlopendynamic-loading

Different version require of CMake results dlopen "undefined symbol"


I'm building with g++/KDevelop/CMake(3.16.3).

Before I put cmake_minimum_required( VERSION 3.0.0 ) in the first line of my CMakeLists.txt and everythings OK, now I need change it to cmake_minimum_required( VERSION 3.4.0 ) because of other requirements.But with this I can NOT load the libs with new main program.

My programs are some little *.so libs as "Module", and be dlopen by the main program.

header.hpp

#pragma once
int GetNumber();

littleModule.cpp

#include "header.hpp"
extern "C" int newStrategy() {
    return GetNumber();
};

loader.cpp

#include <dlfcn.h>
#include "header.hpp"

int GetNumber() {
    return 123;
};

bool anyError( void* pFunc ) {
    const char* err_msg = dlerror();
    if( err_msg != nullptr ) {
        lg_erro << "Error on loading:" << err_msg;
        return true;
    }
    
    if( pFunc == nullptr ) {
        lg_erro << "Error on loading:NULL ptr";
        return true;
    }
    
    return false;
};
    
using StrategyMaker_f = auto( * )()->int;
    
int main( int argc, char** args ) {
    void* pFunc = args; // any ptr not NULL
    
    // no err left before
    if( anyError( pFunc ) )
        exit( EXIT_FAILURE );
    
    auto lib_handle = dlopen( "/path/to/modules/littleModule.so", RTLD_NOW );
    if( anyError( lib_handle ) )
        exit( EXIT_FAILURE );
    
    pFunc = dlsym( lib_handle, "newStrategy" );
    if( anyError( pFunc ) )
        exit( EXIT_FAILURE );
    
    auto _mk_func = reinterpret_cast<StrategyMaker_f>( pFunc );
    lg_debg << "Success with:" << _mk_func();
    
    exit( EXIT_SUCCESS );
};

CMakeLists.txt (loader.cpp)

cmake_minimum_required( VERSION 3.0.0 )
# cmake_minimum_required( VERSION 3.4.0 )
add_executable( loader Loader.cpp
    common/log/Log2Console.cpp
    common/misc/Converts.cpp
)
target_link_libraries( loader dl )
install( TARGETS loader RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} )

CMakeLists.txt (littleModule.cpp)

cmake_minimum_required( VERSION 3.0.0 )
# cmake_minimum_required( VERSION 3.4.0 ) still don't work
# add_library( littleModule SHARED littleModule.cpp )
# As @Tsyvarev's hint
add_library( littleModule MODULE littleModule.cpp )
set_target_properties( littleModule PROPERTIES
    PREFIX              ""
    OUTPUT_NAME_RELEASE littleModule
    OUTPUT_NAME_DEBUG   littleModule
    VERSION             ${PROJECT_VERSION}
)
install( TARGETS littleModule LIBRARY DESTINATION strategies )

Error Output:

Error on loading:undefined symbol: _Z9GetNumberv


Solution

  • This is caused by policy CMP0065 which states:

    New in version 3.4.

    Do not add flags to export symbols from executables without the ENABLE_EXPORTS target property.

    CMake 3.3 and below, for historical reasons, always linked executables on some platforms with flags like -rdynamic to export symbols from the executables for use by any plugins they may load via dlopen. CMake 3.4 and above prefer to do this only for executables that are explicitly marked with the ENABLE_EXPORTS target property.

    The OLD behavior of this policy is to always use the additional link flags when linking executables regardless of the value of the ENABLE_EXPORTS target property.

    The NEW behavior of this policy is to only use the additional link flags when linking executables if the ENABLE_EXPORTS target property is set to True.

    This policy was introduced in CMake version 3.4. Unlike most policies, CMake version 3.24.0-rc2 does not warn by default when this policy is not set and simply uses OLD behavior. See documentation of the CMAKE_POLICY_WARNING_CMP0065 variable to control the warning.

    Note: The OLD behavior of a policy is deprecated by definition and may be removed in a future version of CMake.

    Hence, CMake is working as intended. Indeed this is a reasonable policy; adding such flags affects how the linker loads libraries and can have performance consequences when such functionality isn't actually used.

    To fix your program, write:

    add_executable(loader
        Loader.cpp
        common/log/Log2Console.cpp
        common/misc/Converts.cpp
    )
    target_link_libraries(loader PRIVATE ${CMAKE_DL_LIBS})
    set_target_properties(loader PROPERTIES ENABLE_EXPORTS TRUE)
    

    The last line is the important one for your problem; it will make sure that symbols in your executable are available to dynamically loaded libraries/plugins. The change from dl to ${CMAKE_DL_LIBS} is to account for systems which don't have a separate dl lib or have it named differently (for instance most BSDs including macOS, and HP-UX).