Summary
The module factory loads out-of-tree components from shared libraries by looking up the unmangled symbol module_register:
construct_module() falls back to the dylib loader when the module type is unknown:
|
sc_core::sc_module* construct_module(std::string moduletype, sc_core::sc_module_name name, cci::cci_value_list args) |
|
{ |
|
gs::cci_constructor_vl m_fac; |
|
cci::cci_value v = cci::cci_value::from_json("\"" + moduletype + "\""); |
|
if (!v.try_get<gs::cci_constructor_vl>(m_fac)) { |
|
register_module_from_dylib(name); |
|
} |
|
|
|
if (v.try_get<gs::cci_constructor_vl>(m_fac)) { |
|
/** |
|
* (m_fac)(name, args) will return raw pointer of unmanaged dynamically allocated moduletype: |
|
* new moduletype(name, args) |
|
*/ |
|
sc_core::sc_module* mod = (m_fac)(name, args); |
|
if (mod) { |
|
m_constructedModules.emplace_back(mod); |
|
return mod; |
|
} else { |
|
SC_REPORT_ERROR("ModuleFactory", ("Null module type: " + moduletype).c_str()); |
|
} |
|
} else { |
|
SC_REPORT_ERROR("ModuleFactory", ("Can't find module type: " + moduletype).c_str()); |
register_module_from_dylib() resolves the symbol by name:
|
void register_module_from_dylib(sc_core::sc_module_name name) |
|
{ |
|
std::string libname; |
|
std::vector<std::string> lib_types = { ".moduletype", ".dylib_path" }; |
|
|
|
for (const auto& type : lib_types) { |
|
const std::string base_name = std::string(sc_module::name()) + "." + std::string(name) + type; |
|
if (m_broker.has_preset_value(base_name)) { |
|
libname = std::string(m_broker.get_preset_cci_value(base_name).get_string()) + "." + |
|
std::string(m_library_loader.get_lib_ext()); |
|
} |
|
} |
|
|
|
qemu::LibraryLoaderIface::LibraryIfacePtr libraryHandle = m_library_loader.load_library(libname); |
|
if (!libraryHandle) { |
|
SCP_FATAL(())("Failed to load library {}: {}", libname, m_library_loader.get_last_error()); |
|
} |
|
|
|
m_dls.insert(libraryHandle); |
|
|
|
void (*module_register)() = nullptr; |
|
if (libraryHandle->symbol_exists("module_register")) { |
|
module_register = reinterpret_cast<void (*)()>(libraryHandle->get_symbol("module_register")); |
|
module_register(); |
|
} else { |
|
SCP_WARN(()) << "module_register not found in library " << libname; |
|
} |
|
} |
(symbol_exists("module_register") at L951)
This only works if the component declares the hook extern "C" — as every in-tree component does, e.g. keep_alive.h#L34. But nothing in the documentation states this requirement: the docs show how to use moduletype + dylib_path in Lua (e.g. docs/backends.md#L111-L127) but never mention module_register at all, let alone its required linkage (checked: no match for module_register or extern "C" anywhere under docs/ or in README.md on current main).
Failure mode when the requirement is missed
A plain C++ void module_register() { ... } compiles and links fine, but exports a mangled symbol (e.g. __Z15module_registerv). At runtime:
- The dylib loads successfully.
symbol_exists("module_register") fails, producing only SCP_WARN(...) "module_register not found in library ..." (module_factory_container.h#L955), which is easy to miss at default log levels.
- Elaboration then aborts with
SC_REPORT_ERROR: "Can't find module type: <X>" (module_factory_container.h#L98) — an error that suggests a wrong moduletype string or a missing/unfound library, not C++ name mangling. We lost real time to exactly this on an out-of-tree component before diffing against in-tree headers revealed the extern "C" convention.
Reproduction
- Create an out-of-tree component with
void module_register() { GSC_MODULE_REGISTER_C(mycomp, ...); } — without extern "C" — and build it as a shared library.
- Reference it from a platform config:
mycomp_0 = { moduletype = "mycomp", dylib_path = "<path/to/lib>" , ... }.
- Run the platform: the library loads, registration is skipped with only a warning, and elaboration fails with
Can't find module type: mycomp.
- Add
extern "C" to the declaration; everything works.
Suggestions (scoped)
- Document the contract wherever
dylib_path is introduced (e.g. docs/configuration.md and/or docs/backends.md): a dynamically loadable component must export extern "C" void module_register(); which performs the GSC_MODULE_REGISTER* call, matching the pattern used by all in-tree components.
- Optionally improve the diagnostic: when
module_register is not found after a successful dylib load, mention the likely cause in the message (e.g. "symbol not found — is it declared extern "C"?"), and/or escalate the SCP_WARN to an error since a library named via dylib_path that exposes no module_register can never satisfy the lookup.
Summary
The module factory loads out-of-tree components from shared libraries by looking up the unmangled symbol
module_register:construct_module()falls back to the dylib loader when the module type is unknown:qbox/systemc-components/common/include/module_factory_container.h
Lines 77 to 98 in c00e7f7
register_module_from_dylib()resolves the symbol by name:qbox/systemc-components/common/include/module_factory_container.h
Lines 930 to 957 in c00e7f7
symbol_exists("module_register")at L951)This only works if the component declares the hook
extern "C"— as every in-tree component does, e.g. keep_alive.h#L34. But nothing in the documentation states this requirement: the docs show how to usemoduletype+dylib_pathin Lua (e.g. docs/backends.md#L111-L127) but never mentionmodule_registerat all, let alone its required linkage (checked: no match formodule_registerorextern "C"anywhere underdocs/or inREADME.mdon currentmain).Failure mode when the requirement is missed
A plain C++
void module_register() { ... }compiles and links fine, but exports a mangled symbol (e.g.__Z15module_registerv). At runtime:symbol_exists("module_register")fails, producing onlySCP_WARN(...) "module_register not found in library ..."(module_factory_container.h#L955), which is easy to miss at default log levels.SC_REPORT_ERROR: "Can't find module type: <X>"(module_factory_container.h#L98) — an error that suggests a wrongmoduletypestring or a missing/unfound library, not C++ name mangling. We lost real time to exactly this on an out-of-tree component before diffing against in-tree headers revealed theextern "C"convention.Reproduction
void module_register() { GSC_MODULE_REGISTER_C(mycomp, ...); }— withoutextern "C"— and build it as a shared library.mycomp_0 = { moduletype = "mycomp", dylib_path = "<path/to/lib>" , ... }.Can't find module type: mycomp.extern "C"to the declaration; everything works.Suggestions (scoped)
dylib_pathis introduced (e.g.docs/configuration.mdand/ordocs/backends.md): a dynamically loadable component must exportextern "C" void module_register();which performs theGSC_MODULE_REGISTER*call, matching the pattern used by all in-tree components.module_registeris not found after a successful dylib load, mention the likely cause in the message (e.g. "symbol not found — is it declared extern "C"?"), and/or escalate theSCP_WARNto an error since a library named viadylib_paththat exposes nomodule_registercan never satisfy the lookup.