From e5f9f6ea76d4a73423f385e0c344872b20831f5a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 8 May 2026 09:12:02 +0000 Subject: [PATCH 1/3] Fix yaml-cpp exception symbol visibility (undefined symbol _ZTIN4YAML13BadConversionE) Two complementary fixes for the intermittent runtime error: 1. Set CXX_VISIBILITY_PRESET=default and VISIBILITY_INLINES_HIDDEN=FALSE on the yaml-cpp CMake target (contrib/yaml-cpp-0.6.2/CMakeLists.txt). GAMBIT enables CMAKE_CXX_VISIBILITY_PRESET=hidden globally by default (when no -rdynamic is in CMAKE_CXX_FLAGS), which would otherwise hide yaml-cpp exception RTTI even though YAML_CPP_API correctly applies __attribute__((visibility("default"))) to each exception class. 2. Set ENABLE_EXPORTS=TRUE on all GAMBIT executables (cmake/utilities.cmake). This adds -rdynamic (Linux) / -export_dynamic (macOS) to the executable link step, so yaml-cpp typeinfo symbols that are statically linked into the executable are exported into the dynamic symbol table and remain resolvable by backends and scanner plugins loaded at runtime via dlopen. The error is intermittent because it only manifests when a user builds without -rdynamic in CMAKE_CXX_FLAGS (the default), and when a dlopen'd backend or plugin references yaml-cpp exception typeinfo that is not otherwise visible. https://claude.ai/code/session_01PAuG5z7cdvGWuxbpWMn52L --- cmake/utilities.cmake | 5 ++++- contrib/yaml-cpp-0.6.2/CMakeLists.txt | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmake/utilities.cmake b/cmake/utilities.cmake index d11dde6f66..74a1644f22 100644 --- a/cmake/utilities.cmake +++ b/cmake/utilities.cmake @@ -308,7 +308,10 @@ function(add_gambit_executable executablename LIBRARIES) cmake_parse_arguments(ARG "" "" "SOURCES;HEADERS;" ${ARGN}) add_executable(${executablename} ${ARG_SOURCES} ${ARG_HEADERS}) - set_target_properties(${executablename} PROPERTIES EXCLUDE_FROM_ALL 1) + set_target_properties(${executablename} PROPERTIES + EXCLUDE_FROM_ALL 1 + ENABLE_EXPORTS TRUE + ) if(${CMAKE_VERSION} VERSION_GREATER 2.8.10) foreach (dir ${GAMBIT_INCDIRS}) diff --git a/contrib/yaml-cpp-0.6.2/CMakeLists.txt b/contrib/yaml-cpp-0.6.2/CMakeLists.txt index 91fd7f16af..05d7f87144 100755 --- a/contrib/yaml-cpp-0.6.2/CMakeLists.txt +++ b/contrib/yaml-cpp-0.6.2/CMakeLists.txt @@ -51,4 +51,6 @@ endif() set_target_properties(yaml-cpp PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + CXX_VISIBILITY_PRESET default + VISIBILITY_INLINES_HIDDEN FALSE ) From 1b29a2b811bc386dc331609e104dbb130f345654 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 8 May 2026 13:39:35 +0000 Subject: [PATCH 2/3] Add explanatory comment for ENABLE_EXPORTS on GAMBIT executables Documents why ENABLE_EXPORTS is set via a target property rather than CMAKE_CXX_FLAGS, how it interacts with the project-wide hidden visibility preset to keep GAMBIT internals unexported, and the symbol interposition behaviour to be aware of for third-party backends. https://claude.ai/code/session_01PAuG5z7cdvGWuxbpWMn52L --- cmake/utilities.cmake | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cmake/utilities.cmake b/cmake/utilities.cmake index 74a1644f22..041c4d07f3 100644 --- a/cmake/utilities.cmake +++ b/cmake/utilities.cmake @@ -308,6 +308,29 @@ function(add_gambit_executable executablename LIBRARIES) cmake_parse_arguments(ARG "" "" "SOURCES;HEADERS;" ${ARGN}) add_executable(${executablename} ${ARG_SOURCES} ${ARG_HEADERS}) + # ENABLE_EXPORTS adds -rdynamic (Linux) / -export_dynamic (macOS) to the linker, + # placing all default-visibility symbols into the executable's dynamic symbol table. + # This is needed so that backends and scanner plugins loaded at runtime via dlopen + # can resolve symbols from statically linked contrib libraries (notably yaml-cpp + # exception typeinfo, e.g. _ZTIN4YAML13BadConversionE). + # + # ENABLE_EXPORTS is intentionally set via a target property rather than via + # CMAKE_CXX_FLAGS. The visibility logic above (CMakeLists.txt) checks CMAKE_CXX_FLAGS + # for -rdynamic and sets CMAKE_CXX_VISIBILITY_PRESET=hidden when it is absent. + # Using a target property here means that check is unaffected: GAMBIT's own code + # is still compiled with -fvisibility=hidden and its internal symbols remain hidden. + # Only symbols compiled with default visibility (e.g. yaml-cpp, which overrides the + # preset via CXX_VISIBILITY_PRESET=default on its target) are exported. This keeps + # the exported symbol set minimal and avoids polluting the global namespace with + # GAMBIT internals. + # + # The one consequence to be aware of: default-visibility symbols exported from the + # executable are visible to all dlopen'd plugins, so a plugin that bundles its own + # copy of a contrib library (e.g. a different yaml-cpp version) may have its calls + # interposed by the executable's copy. For GAMBIT's own backends this is intentional + # (shared typeinfo pointers are required for cross-DSO exception catching), but + # third-party backends that require a private copy of a library should be loaded + # with RTLD_DEEPBIND to opt out of interposition. set_target_properties(${executablename} PROPERTIES EXCLUDE_FROM_ALL 1 ENABLE_EXPORTS TRUE From ea112f8611f5348f9cdc212d11625f06e7938404 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 8 May 2026 13:40:58 +0000 Subject: [PATCH 3/3] Shorten ENABLE_EXPORTS comment to two sentences https://claude.ai/code/session_01PAuG5z7cdvGWuxbpWMn52L --- cmake/utilities.cmake | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/cmake/utilities.cmake b/cmake/utilities.cmake index 041c4d07f3..7760a703c7 100644 --- a/cmake/utilities.cmake +++ b/cmake/utilities.cmake @@ -308,29 +308,11 @@ function(add_gambit_executable executablename LIBRARIES) cmake_parse_arguments(ARG "" "" "SOURCES;HEADERS;" ${ARGN}) add_executable(${executablename} ${ARG_SOURCES} ${ARG_HEADERS}) - # ENABLE_EXPORTS adds -rdynamic (Linux) / -export_dynamic (macOS) to the linker, - # placing all default-visibility symbols into the executable's dynamic symbol table. - # This is needed so that backends and scanner plugins loaded at runtime via dlopen - # can resolve symbols from statically linked contrib libraries (notably yaml-cpp - # exception typeinfo, e.g. _ZTIN4YAML13BadConversionE). - # - # ENABLE_EXPORTS is intentionally set via a target property rather than via - # CMAKE_CXX_FLAGS. The visibility logic above (CMakeLists.txt) checks CMAKE_CXX_FLAGS - # for -rdynamic and sets CMAKE_CXX_VISIBILITY_PRESET=hidden when it is absent. - # Using a target property here means that check is unaffected: GAMBIT's own code - # is still compiled with -fvisibility=hidden and its internal symbols remain hidden. - # Only symbols compiled with default visibility (e.g. yaml-cpp, which overrides the - # preset via CXX_VISIBILITY_PRESET=default on its target) are exported. This keeps - # the exported symbol set minimal and avoids polluting the global namespace with - # GAMBIT internals. - # - # The one consequence to be aware of: default-visibility symbols exported from the - # executable are visible to all dlopen'd plugins, so a plugin that bundles its own - # copy of a contrib library (e.g. a different yaml-cpp version) may have its calls - # interposed by the executable's copy. For GAMBIT's own backends this is intentional - # (shared typeinfo pointers are required for cross-DSO exception catching), but - # third-party backends that require a private copy of a library should be loaded - # with RTLD_DEEPBIND to opt out of interposition. + # ENABLE_EXPORTS adds -rdynamic/-export_dynamic to the linker so that dlopen'd backends + # and scanner plugins can resolve symbols from statically linked contrib libraries (e.g. + # yaml-cpp exception typeinfo). Setting this via a target property rather than + # CMAKE_CXX_FLAGS keeps GAMBIT's own code compiled with -fvisibility=hidden, so only + # contrib symbols built with default visibility are exported, not GAMBIT internals. set_target_properties(${executablename} PROPERTIES EXCLUDE_FROM_ALL 1 ENABLE_EXPORTS TRUE