[Lldb-commits] [lldb] 119e576 - [lldb] Build liblldb exports from script-interpreter imports (#201392)
via lldb-commits
lldb-commits at lists.llvm.org
Thu Jun 4 09:59:24 PDT 2026
Author: Jonas Devlieghere
Date: 2026-06-04T16:59:18Z
New Revision: 119e57630281e246d858717f8e331772b01f2908
URL: https://github.com/llvm/llvm-project/commit/119e57630281e246d858717f8e331772b01f2908
DIFF: https://github.com/llvm/llvm-project/commit/119e57630281e246d858717f8e331772b01f2908.diff
LOG: [lldb] Build liblldb exports from script-interpreter imports (#201392)
With LLDB_ENABLE_DYNAMIC_SCRIPTINTERPRETERS, the script interpreter
plugins are built as standalone shared libraries that resolve their
lldb_private/llvm references through liblldb's re-exports. liblldb was
falling back to liblldb-private.exports, which allowlists the
lldb_private/llvm namespaces wholesale.
Generate the export list per build instead. For each plugin, run llvm-nm
on its compiled objects, keep undefined references in namespaces liblldb
owns, subtract the plugin's own definitions, and concatenate the
per-plugin lists with liblldb.exports.
The plugin's SHARED target LINK_LIBS liblldb, which makes the plugin's
compile order depend on liblldb. Wiring the exports file in via
add_llvm_symbol_exports' built-in target dependency would form a cycle
through that order-only edge. Add NO_TARGET_DEPENDENCY to
add_llvm_symbol_exports so we can apply the file via LINK_DEPENDS alone;
that still triggers a relink when the symbol set changes, without
participating in compile-order tracking.
Windows is unchanged: msvc_extract_private_symbols.py was already doing
per-symbol extraction at namespace granularity.
Assisted-by: Claude
Added:
lldb/cmake/modules/LLDBConcatFiles.cmake
lldb/scripts/extract-dynamic-script-interpreter-exports.py
Modified:
lldb/cmake/modules/AddLLDB.cmake
lldb/source/API/CMakeLists.txt
lldb/source/Plugins/ScriptInterpreter/Lua/CMakeLists.txt
lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
llvm/cmake/modules/AddLLVM.cmake
Removed:
################################################################################
diff --git a/lldb/cmake/modules/AddLLDB.cmake b/lldb/cmake/modules/AddLLDB.cmake
index e317cd6baa02d..394dcb1ab6cf0 100644
--- a/lldb/cmake/modules/AddLLDB.cmake
+++ b/lldb/cmake/modules/AddLLDB.cmake
@@ -60,6 +60,67 @@ function(_lldb_propagate_links_liblldb name libs)
set_target_properties(${name} PROPERTIES LLDB_LINKS_LIBLLDB ${_links_liblldb})
endfunction()
+# Records a target's undefined LLDB symbols for re-export by liblldb.
+# The output path is appended to the LLDB_DYNAMIC_SCRIPTINTERPRETER_SYMBOL_FILES
+# global property.
+function(lldb_record_dynamic_script_interpreter_exports target)
+ set(symbol_file
+ "${CMAKE_CURRENT_BINARY_DIR}/${target}.undefined.symbols")
+
+ # llvm-nm is registered as a CMake target after the LLDB subdirectory is
+ # processed, so a forward target reference is required. LLVM_NM, when set,
+ # points to a prebuilt host tool for cross-compiled builds.
+ if(LLVM_NM)
+ set(nm_exe "${LLVM_NM}")
+ set(nm_dep)
+ else()
+ set(nm_exe "$<TARGET_FILE:llvm-nm>")
+ set(nm_dep llvm-nm)
+ endif()
+
+ set(extra_args)
+ if(APPLE)
+ list(APPEND extra_args --mach-o)
+ endif()
+
+ add_custom_command(
+ OUTPUT ${symbol_file}
+ COMMAND "${Python3_EXECUTABLE}"
+ ${LLDB_SOURCE_DIR}/scripts/extract-dynamic-script-interpreter-exports.py
+ --nm ${nm_exe}
+ ${extra_args}
+ -o ${symbol_file}
+ $<TARGET_OBJECTS:${target}>
+ DEPENDS
+ ${LLDB_SOURCE_DIR}/scripts/extract-dynamic-script-interpreter-exports.py
+ $<TARGET_OBJECTS:${target}>
+ ${nm_dep}
+ COMMAND_EXPAND_LISTS
+ VERBATIM
+ COMMENT "Extracting LLDB symbols required by ${target}"
+ )
+
+ add_custom_target(${target}-exports DEPENDS ${symbol_file})
+ set_target_properties(${target}-exports PROPERTIES FOLDER "LLDB/API")
+
+ set_property(GLOBAL APPEND PROPERTY
+ LLDB_DYNAMIC_SCRIPTINTERPRETER_SYMBOL_FILES ${symbol_file})
+endfunction()
+
+# Applies an exports file to a target's link without inserting the
+# order-only edge that add_llvm_symbol_exports normally adds. That edge
+# propagates through CMake's compile-order graph and forms a cycle when
+# the exports list is itself derived from a target that links the same
+# library. LINK_DEPENDS still triggers a relink when the file's content
+# changes (Ninja and Makefile generators only).
+function(lldb_apply_exports_file target export_file)
+ add_llvm_symbol_exports(${target} ${export_file}
+ NO_TARGET_DEPENDENCY
+ OUTPUT_FILE_VAR native_export_file)
+ set_property(TARGET ${target} APPEND PROPERTY LINK_DEPENDS
+ ${native_export_file})
+endfunction()
+
# Configure-time scan: every #include "lldb/<Module>/..." in <SOURCES> and in
# PUBLIC_HEADER_DIR (recursive) must reference OWN_MODULE or a name in
# ALLOWED_MODULES, otherwise FATAL_ERROR. Catches accidental cross-module
diff --git a/lldb/cmake/modules/LLDBConcatFiles.cmake b/lldb/cmake/modules/LLDBConcatFiles.cmake
new file mode 100644
index 0000000000000..ac832806f8dad
--- /dev/null
+++ b/lldb/cmake/modules/LLDBConcatFiles.cmake
@@ -0,0 +1,18 @@
+# Concatenate the input files (positional args 2..N) into the output
+# file (positional arg 1).
+#
+# Usage: cmake -P LLDBConcatFiles.cmake <output> <input1> [<input2> ...]
+
+if(CMAKE_ARGC LESS 5)
+ message(FATAL_ERROR
+ "LLDBConcatFiles.cmake requires <output> and at least one <input>.")
+endif()
+
+set(_out "${CMAKE_ARGV3}")
+file(WRITE "${_out}" "")
+
+math(EXPR _last "${CMAKE_ARGC} - 1")
+foreach(_i RANGE 4 ${_last})
+ file(READ "${CMAKE_ARGV${_i}}" _data)
+ file(APPEND "${_out}" "${_data}")
+endforeach()
diff --git a/lldb/scripts/extract-dynamic-script-interpreter-exports.py b/lldb/scripts/extract-dynamic-script-interpreter-exports.py
new file mode 100644
index 0000000000000..b3da994b2bef1
--- /dev/null
+++ b/lldb/scripts/extract-dynamic-script-interpreter-exports.py
@@ -0,0 +1,100 @@
+"""Emit the LLDB-side imports of a script interpreter plugin's objects.
+
+The result is fed into liblldb's exports file so the dynamic loader can
+satisfy the plugin's references at runtime without growing liblldb's
+re-export surface beyond what the plugin actually consumes.
+"""
+
+import argparse
+import subprocess
+import sys
+
+
+# Mangled-name prefixes liblldb owns. Symbols outside these namespaces
+# are resolved through other libraries on the loader path.
+EXPORT_PREFIXES = (
+ "_ZN12lldb_private",
+ "_ZNK12lldb_private",
+ "_ZTVN12lldb_private",
+ "_ZTSN12lldb_private",
+ "_ZTIN12lldb_private",
+ "_ZN4lldb",
+ "_ZNK4lldb",
+ "_ZTVN4lldb",
+ "_ZTSN4lldb",
+ "_ZTIN4lldb",
+ "_ZN4llvm",
+ "_ZNK4llvm",
+ "_ZTVN4llvm",
+ "_ZTSN4llvm",
+ "_ZTIN4llvm",
+)
+
+
+def normalize(sym, mach_o):
+ # Mach-O reports an extra leading underscore that the .exports file
+ # format does not carry.
+ if mach_o and sym.startswith("_"):
+ return sym[1:]
+ return sym
+
+
+def collect(nm, obj, mach_o):
+ """Return (undefined, defined) symbol-name sets for `obj`.
+
+ Both halves are needed so cross-object references that the plugin's
+ own objects satisfy can be subtracted before emitting.
+ """
+ undefined = set()
+ defined = set()
+
+ out = subprocess.check_output(
+ [nm, "--undefined-only", "--just-symbol-name", obj], text=True
+ )
+ for line in out.splitlines():
+ sym = line.strip()
+ if sym:
+ undefined.add(normalize(sym, mach_o))
+
+ out = subprocess.check_output(
+ [nm, "--defined-only", "--extern-only", "--just-symbol-name", obj],
+ text=True,
+ )
+ for line in out.splitlines():
+ sym = line.strip()
+ if sym:
+ defined.add(normalize(sym, mach_o))
+
+ return undefined, defined
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--nm", required=True, help="Path to llvm-nm.")
+ parser.add_argument("-o", "--output", required=True, help="Output exports file.")
+ parser.add_argument(
+ "--mach-o",
+ action="store_true",
+ help="Strip the leading underscore Mach-O prepends to every symbol.",
+ )
+ parser.add_argument(
+ "objects", nargs="+", help="Object files (or archives) to scan."
+ )
+ args = parser.parse_args()
+
+ undefined = set()
+ defined = set()
+ for obj in args.objects:
+ u, d = collect(args.nm, obj, args.mach_o)
+ undefined.update(u)
+ defined.update(d)
+
+ syms = {s for s in (undefined - defined) if s.startswith(EXPORT_PREFIXES)}
+
+ with open(args.output, "w", newline="\n") as f:
+ for s in sorted(syms):
+ f.write(s + "\n")
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt
index 311c739b550f8..76ee0ecd60499 100644
--- a/lldb/source/API/CMakeLists.txt
+++ b/lldb/source/API/CMakeLists.txt
@@ -159,7 +159,34 @@ set_target_properties(liblldb
target_compile_definitions(liblldb PRIVATE LLDB_IN_LIBLLDB)
if (NOT CMAKE_SYSTEM_NAME MATCHES "Windows")
- if (NOT LLDB_EXPORT_ALL_SYMBOLS)
+ if (LLDB_ENABLE_DYNAMIC_SCRIPTINTERPRETERS AND NOT LLDB_EXPORT_ALL_SYMBOLS_EXPORTS_FILE)
+ # Each script interpreter plugin contributes a list of its undefined
+ # LLDB symbols. Concatenating those with liblldb.exports yields the
+ # smallest export set that satisfies dynamically loaded plugins.
+ get_property(plugin_symbol_files GLOBAL
+ PROPERTY LLDB_DYNAMIC_SCRIPTINTERPRETER_SYMBOL_FILES)
+ set(generated_exports_file
+ ${CMAKE_CURRENT_BINARY_DIR}/liblldb-script-interpreter.exports)
+ add_custom_command(
+ OUTPUT ${generated_exports_file}
+ COMMAND ${CMAKE_COMMAND} -P
+ ${LLDB_SOURCE_DIR}/cmake/modules/LLDBConcatFiles.cmake
+ ${generated_exports_file}
+ ${CMAKE_CURRENT_SOURCE_DIR}/liblldb.exports
+ ${plugin_symbol_files}
+ DEPENDS
+ ${LLDB_SOURCE_DIR}/cmake/modules/LLDBConcatFiles.cmake
+ ${CMAKE_CURRENT_SOURCE_DIR}/liblldb.exports
+ ${plugin_symbol_files}
+ VERBATIM
+ COMMENT "Merging script interpreter export lists for liblldb"
+ )
+ add_custom_target(liblldb-script-interpreter-exports
+ DEPENDS ${generated_exports_file})
+ message(STATUS "Symbols (liblldb): exporting the lldb namespace plus "
+ "script interpreter dependencies")
+ lldb_apply_exports_file(liblldb ${generated_exports_file})
+ elseif (NOT LLDB_EXPORT_ALL_SYMBOLS)
# If we're not exporting all symbols, we'll want to explicitly set
# the exported symbols here. This prevents 'log enable --stack ...'
# from working on some systems but limits the liblldb size.
diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Lua/CMakeLists.txt
index 7d553bfc9d87a..03a7670de0040 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Lua/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/Lua/CMakeLists.txt
@@ -14,6 +14,11 @@ if (LLDB_ENABLE_DYNAMIC_SCRIPTINTERPRETERS)
)
add_lua_wrapper(lldbPluginScriptInterpreterLua)
+ if (NOT CMAKE_SYSTEM_NAME MATCHES "Windows")
+ lldb_record_dynamic_script_interpreter_exports(
+ lldbPluginScriptInterpreterLua)
+ endif()
+
# Static variant linked directly by unit tests. Separate compilation is
# required so llvm::Error RTTI (ErrorInfoBase::ID) has a single address
# shared between the test binary and the plugin code.
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
index 03ef6b10b06c4..a5c2363bd59d3 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
@@ -69,6 +69,11 @@ if (LLDB_ENABLE_DYNAMIC_SCRIPTINTERPRETERS)
${PYTHON_SABI_LIBRARY_DIRS})
endif()
+ if (NOT CMAKE_SYSTEM_NAME MATCHES "Windows")
+ lldb_record_dynamic_script_interpreter_exports(
+ lldbPluginScriptInterpreterPython)
+ endif()
+
# Static variant linked directly by unit tests. Separate compilation is
# required so llvm::Error RTTI (ErrorInfoBase::ID) has a single address
# shared between the test binary and the plugin code.
@@ -89,8 +94,8 @@ if (LLDB_ENABLE_DYNAMIC_SCRIPTINTERPRETERS)
)
target_include_directories(lldbStaticScriptInterpreterPython
PUBLIC ${Python3_INCLUDE_DIRS})
-
- target_link_directories(lldbStaticScriptInterpreterPython PUBLIC ${PYTHON_SABI_LIBRARY_DIRS})
+ target_link_directories(lldbStaticScriptInterpreterPython
+ PUBLIC ${PYTHON_SABI_LIBRARY_DIRS})
else()
add_lldb_library(lldbPluginScriptInterpreterPython PLUGIN
${python_plugin_sources}
diff --git a/llvm/cmake/modules/AddLLVM.cmake b/llvm/cmake/modules/AddLLVM.cmake
index f4a73d1fc8943..a267166eb6c2d 100644
--- a/llvm/cmake/modules/AddLLVM.cmake
+++ b/llvm/cmake/modules/AddLLVM.cmake
@@ -147,6 +147,8 @@ function(llvm_update_pch name)
endfunction()
function(add_llvm_symbol_exports target_name export_file)
+ cmake_parse_arguments(ARG "NO_TARGET_DEPENDENCY" "OUTPUT_FILE_VAR" "" ${ARGN})
+
if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
set(native_export_file "${target_name}.exports")
add_custom_command(OUTPUT ${native_export_file}
@@ -239,6 +241,15 @@ function(add_llvm_symbol_exports target_name export_file)
set_property(DIRECTORY APPEND
PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${native_export_file})
+ if(ARG_OUTPUT_FILE_VAR)
+ set(${ARG_OUTPUT_FILE_VAR}
+ "${CMAKE_CURRENT_BINARY_DIR}/${native_export_file}" PARENT_SCOPE)
+ endif()
+
+ if(ARG_NO_TARGET_DEPENDENCY)
+ return()
+ endif()
+
add_dependencies(${target_name} ${target_name}_exports)
# Add dependency to *_exports later -- CMake issue 14747
More information about the lldb-commits
mailing list