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 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
