From 26f05dc1933fc6290e85c9edffc8373446b04e2d Mon Sep 17 00:00:00 2001 From: Sebastian Poeplau Date: Fri, 1 Aug 2025 12:39:50 +0200 Subject: [PATCH 1/2] Preserve the .debug_gdb_scripts section Make sure that compiler and linker don't optimize the section's contents away by adding the global holding the data to "llvm.used". The volatile load in the main shim is retained because "llvm.used", which translates to SHF_GNU_RETAIN on ELF targets, requires a reasonably recent linker; emitting the volatile load ensures compatibility with older linkers, at least when libstd is used. Pretty printers in dylib dependencies are now emitted by the main crate instead of the dylib; apart from matching how rlibs are handled, this approach has the advantage that `omit_gdb_pretty_printer_section` keeps working with dylib dependencies. --- compiler/rustc_codegen_gcc/src/debuginfo.rs | 3 +- compiler/rustc_codegen_llvm/src/base.rs | 12 +++---- .../rustc_codegen_llvm/src/debuginfo/gdb.rs | 30 ++++++++-------- .../rustc_codegen_llvm/src/debuginfo/mod.rs | 36 +++++++++++-------- compiler/rustc_codegen_ssa/src/base.rs | 10 +----- .../rustc_codegen_ssa/src/traits/debuginfo.rs | 2 +- tests/codegen-llvm/gdb_debug_script_load.rs | 4 ++- tests/run-make/symbols-all-mangled/rmake.rs | 35 ++++++++++-------- 8 files changed, 72 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/debuginfo.rs b/compiler/rustc_codegen_gcc/src/debuginfo.rs index 4c8585192a1b1..4c0b643952320 100644 --- a/compiler/rustc_codegen_gcc/src/debuginfo.rs +++ b/compiler/rustc_codegen_gcc/src/debuginfo.rs @@ -254,7 +254,8 @@ impl<'gcc, 'tcx> DebugInfoCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { // TODO(antoyo): implement. } - fn debuginfo_finalize(&self) { + fn debuginfo_finalize(&mut self) { + // TODO: emit section `.debug_gdb_scripts`. self.context.set_debug_info(true) } diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs index 5dda836988c81..d7da03bf490fd 100644 --- a/compiler/rustc_codegen_llvm/src/base.rs +++ b/compiler/rustc_codegen_llvm/src/base.rs @@ -109,11 +109,16 @@ pub(crate) fn compile_codegen_unit( } // Finalize code coverage by injecting the coverage map. Note, the coverage map will - // also be added to the `llvm.compiler.used` variable, created next. + // also be added to the `llvm.compiler.used` variable, created below. if cx.sess().instrument_coverage() { cx.coverageinfo_finalize(); } + // Finalize debuginfo. This adds to `llvm.used`, created below. + if cx.sess().opts.debuginfo != DebugInfo::None { + cx.debuginfo_finalize(); + } + // Create the llvm.used and llvm.compiler.used variables. if !cx.used_statics.is_empty() { cx.create_used_variable_impl(c"llvm.used", &cx.used_statics); @@ -130,11 +135,6 @@ pub(crate) fn compile_codegen_unit( llvm::LLVMDeleteGlobal(old_g); } } - - // Finalize debuginfo - if cx.sess().opts.debuginfo != DebugInfo::None { - cx.debuginfo_finalize(); - } } ModuleCodegen::new_regular(cgu_name.to_string(), llvm_module) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs index bcfa0381cc1b8..9baec9235f0d0 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs @@ -1,5 +1,7 @@ // .debug_gdb_scripts binary section. +use std::ffi::CString; + use rustc_codegen_ssa::base::collect_debugger_visualizers_transitive; use rustc_codegen_ssa::traits::*; use rustc_hir::attrs::AttributeKind; @@ -33,7 +35,12 @@ pub(crate) fn insert_reference_to_gdb_debug_scripts_section_global(bx: &mut Buil pub(crate) fn get_or_insert_gdb_debug_scripts_section_global<'ll>( cx: &CodegenCx<'ll, '_>, ) -> &'ll Value { - let c_section_var_name = c"__rustc_debug_gdb_scripts_section__"; + let c_section_var_name = CString::new(format!( + "__rustc_debug_gdb_scripts_section_{}_{:08x}", + cx.tcx.crate_name(LOCAL_CRATE), + cx.tcx.stable_crate_id(LOCAL_CRATE), + )) + .unwrap(); let section_var_name = c_section_var_name.to_str().unwrap(); let section_var = unsafe { llvm::LLVMGetNamedGlobal(cx.llmod, c_section_var_name.as_ptr()) }; @@ -89,17 +96,10 @@ pub(crate) fn needs_gdb_debug_scripts_section(cx: &CodegenCx<'_, '_>) -> bool { let omit_gdb_pretty_printer_section = find_attr!(cx.tcx.hir_krate_attrs(), AttributeKind::OmitGdbPrettyPrinterSection); - // To ensure the section `__rustc_debug_gdb_scripts_section__` will not create - // ODR violations at link time, this section will not be emitted for rlibs since - // each rlib could produce a different set of visualizers that would be embedded - // in the `.debug_gdb_scripts` section. For that reason, we make sure that the - // section is only emitted for leaf crates. + // We collect pretty printers transitively for all crates, so we make sure + // that the section is only emitted for leaf crates. let embed_visualizers = cx.tcx.crate_types().iter().any(|&crate_type| match crate_type { - CrateType::Executable - | CrateType::Dylib - | CrateType::Cdylib - | CrateType::Staticlib - | CrateType::Sdylib => { + CrateType::Executable | CrateType::Cdylib | CrateType::Staticlib | CrateType::Sdylib => { // These are crate types for which we will embed pretty printers since they // are treated as leaf crates. true @@ -110,9 +110,11 @@ pub(crate) fn needs_gdb_debug_scripts_section(cx: &CodegenCx<'_, '_>) -> bool { // want to slow down the common case. false } - CrateType::Rlib => { - // As per the above description, embedding pretty printers for rlibs could - // lead to ODR violations so we skip this crate type as well. + CrateType::Rlib | CrateType::Dylib => { + // Don't embed pretty printers for these crate types; the compiler + // can see the `#[debug_visualizer]` attributes when using the + // library, and emitting `.debug_gdb_scripts` regardless would + // break `#![omit_gdb_pretty_printer_section]`. false } }); diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index 6cbf2dbf7d3fd..c911435967c3e 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -30,7 +30,7 @@ use tracing::debug; use self::metadata::{UNKNOWN_COLUMN_NUMBER, UNKNOWN_LINE_NUMBER, file_metadata, type_di_node}; use self::namespace::mangled_name_of_instance; -use self::utils::{DIB, create_DIArray, is_node_local_to_unit}; +use self::utils::{DIB, create_DIArray, debug_context, is_node_local_to_unit}; use crate::builder::Builder; use crate::common::{AsCCharPtr, CodegenCx}; use crate::llvm; @@ -131,20 +131,28 @@ impl<'ll, 'tcx> CodegenUnitDebugContext<'ll, 'tcx> { } /// Creates any deferred debug metadata nodes -pub(crate) fn finalize(cx: &CodegenCx<'_, '_>) { - if let Some(dbg_cx) = &cx.dbg_cx { - debug!("finalize"); - - if gdb::needs_gdb_debug_scripts_section(cx) { - // Add a .debug_gdb_scripts section to this compile-unit. This will - // cause GDB to try and load the gdb_load_rust_pretty_printers.py file, - // which activates the Rust pretty printers for binary this section is - // contained in. - gdb::get_or_insert_gdb_debug_scripts_section_global(cx); - } +pub(crate) fn finalize(cx: &mut CodegenCx<'_, '_>) { + if cx.dbg_cx.is_none() { + return; + } + + debug!("finalize"); - dbg_cx.finalize(cx.sess()); + if gdb::needs_gdb_debug_scripts_section(cx) { + // Add a .debug_gdb_scripts section to this compile-unit. This will + // cause GDB to try and load the gdb_load_rust_pretty_printers.py file, + // which activates the Rust pretty printers for binary this section is + // contained in. + let section_var = gdb::get_or_insert_gdb_debug_scripts_section_global(cx); + + // Make sure that the linker doesn't optimize the global away. Adding + // it to `llvm.used` has the advantage that it works even in no_std + // binaries, where we don't have a main shim and thus don't emit a + // volatile load to preserve the global. + cx.add_used_global(section_var); } + + debug_context(cx).finalize(cx.sess()); } impl<'ll> Builder<'_, 'll, '_> { @@ -614,7 +622,7 @@ impl<'ll, 'tcx> DebugInfoCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { metadata::extend_scope_to_file(self, scope_metadata, file) } - fn debuginfo_finalize(&self) { + fn debuginfo_finalize(&mut self) { finalize(self) } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index b4556ced0b3fb..eb21468650c76 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -609,15 +609,7 @@ pub fn collect_debugger_visualizers_transitive( ) -> BTreeSet { tcx.debugger_visualizers(LOCAL_CRATE) .iter() - .chain( - tcx.crates(()) - .iter() - .filter(|&cnum| { - let used_crate_source = tcx.used_crate_source(*cnum); - used_crate_source.rlib.is_some() || used_crate_source.rmeta.is_some() - }) - .flat_map(|&cnum| tcx.debugger_visualizers(cnum)), - ) + .chain(tcx.crates(()).iter().flat_map(|&cnum| tcx.debugger_visualizers(cnum))) .filter(|visualizer| visualizer.visualizer_type == visualizer_type) .cloned() .collect::>() diff --git a/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs b/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs index b9d4950e0ad36..30a3bd5abe492 100644 --- a/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs +++ b/compiler/rustc_codegen_ssa/src/traits/debuginfo.rs @@ -50,7 +50,7 @@ pub trait DebugInfoCodegenMethods<'tcx>: BackendTypes { scope_metadata: Self::DIScope, file: &SourceFile, ) -> Self::DIScope; - fn debuginfo_finalize(&self); + fn debuginfo_finalize(&mut self); // FIXME(eddyb) find a common convention for all of the debuginfo-related // names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.). diff --git a/tests/codegen-llvm/gdb_debug_script_load.rs b/tests/codegen-llvm/gdb_debug_script_load.rs index 3e92eba10b121..90f2be41cf2ac 100644 --- a/tests/codegen-llvm/gdb_debug_script_load.rs +++ b/tests/codegen-llvm/gdb_debug_script_load.rs @@ -9,6 +9,8 @@ #![feature(lang_items)] #![no_std] +// CHECK: @llvm.used = {{.+}} @__rustc_debug_gdb_scripts_section + #[panic_handler] fn panic_handler(_: &core::panic::PanicInfo) -> ! { loop {} @@ -22,7 +24,7 @@ extern "C" fn rust_eh_personality() { // Needs rustc to generate `main` as that's where the magic load is inserted. // IOW, we cannot write this test with `#![no_main]`. // CHECK-LABEL: @main -// CHECK: load volatile i8, {{.+}} @__rustc_debug_gdb_scripts_section__ +// CHECK: load volatile i8, {{.+}} @__rustc_debug_gdb_scripts_section #[lang = "start"] fn lang_start( diff --git a/tests/run-make/symbols-all-mangled/rmake.rs b/tests/run-make/symbols-all-mangled/rmake.rs index 2cf579758002a..e30bef9858079 100644 --- a/tests/run-make/symbols-all-mangled/rmake.rs +++ b/tests/run-make/symbols-all-mangled/rmake.rs @@ -35,13 +35,7 @@ fn symbols_check_archive(path: &str) { continue; // All compiler-builtins symbols must remain unmangled } - if name.contains("rust_eh_personality") { - continue; // Unfortunately LLVM doesn't allow us to mangle this symbol - } - - if name.contains(".llvm.") { - // Starting in LLVM 21 we get various implementation-detail functions which - // contain .llvm. that are not a problem. + if symbol_ok_everywhere(name) { continue; } @@ -71,13 +65,7 @@ fn symbols_check(path: &str) { continue; } - if name.contains("rust_eh_personality") { - continue; // Unfortunately LLVM doesn't allow us to mangle this symbol - } - - if name.contains(".llvm.") { - // Starting in LLVM 21 we get various implementation-detail functions which - // contain .llvm. that are not a problem. + if symbol_ok_everywhere(name) { continue; } @@ -88,3 +76,22 @@ fn symbols_check(path: &str) { fn strip_underscore_if_apple(symbol: &str) -> &str { if cfg!(target_vendor = "apple") { symbol.strip_prefix("_").unwrap() } else { symbol } } + +fn symbol_ok_everywhere(name: &str) -> bool { + if name.contains("rust_eh_personality") { + return true; // Unfortunately LLVM doesn't allow us to mangle this symbol + } + + if name.contains(".llvm.") { + // Starting in LLVM 21 we get various implementation-detail functions which + // contain .llvm. that are not a problem. + return true; + } + + if name.starts_with("__rustc_debug_gdb_scripts_section") { + // These symbols are fine; they're made unique by the crate ID. + return true; + } + + return false; +} From 13f9889e9e2d58e5aed23345566c9a6e8dbdad4b Mon Sep 17 00:00:00 2001 From: Sebastian Poeplau Date: Fri, 1 Aug 2025 13:22:06 +0200 Subject: [PATCH 2/2] Embed GDB pretty printers in rlibs and dylibs Instead of collecting pretty printers transitively when building executables/staticlibs/cdylibs, let the debugger find each crate's pretty printers via its .debug_gdb_scripts section. This covers the case where libraries defining custom pretty printers are loaded dynamically. --- .../rustc_codegen_llvm/src/debuginfo/gdb.rs | 43 +++++++------------ compiler/rustc_codegen_ssa/src/base.rs | 10 ++++- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs index 9baec9235f0d0..3be48a48d6eb7 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs @@ -1,8 +1,8 @@ // .debug_gdb_scripts binary section. +use std::collections::BTreeSet; use std::ffi::CString; -use rustc_codegen_ssa::base::collect_debugger_visualizers_transitive; use rustc_codegen_ssa::traits::*; use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::LOCAL_CRATE; @@ -53,10 +53,14 @@ pub(crate) fn get_or_insert_gdb_debug_scripts_section_global<'ll>( // Next, add the pretty printers that were specified via the `#[debugger_visualizer]` // attribute. - let visualizers = collect_debugger_visualizers_transitive( - cx.tcx, - DebuggerVisualizerType::GdbPrettyPrinter, - ); + let visualizers = cx + .tcx + .debugger_visualizers(LOCAL_CRATE) + .iter() + .filter(|visualizer| { + visualizer.visualizer_type == DebuggerVisualizerType::GdbPrettyPrinter + }) + .collect::>(); let crate_name = cx.tcx.crate_name(LOCAL_CRATE); for (index, visualizer) in visualizers.iter().enumerate() { // The initial byte `4` instructs GDB that the following pretty printer @@ -96,31 +100,14 @@ pub(crate) fn needs_gdb_debug_scripts_section(cx: &CodegenCx<'_, '_>) -> bool { let omit_gdb_pretty_printer_section = find_attr!(cx.tcx.hir_krate_attrs(), AttributeKind::OmitGdbPrettyPrinterSection); - // We collect pretty printers transitively for all crates, so we make sure - // that the section is only emitted for leaf crates. - let embed_visualizers = cx.tcx.crate_types().iter().any(|&crate_type| match crate_type { - CrateType::Executable | CrateType::Cdylib | CrateType::Staticlib | CrateType::Sdylib => { - // These are crate types for which we will embed pretty printers since they - // are treated as leaf crates. - true - } - CrateType::ProcMacro => { - // We could embed pretty printers for proc macro crates too but it does not - // seem like a good default, since this is a rare use case and we don't - // want to slow down the common case. - false - } - CrateType::Rlib | CrateType::Dylib => { - // Don't embed pretty printers for these crate types; the compiler - // can see the `#[debug_visualizer]` attributes when using the - // library, and emitting `.debug_gdb_scripts` regardless would - // break `#![omit_gdb_pretty_printer_section]`. - false - } - }); + // We could embed pretty printers for proc macro crates too but it does not + // seem like a good default, since this is a rare use case and we don't + // want to slow down the common case. + let proc_macro_only = + cx.tcx.crate_types().iter().all(|&crate_type| crate_type == CrateType::ProcMacro); !omit_gdb_pretty_printer_section && cx.sess().opts.debuginfo != DebugInfo::None && cx.sess().target.emit_debug_gdb_scripts - && embed_visualizers + && !proc_macro_only } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index eb21468650c76..b4556ced0b3fb 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -609,7 +609,15 @@ pub fn collect_debugger_visualizers_transitive( ) -> BTreeSet { tcx.debugger_visualizers(LOCAL_CRATE) .iter() - .chain(tcx.crates(()).iter().flat_map(|&cnum| tcx.debugger_visualizers(cnum))) + .chain( + tcx.crates(()) + .iter() + .filter(|&cnum| { + let used_crate_source = tcx.used_crate_source(*cnum); + used_crate_source.rlib.is_some() || used_crate_source.rmeta.is_some() + }) + .flat_map(|&cnum| tcx.debugger_visualizers(cnum)), + ) .filter(|visualizer| visualizer.visualizer_type == visualizer_type) .cloned() .collect::>()