I have an executable that loads two so library files. The second so file that is loaded results in a segfault (the order of which one to load first does not have any impact on the segfault). I link against the shared spdlog
I have tried to reproduce the issue on a minimal example, however, I have not managed to recreate the problem. Although, I will still share the minimal example whether someone can help providing some guides or hints into why this might happen.
log_wrap.hpp
#ifndef LOG_WRAP_HPP
#define LOG_WRAP_HPP
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <cxxabi.h>
class LogImpl;
class LogRegister {
inline static std::vector<spdlog::sink_ptr> sinks;
inline static std::mutex mmutex;
inline static std::atomic<bool> sss{ false };
template <typename SINK, typename... ARGS>
static void add_sink(std::atomic<bool>& state, ARGS... args) {
const std::lock_guard lock(mmutex);
if (state.load()) return;
sinks.push_back(std::make_shared<SINK>(args...));
auto& new_sink = sinks.back();
spdlog::apply_all([&new_sink](const std::shared_ptr<spdlog::logger>& l) { l->sinks().push_back(new_sink); });
state.exchange(true);
}
public:
static std::shared_ptr<LogImpl> register_logger(const std::string& app_id);
static void enable_stdout() { add_sink<spdlog::sinks::stdout_color_sink_mt>(sss); }
};
class LogImpl : public spdlog::logger
{
private:
std::string m_name_wo_prefix;
std::string m_prefix;
public:
void set_prefix(std::string prefix)
{
m_prefix = std::move(prefix);
name_ = m_prefix + m_name_wo_prefix;
}
explicit LogImpl(const std::string& name) : spdlog::logger(name), m_name_wo_prefix(name) {}
template <typename ITERATOR>
LogImpl(const std::string& name, ITERATOR begin, ITERATOR end)
: spdlog::logger(name, begin, end), m_name_wo_prefix(name)
{}
};
template<typename T>
class LogWrap : public std::shared_ptr<LogImpl> {
private:
static std::string make_logger_name()
{
int status = 0;
const char *tid = typeid(T).name();
const char *result_cstr = abi::__cxa_demangle(tid, nullptr, nullptr, &status);
if (status)
{
free((void *)result_cstr);
return tid;
}
std::string result(result_cstr);
free((void *) result_cstr);
return result;
}
public:
LogWrap() : std::shared_ptr<LogImpl>(LogRegister::register_logger(make_logger_name())) {}
};
inline std::shared_ptr<LogImpl> LogRegister::register_logger(const std::string& app_id)
{
if (!spdlog::get(app_id)){
spdlog::register_logger(std::make_shared<LogImpl>(app_id, begin(sinks), end(sinks)));
}
auto result_spdlog = spdlog::get(app_id);
auto result_impl = std::dynamic_pointer_cast<LogImpl>(result_spdlog);
return result_impl;
}
#endif //LOG_WRAP_HPP
common_class.hpp
#ifndef COMMON_CLASS_HPP
#define COMMON_CLASS_HPP
#include "log_wrap.hpp"
class CommonClass {
inline static LogWrap<CommonClass> logger;
public:
CommonClass();
};
#endif //COMMON_CLASS_HPP
common_class.cpp
#include "common_class.hpp"
CommonClass::CommonClass()
{
logger->info("hello world!");
}
Plugin 1
#include "common_class.hpp"
extern "C" {
__attribute__ ((visibility ("default")))
void plugin() {
CommonClass c;
return;
};
}
Plugin 2
#include "common_class.hpp"
extern "C" {
__attribute__ ((visibility ("default")))
void plugin2() {
CommonClass c;
};
}
Executable:
#include "log_wrap.hpp"
#include <dlfcn.h>
#include <iostream>
static const std::string plugin_func = "plugin";
int main() {
void *m_lib{ nullptr };
void *m_lib2{ nullptr };
LogRegister::enable_stdout();
m_lib = dlopen("./libdum_plugin.so", RTLD_LAZY );
m_lib2 = dlopen("./libliba.so", RTLD_LAZY );
assert(m_lib);
assert(m_lib2);
}
CMakefile
cmake_minimum_required(VERSION 3.26)
project(ReproduceSegfault
VERSION 0.1
LANGUAGES CXX )
find_package(spdlog REQUIRED)
add_library(obj_ OBJECT common_class.cpp )
target_link_libraries(obj_ PUBLIC spdlog::spdlog)
set_property(TARGET obj_ PROPERTY POSITION_INDEPENDENT_CODE ON)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-allow-shlib-undefined")
add_library(liba SHARED)
target_sources(liba PRIVATE $<TARGET_OBJECTS:obj_>)
target_link_libraries(liba PUBLIC spdlog::spdlog)
add_library(dum_plugin SHARED)
target_sources(dum_plugin PRIVATE $<TARGET_OBJECTS:obj_> plugin.cpp)
target_link_libraries(dum_plugin PUBLIC liba spdlog::spdlog)
add_library(dum_plugin2 SHARED)
target_sources(dum_plugin2 PRIVATE $<TARGET_OBJECTS:obj_> plugin2.cpp)
target_link_libraries(dum_plugin2 PUBLIC liba spdlog::spdlog)
add_executable(dum_exe dum_exe.cpp )
target_link_libraries(dum_exe PUBLIC spdlog::spdlog)
As mentioned, the above example does not result in a segfault, however, it does resemble the same situation I have in my prod code. One key difference I observed with the above example, is that the Log registration of CommonClass only happens once!. So when I open the second plugin I do not observe any other registration of CommonClass
, however, in my real application I got many similar to CommonClass
being invoked multiple times, including accros the two different plugins..
I get the segfault at this line:
auto result_impl = std::dynamic_pointer_cast<LogImpl>(result_spdlog);
I did run through the debugger and result_spdlog is not a nullptr (and even if it was I guess result_impl would just be a nullptr itself). However, in this case the segfault occurs within dynamic_pointer_cast