From 60458b97e750452409aa7e7e2307be6205512167 Mon Sep 17 00:00:00 2001 From: ridwanabdillahi <91507758+ridwanabdillahi@users.noreply.github.com> Date: Tue, 24 May 2022 11:14:48 -0700 Subject: [PATCH 1/3] Add support for embedding pretty printers via the `#[debugger_visualizer]` attribute. Add tests for embedding pretty printers and update documentation. Ensure all error checking for `#[debugger_visualizer]` is done up front and not when the `debugger_visualizer` query is run. Clean up potential ODR violations when embedding pretty printers into the `__rustc_debug_gdb_scripts_section__` section. Respond to PR comments and update documentation. --- .../rustc_codegen_llvm/src/debuginfo/gdb.rs | 56 ++++++++- compiler/rustc_codegen_ssa/src/back/link.rs | 74 ++++++------ compiler/rustc_codegen_ssa/src/back/linker.rs | 6 +- compiler/rustc_codegen_ssa/src/base.rs | 61 ++++++++-- compiler/rustc_codegen_ssa/src/lib.rs | 3 +- compiler/rustc_feature/src/builtin_attrs.rs | 2 +- compiler/rustc_passes/src/check_attr.rs | 56 +++++---- .../rustc_passes/src/debugger_visualizer.rs | 54 +++------ compiler/rustc_span/src/lib.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + .../language-features/debugger-visualizer.md | 4 +- ...ependency-with-embedded-visualizers.natvis | 10 ++ .../dependency-with-embedded-visualizers.py | 23 ++++ .../dependency-with-embedded-visualizers.rs | 19 +++ ...atvis => embedded-visualizer-point.natvis} | 10 +- .../debuginfo/embedded-visualizer-point.py | 23 ++++ src/test/debuginfo/embedded-visualizer.natvis | 10 ++ src/test/debuginfo/embedded-visualizer.py | 23 ++++ src/test/debuginfo/embedded-visualizer.rs | 112 ++++++++++++++++++ src/test/debuginfo/msvc-embedded-natvis.rs | 64 ---------- .../auxiliary/debugger-visualizer.natvis | 3 + .../feature-gate-debugger-visualizer.rs | 2 +- .../feature-gate-debugger-visualizer.stderr | 4 +- .../invalid-debugger-visualizer-option.rs | 5 +- .../invalid-debugger-visualizer-option.stderr | 14 ++- .../invalid-debugger-visualizer-target.rs | 1 - src/tools/compiletest/src/runtest.rs | 10 ++ 27 files changed, 453 insertions(+), 198 deletions(-) create mode 100644 src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.natvis create mode 100644 src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.py create mode 100644 src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.rs rename src/test/debuginfo/{msvc-embedded-natvis.natvis => embedded-visualizer-point.natvis} (54%) create mode 100644 src/test/debuginfo/embedded-visualizer-point.py create mode 100644 src/test/debuginfo/embedded-visualizer.natvis create mode 100644 src/test/debuginfo/embedded-visualizer.py create mode 100644 src/test/debuginfo/embedded-visualizer.rs delete mode 100644 src/test/debuginfo/msvc-embedded-natvis.rs create mode 100644 src/test/ui/feature-gates/auxiliary/debugger-visualizer.natvis diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs index 31a09242c5ada..355ef88382d24 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs @@ -5,11 +5,14 @@ use crate::llvm; use crate::builder::Builder; use crate::common::CodegenCx; use crate::value::Value; +use rustc_codegen_ssa::base::collect_debugger_visualizers_transitive; use rustc_codegen_ssa::traits::*; +use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::bug; -use rustc_session::config::DebugInfo; +use rustc_session::config::{CrateType, DebugInfo}; use rustc_span::symbol::sym; +use rustc_span::DebuggerVisualizerType; /// Inserts a side-effect free instruction sequence that makes sure that the /// .debug_gdb_scripts global is referenced, so it isn't removed by the linker. @@ -37,9 +40,33 @@ pub fn get_or_insert_gdb_debug_scripts_section_global<'ll>(cx: &CodegenCx<'ll, ' section_var.unwrap_or_else(|| { let section_name = b".debug_gdb_scripts\0"; - let section_contents = b"\x01gdb_load_rust_pretty_printers.py\0"; + let mut section_contents = Vec::new(); + + // Add the pretty printers for the standard library first. + section_contents.extend_from_slice(b"\x01gdb_load_rust_pretty_printers.py\0"); + + // Next, add the pretty printers that were specified via the `#[debugger_visualizer]` attribute. + let visualizers = collect_debugger_visualizers_transitive( + cx.tcx, + DebuggerVisualizerType::GdbPrettyPrinter, + ); + 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 + // is defined inline as opposed to in a file standalone file. + section_contents.extend_from_slice(b"\x04"); + let vis_name = format!("pretty-printer-{}-{}\n", crate_name.as_str(), index); + section_contents.extend_from_slice(vis_name.as_bytes()); + section_contents.extend_from_slice(&visualizer.src); + + // The final byte `0` tells GDB that the pretty printer has been + // fully defined and can continue searching for additional + // pretty printers. + section_contents.extend_from_slice(b"\0"); + } unsafe { + let section_contents = section_contents.as_slice(); let llvm_type = cx.type_array(cx.type_i8(), section_contents.len() as u64); let section_var = cx @@ -62,7 +89,32 @@ pub fn needs_gdb_debug_scripts_section(cx: &CodegenCx<'_, '_>) -> bool { let omit_gdb_pretty_printer_section = cx.tcx.sess.contains_name(cx.tcx.hir().krate_attrs(), sym::omit_gdb_pretty_printer_section); + // 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. + let embed_visualizers = cx.sess().crate_types().iter().any(|&crate_type| match crate_type { + CrateType::Executable | CrateType::Dylib | CrateType::Cdylib | CrateType::Staticlib => { + // 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 => { + // As per the above description, embedding visualizers for rlibs could + // lead to ODR violations so we skip this crate type as well. + false + } + }); + !omit_gdb_pretty_printer_section && cx.sess().opts.debuginfo != DebugInfo::None && cx.sess().target.emit_debug_gdb_scripts + && embed_visualizers } diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 04ec1e7f3c1af..aecbdffe499b1 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -5,7 +5,7 @@ use rustc_data_structures::memmap::Mmap; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{ErrorGuaranteed, Handler}; use rustc_fs_util::fix_windows_verbatim_for_gcc; -use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; +use rustc_hir::def_id::CrateNum; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::exported_symbols::SymbolExportKind; use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Strip}; @@ -18,6 +18,7 @@ use rustc_session::utils::NativeLibKind; /// need out of the shared crate context before we get rid of it. use rustc_session::{filesearch, Session}; use rustc_span::symbol::Symbol; +use rustc_span::DebuggerVisualizerFile; use rustc_target::spec::crt_objects::{CrtObjects, CrtObjectsFallback}; use rustc_target::spec::{LinkOutputKind, LinkerFlavor, LldFlavor, SplitDebuginfo}; use rustc_target::spec::{PanicStrategy, RelocModel, RelroLevel, SanitizerSet, Target}; @@ -37,6 +38,7 @@ use regex::Regex; use tempfile::Builder as TempFileBuilder; use std::borrow::Borrow; +use std::collections::BTreeSet; use std::ffi::OsString; use std::fs::{File, OpenOptions}; use std::io::{BufWriter, Write}; @@ -2099,14 +2101,16 @@ fn add_order_independent_options( // Pass optimization flags down to the linker. cmd.optimize(); - let debugger_visualizer_paths = if sess.target.is_like_msvc { - collect_debugger_visualizers(tmpdir, sess, &codegen_results.crate_info) - } else { - Vec::new() - }; + // Gather the set of NatVis files, if any, and write them out to a temp directory. + let natvis_visualizers = collect_natvis_visualizers( + tmpdir, + sess, + &codegen_results.crate_info.local_crate_name, + &codegen_results.crate_info.natvis_debugger_visualizers, + ); - // Pass debuginfo and strip flags down to the linker. - cmd.debuginfo(strip_value(sess), &debugger_visualizer_paths); + // Pass debuginfo, NatVis debugger visualizers and strip flags down to the linker. + cmd.debuginfo(strip_value(sess), &natvis_visualizers); // We want to prevent the compiler from accidentally leaking in any system libraries, // so by default we tell linkers not to link to any default libraries. @@ -2125,43 +2129,33 @@ fn add_order_independent_options( add_rpath_args(cmd, sess, codegen_results, out_filename); } -// Write the debugger visualizer files for each crate to the temp directory and gather the file paths. -fn collect_debugger_visualizers( +// Write the NatVis debugger visualizer files for each crate to the temp directory and gather the file paths. +fn collect_natvis_visualizers( tmpdir: &Path, sess: &Session, - crate_info: &CrateInfo, + crate_name: &Symbol, + natvis_debugger_visualizers: &BTreeSet, ) -> Vec { - let mut visualizer_paths = Vec::new(); - let debugger_visualizers = &crate_info.debugger_visualizers; - let mut index = 0; + let mut visualizer_paths = Vec::with_capacity(natvis_debugger_visualizers.len()); - for (&cnum, visualizers) in debugger_visualizers { - let crate_name = if cnum == LOCAL_CRATE { - crate_info.local_crate_name.as_str() - } else { - crate_info.crate_name[&cnum].as_str() - }; + for (index, visualizer) in natvis_debugger_visualizers.iter().enumerate() { + let visualizer_out_file = tmpdir.join(format!("{}-{}.natvis", crate_name.as_str(), index)); - for visualizer in visualizers { - let visualizer_out_file = tmpdir.join(format!("{}-{}.natvis", crate_name, index)); - - match fs::write(&visualizer_out_file, &visualizer.src) { - Ok(()) => { - visualizer_paths.push(visualizer_out_file.clone()); - index += 1; - } - Err(error) => { - sess.warn( - format!( - "Unable to write debugger visualizer file `{}`: {} ", - visualizer_out_file.display(), - error - ) - .as_str(), - ); - } - }; - } + match fs::write(&visualizer_out_file, &visualizer.src) { + Ok(()) => { + visualizer_paths.push(visualizer_out_file); + } + Err(error) => { + sess.warn( + format!( + "Unable to write debugger visualizer file `{}`: {} ", + visualizer_out_file.display(), + error + ) + .as_str(), + ); + } + }; } visualizer_paths } diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 2a71377d2f15b..e4236876463fc 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -183,7 +183,7 @@ pub trait Linker { fn optimize(&mut self); fn pgo_gen(&mut self); fn control_flow_guard(&mut self); - fn debuginfo(&mut self, strip: Strip, debugger_visualizers: &[PathBuf]); + fn debuginfo(&mut self, strip: Strip, natvis_debugger_visualizers: &[PathBuf]); fn no_crt_objects(&mut self); fn no_default_libraries(&mut self); fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType, symbols: &[String]); @@ -915,7 +915,7 @@ impl<'a> Linker for MsvcLinker<'a> { self.cmd.arg("/guard:cf"); } - fn debuginfo(&mut self, strip: Strip, debugger_visualizers: &[PathBuf]) { + fn debuginfo(&mut self, strip: Strip, natvis_debugger_visualizers: &[PathBuf]) { match strip { Strip::None => { // This will cause the Microsoft linker to generate a PDB file @@ -944,7 +944,7 @@ impl<'a> Linker for MsvcLinker<'a> { } // This will cause the Microsoft linker to embed .natvis info for all crates into the PDB file - for path in debugger_visualizers { + for path in natvis_debugger_visualizers { let mut arg = OsString::from("/NATVIS:"); arg.push(path); self.cmd.arg(arg); diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index d11f1534153ad..420adec456f61 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -31,11 +31,13 @@ use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, TyAndLayout}; use rustc_middle::ty::query::Providers; use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; use rustc_session::cgu_reuse_tracker::CguReuse; -use rustc_session::config::{self, EntryFnType, OutputType}; +use rustc_session::config::{self, CrateType, EntryFnType, OutputType}; use rustc_session::Session; use rustc_span::symbol::sym; +use rustc_span::{DebuggerVisualizerFile, DebuggerVisualizerType}; use rustc_target::abi::{Align, VariantIdx}; +use std::collections::BTreeSet; use std::convert::TryFrom; use std::ops::{Deref, DerefMut}; use std::time::{Duration, Instant}; @@ -487,6 +489,29 @@ fn get_argc_argv<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( } } +/// This function returns all of the debugger visualizers specified for the +/// current crate as well as all upstream crates transitively that match the +/// `visualizer_type` specified. +pub fn collect_debugger_visualizers_transitive( + tcx: TyCtxt<'_>, + visualizer_type: DebuggerVisualizerType, +) -> 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)), + ) + .filter(|visualizer| visualizer.visualizer_type == visualizer_type) + .cloned() + .collect::>() +} + pub fn codegen_crate( backend: B, tcx: TyCtxt<'_>, @@ -838,13 +863,8 @@ impl CrateInfo { missing_lang_items: Default::default(), dependency_formats: tcx.dependency_formats(()).clone(), windows_subsystem, - debugger_visualizers: Default::default(), + natvis_debugger_visualizers: Default::default(), }; - let debugger_visualizers = tcx.debugger_visualizers(LOCAL_CRATE).clone(); - if !debugger_visualizers.is_empty() { - info.debugger_visualizers.insert(LOCAL_CRATE, debugger_visualizers); - } - let lang_items = tcx.lang_items(); let crates = tcx.crates(()); @@ -882,14 +902,29 @@ impl CrateInfo { let missing = missing.iter().cloned().filter(|&l| lang_items::required(tcx, l)).collect(); info.missing_lang_items.insert(cnum, missing); + } - // Only include debugger visualizer files from crates that will be statically linked. - if used_crate_source.rlib.is_some() || used_crate_source.rmeta.is_some() { - let debugger_visualizers = tcx.debugger_visualizers(cnum).clone(); - if !debugger_visualizers.is_empty() { - info.debugger_visualizers.insert(cnum, debugger_visualizers); - } + let embed_visualizers = tcx.sess.crate_types().iter().any(|&crate_type| match crate_type { + CrateType::Executable | CrateType::Dylib | CrateType::Cdylib => { + // These are crate types for which we invoke the linker and can embed + // NatVis visualizers. + true + } + CrateType::ProcMacro => { + // We could embed NatVis for proc macro crates too (to improve the debugging + // experience for them) 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::Staticlib | CrateType::Rlib => { + // We don't invoke the linker for these, so we don't need to collect the NatVis for them. + false + } + }); + + if tcx.sess.target.is_like_msvc && embed_visualizers { + info.natvis_debugger_visualizers = + collect_debugger_visualizers_transitive(tcx, DebuggerVisualizerType::Natvis); } info diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 9e1fe588c5379..7fde700be393d 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -36,6 +36,7 @@ use rustc_session::cstore::{self, CrateSource}; use rustc_session::utils::NativeLibKind; use rustc_span::symbol::Symbol; use rustc_span::DebuggerVisualizerFile; +use std::collections::BTreeSet; use std::path::{Path, PathBuf}; pub mod back; @@ -157,7 +158,7 @@ pub struct CrateInfo { pub missing_lang_items: FxHashMap>, pub dependency_formats: Lrc, pub windows_subsystem: Option, - pub debugger_visualizers: FxHashMap>, + pub natvis_debugger_visualizers: BTreeSet, } #[derive(Encodable, Decodable)] diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 097493e8985fb..e021e9603f321 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -399,7 +399,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // RFC #3191: #[debugger_visualizer] support gated!( - debugger_visualizer, Normal, template!(List: r#"natvis_file = "...""#), + debugger_visualizer, Normal, template!(List: r#"natvis_file = "...", gdb_script_file = "...""#), DuplicatesOk, experimental!(debugger_visualizer) ), diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 3d5da114ecfde..3d38ff00eaba7 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -8,6 +8,7 @@ use rustc_ast::tokenstream::DelimSpan; use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MacArgs, MetaItemKind, NestedMetaItem}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{pluralize, struct_span_err, Applicability, MultiSpan}; +use rustc_expand::base::resolve_path; use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP}; use rustc_hir as hir; use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID}; @@ -1982,49 +1983,64 @@ impl CheckAttrVisitor<'_> { } } - let hints = match attr.meta_item_list() { - Some(meta_item_list) => meta_item_list, - None => { - self.emit_debugger_visualizer_err(attr); - return false; - } + let Some(hints) = attr.meta_item_list() else { + self.emit_debugger_visualizer_err(attr.span); + return false; }; let hint = match hints.len() { 1 => &hints[0], _ => { - self.emit_debugger_visualizer_err(attr); + self.emit_debugger_visualizer_err(attr.span); return false; } }; - if !hint.has_name(sym::natvis_file) { - self.emit_debugger_visualizer_err(attr); + let Some(meta_item) = hint.meta_item() else { + self.emit_debugger_visualizer_err(attr.span); return false; - } + }; - let meta_item = match hint.meta_item() { - Some(meta_item) => meta_item, - None => { - self.emit_debugger_visualizer_err(attr); + let visualizer_path = match (meta_item.name_or_empty(), meta_item.value_str()) { + (sym::natvis_file, Some(value)) => value, + (sym::gdb_script_file, Some(value)) => value, + (_, _) => { + self.emit_debugger_visualizer_err(meta_item.span); return false; } }; - match (meta_item.name_or_empty(), meta_item.value_str()) { - (sym::natvis_file, Some(_)) => true, - (_, _) => { - self.emit_debugger_visualizer_err(attr); + let file = + match resolve_path(&self.tcx.sess.parse_sess, visualizer_path.as_str(), attr.span) { + Ok(file) => file, + Err(mut err) => { + err.emit(); + return false; + } + }; + + match std::fs::File::open(&file) { + Ok(_) => true, + Err(err) => { + self.tcx + .sess + .struct_span_err( + meta_item.span, + &format!("couldn't read {}: {}", file.display(), err), + ) + .emit(); false } } } - fn emit_debugger_visualizer_err(&self, attr: &Attribute) { + fn emit_debugger_visualizer_err(&self, span: Span) { self.tcx .sess - .struct_span_err(attr.span, "invalid argument") + .struct_span_err(span, "invalid argument") .note(r#"expected: `natvis_file = "..."`"#) + .note(r#"OR"#) + .note(r#"expected: `gdb_script_file = "..."`"#) .emit(); } diff --git a/compiler/rustc_passes/src/debugger_visualizer.rs b/compiler/rustc_passes/src/debugger_visualizer.rs index 8305830bc988f..9b9ee93f63d99 100644 --- a/compiler/rustc_passes/src/debugger_visualizer.rs +++ b/compiler/rustc_passes/src/debugger_visualizer.rs @@ -21,9 +21,8 @@ fn check_for_debugger_visualizer<'tcx>( let attrs = tcx.hir().attrs(hir_id); for attr in attrs { if attr.has_name(sym::debugger_visualizer) { - let list = match attr.meta_item_list() { - Some(list) => list, - _ => continue, + let Some(list) = attr.meta_item_list() else { + continue }; let meta_item = match list.len() { @@ -34,45 +33,28 @@ fn check_for_debugger_visualizer<'tcx>( _ => continue, }; - let file = match (meta_item.name_or_empty(), meta_item.value_str()) { - (sym::natvis_file, Some(value)) => { + let visualizer_type = match meta_item.name_or_empty() { + sym::natvis_file => DebuggerVisualizerType::Natvis, + sym::gdb_script_file => DebuggerVisualizerType::GdbPrettyPrinter, + _ => continue, + }; + + let file = match meta_item.value_str() { + Some(value) => { match resolve_path(&tcx.sess.parse_sess, value.as_str(), attr.span) { Ok(file) => file, - Err(mut err) => { - err.emit(); - continue; - } + _ => continue, } } - (_, _) => continue, + None => continue, }; - if file.is_file() { - let contents = match std::fs::read(&file) { - Ok(contents) => contents, - Err(err) => { - tcx.sess - .struct_span_err( - attr.span, - &format!( - "Unable to read contents of file `{}`. {}", - file.display(), - err - ), - ) - .emit(); - continue; - } - }; - - debugger_visualizers.insert(DebuggerVisualizerFile::new( - Arc::from(contents), - DebuggerVisualizerType::Natvis, - )); - } else { - tcx.sess - .struct_span_err(attr.span, &format!("{} is not a valid file", file.display())) - .emit(); + match std::fs::read(&file) { + Ok(contents) => { + debugger_visualizers + .insert(DebuggerVisualizerFile::new(Arc::from(contents), visualizer_type)); + } + _ => {} } } } diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 8737e45487e90..4827eb0eaebca 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -1203,6 +1203,7 @@ impl SourceFileHash { #[derive(Copy, PartialEq, PartialOrd, Clone, Ord, Eq, Hash, Debug, Encodable, Decodable)] pub enum DebuggerVisualizerType { Natvis, + GdbPrettyPrinter, } /// A single debugger visualizer file. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 5c9c16350e469..e2a7ff3f92eff 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -721,6 +721,7 @@ symbols! { fundamental, future, future_trait, + gdb_script_file, ge, gen_future, gen_kill, diff --git a/src/doc/unstable-book/src/language-features/debugger-visualizer.md b/src/doc/unstable-book/src/language-features/debugger-visualizer.md index 4ab482fffb97c..c7a0414b67602 100644 --- a/src/doc/unstable-book/src/language-features/debugger-visualizer.md +++ b/src/doc/unstable-book/src/language-features/debugger-visualizer.md @@ -14,6 +14,7 @@ to embed a debugger visualizer file into the PDB/ELF generated by `rustc`. ``` rust,ignore (partial-example) #![feature(debugger_visualizer)] #![debugger_visualizer(natvis_file = "foo.natvis")] +#![debugger_visualizer(gdb_script_file = "foo.py")] struct Foo { } @@ -22,4 +23,5 @@ struct Foo { ## Limitations Currently, this feature only supports embedding Natvis files on `-windows-msvc` -targets when using the MSVC linker via the `natvis_file` meta item. +targets via the `natvis_file` meta item. `-windows-gnu` targets are not currently +supported. diff --git a/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.natvis b/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.natvis new file mode 100644 index 0000000000000..5900fcc01a6fe --- /dev/null +++ b/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.natvis @@ -0,0 +1,10 @@ + + + + {name} is {age} years old. + + name + age + + + diff --git a/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.py b/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.py new file mode 100644 index 0000000000000..2635ed487c803 --- /dev/null +++ b/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.py @@ -0,0 +1,23 @@ +import gdb + +class PersonPrinter: + "Print a Person" + + def __init__(self, val): + self.val = val + self.name = val["name"] + self.age = int(val["age"]) + + def to_string(self): + return "{} is {} years old.".format(self.name, self.age) + +def lookup(val): + lookup_tag = val.type.tag + if lookup_tag is None: + return None + if "dependency_with_embedded_visualizers::Person" == lookup_tag: + return PersonPrinter(val) + + return None + +gdb.current_objfile().pretty_printers.append(lookup) diff --git a/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.rs b/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.rs new file mode 100644 index 0000000000000..327515b10afd1 --- /dev/null +++ b/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.rs @@ -0,0 +1,19 @@ +// compile-flags:-g +// ignore-lldb +// no-prefer-dynamic + +#![feature(debugger_visualizer)] +#![debugger_visualizer(natvis_file = "dependency-with-embedded-visualizers.natvis")] +#![debugger_visualizer(gdb_script_file = "dependency-with-embedded-visualizers.py")] +#![crate_type = "rlib"] + +pub struct Person { + name: String, + age: i32, +} + +impl Person { + pub fn new(name: String, age: i32) -> Person { + Person { name: name, age: age } + } +} diff --git a/src/test/debuginfo/msvc-embedded-natvis.natvis b/src/test/debuginfo/embedded-visualizer-point.natvis similarity index 54% rename from src/test/debuginfo/msvc-embedded-natvis.natvis rename to src/test/debuginfo/embedded-visualizer-point.natvis index 201d014b5201d..d7bf6885dd40a 100644 --- a/src/test/debuginfo/msvc-embedded-natvis.natvis +++ b/src/test/debuginfo/embedded-visualizer-point.natvis @@ -1,18 +1,10 @@ - + ({x}, {y}) x y - - - ({a}, {b}) - - a - b - - diff --git a/src/test/debuginfo/embedded-visualizer-point.py b/src/test/debuginfo/embedded-visualizer-point.py new file mode 100644 index 0000000000000..d6b1af007855a --- /dev/null +++ b/src/test/debuginfo/embedded-visualizer-point.py @@ -0,0 +1,23 @@ +import gdb + +class PointPrinter: + "Print a Point" + + def __init__(self, val): + self.val = val + self.x = int(val["x"]) + self.y = int(val["y"]) + + def to_string(self): + return "({}, {})".format(self.x, self.y) + +def lookup(val): + lookup_tag = val.type.tag + if lookup_tag is None: + return None + if "embedded_visualizer::point::Point" == lookup_tag: + return PointPrinter(val) + + return None + +gdb.current_objfile().pretty_printers.append(lookup) diff --git a/src/test/debuginfo/embedded-visualizer.natvis b/src/test/debuginfo/embedded-visualizer.natvis new file mode 100644 index 0000000000000..100437f90e5d6 --- /dev/null +++ b/src/test/debuginfo/embedded-visualizer.natvis @@ -0,0 +1,10 @@ + + + + ({a}, {b}) + + a + b + + + diff --git a/src/test/debuginfo/embedded-visualizer.py b/src/test/debuginfo/embedded-visualizer.py new file mode 100644 index 0000000000000..8e4fab61614e2 --- /dev/null +++ b/src/test/debuginfo/embedded-visualizer.py @@ -0,0 +1,23 @@ +import gdb + +class LinePrinter: + "Print a Line" + + def __init__(self, val): + self.val = val + self.a = val["a"] + self.b = val["b"] + + def to_string(self): + return "({}, {})".format(self.a, self.b) + +def lookup(val): + lookup_tag = val.type.tag + if lookup_tag is None: + return None + if "embedded_visualizer::Line" == lookup_tag: + return LinePrinter(val) + + return None + +gdb.current_objfile().pretty_printers.append(lookup) diff --git a/src/test/debuginfo/embedded-visualizer.rs b/src/test/debuginfo/embedded-visualizer.rs new file mode 100644 index 0000000000000..0269015b46617 --- /dev/null +++ b/src/test/debuginfo/embedded-visualizer.rs @@ -0,0 +1,112 @@ +// compile-flags:-g +// min-gdb-version: 8.1 +// ignore-lldb + +// === CDB TESTS ================================================================================== + +// cdb-command: g + +// The .nvlist command in cdb does not always have a deterministic output +// for the order that NatVis files are displayed. + +// cdb-command: .nvlist +// cdb-check: [...].exe (embedded NatVis "[...]embedded_visualizer-0.natvis") + +// cdb-command: .nvlist +// cdb-check: [...].exe (embedded NatVis "[...]embedded_visualizer-1.natvis") + +// cdb-command: .nvlist +// cdb-check: [...].exe (embedded NatVis "[...]embedded_visualizer-2.natvis") + +// cdb-command: dx point_a +// cdb-check:point_a : (0, 0) [Type: embedded_visualizer::point::Point] +// cdb-check: [] [Type: embedded_visualizer::point::Point] +// cdb-check: [x] : 0 [Type: int] +// cdb-check: [y] : 0 [Type: int] + +// cdb-command: dx point_b +// cdb-check:point_b : (5, 8) [Type: embedded_visualizer::point::Point] +// cdb-check: [] [Type: embedded_visualizer::point::Point] +// cdb-check: [x] : 5 [Type: int] +// cdb-check: [y] : 8 [Type: int] + +// cdb-command: dx line +// cdb-check:line : ((0, 0), (5, 8)) [Type: embedded_visualizer::Line] +// cdb-check: [] [Type: embedded_visualizer::Line] +// cdb-check: [a] : (0, 0) [Type: embedded_visualizer::point::Point] +// cdb-check: [b] : (5, 8) [Type: embedded_visualizer::point::Point] + +// cdb-command: dx person +// cdb-check:person : "Person A" is 10 years old. [Type: dependency_with_embedded_visualizers::Person] +// cdb-check: [] [Type: dependency_with_embedded_visualizers::Person] +// cdb-check: [name] : "Person A" [Type: alloc::string::String] +// cdb-check: [age] : 10 [Type: int] + +// === GDB TESTS =================================================================================== + +// gdb-command: run + +// gdb-command: info auto-load python-scripts +// gdb-check:Yes pretty-printer-embedded_visualizer-0 +// gdb-check:Yes pretty-printer-embedded_visualizer-1 +// gdb-command: print point_a +// gdb-check:$1 = (0, 0) +// gdb-command: print point_b +// gdb-check:$2 = (5, 8) +// gdb-command: print line +// gdb-check:$3 = ((0, 0), (5, 8)) +// gdb-command: print person +// gdb-check:$4 = "Person A" is 10 years old. + +#![allow(unused_variables)] +#![feature(debugger_visualizer)] +#![debugger_visualizer(natvis_file = "embedded-visualizer.natvis")] +#![debugger_visualizer(gdb_script_file = "embedded-visualizer.py")] + +// aux-build: dependency-with-embedded-visualizers.rs +extern crate dependency_with_embedded_visualizers; + +use dependency_with_embedded_visualizers::Person; + +#[debugger_visualizer(natvis_file = "embedded-visualizer-point.natvis")] +#[debugger_visualizer(gdb_script_file = "embedded-visualizer-point.py")] +mod point { + pub struct Point { + x: i32, + y: i32, + } + + impl Point { + pub fn new(x: i32, y: i32) -> Point { + Point { x: x, y: y } + } + } +} + +use point::Point; + +pub struct Line { + a: Point, + b: Point, +} + +impl Line { + pub fn new(a: Point, b: Point) -> Line { + Line { a: a, b: b } + } +} + +fn main() { + let point_a = Point::new(0, 0); + let point_b = Point::new(5, 8); + let line = Line::new(point_a, point_b); + + let name = String::from("Person A"); + let person = Person::new(name, 10); + + zzz(); // #break +} + +fn zzz() { + () +} diff --git a/src/test/debuginfo/msvc-embedded-natvis.rs b/src/test/debuginfo/msvc-embedded-natvis.rs deleted file mode 100644 index f714fb2ad174d..0000000000000 --- a/src/test/debuginfo/msvc-embedded-natvis.rs +++ /dev/null @@ -1,64 +0,0 @@ -// only-cdb -// compile-flags:-g - -// === CDB TESTS ================================================================================== - -// cdb-command: g - -// cdb-command: .nvlist -// cdb-check: [...].exe (embedded NatVis "[...]msvc_embedded_natvis-0.natvis") - -// cdb-command: dx point_a -// cdb-check:point_a : (0, 0) [Type: msvc_embedded_natvis::Point] -// cdb-check: [] [Type: msvc_embedded_natvis::Point] -// cdb-check: [x] : 0 [Type: int] -// cdb-check: [y] : 0 [Type: int] - -// cdb-command: dx point_b -// cdb-check:point_b : (5, 8) [Type: msvc_embedded_natvis::Point] -// cdb-check: [] [Type: msvc_embedded_natvis::Point] -// cdb-check: [x] : 5 [Type: int] -// cdb-check: [y] : 8 [Type: int] - -// cdb-command: dx line -// cdb-check:line : ((0, 0), (5, 8)) [Type: msvc_embedded_natvis::Line] -// cdb-check: [] [Type: msvc_embedded_natvis::Line] -// cdb-check: [a] : (0, 0) [Type: msvc_embedded_natvis::Point] -// cdb-check: [b] : (5, 8) [Type: msvc_embedded_natvis::Point] - -#![feature(debugger_visualizer)] -#![debugger_visualizer(natvis_file = "msvc-embedded-natvis.natvis")] - -pub struct Point { - x: i32, - y: i32, -} - -impl Point { - pub fn new(x: i32, y: i32) -> Point { - Point { x: x, y: y } - } -} - -pub struct Line { - a: Point, - b: Point, -} - -impl Line { - pub fn new(a: Point, b: Point) -> Line { - Line { a: a, b: b } - } -} - -fn main() { - let point_a = Point::new(0, 0); - let point_b = Point::new(5, 8); - let line = Line::new(point_a, point_b); - - zzz(); // #break -} - -fn zzz() { - () -} diff --git a/src/test/ui/feature-gates/auxiliary/debugger-visualizer.natvis b/src/test/ui/feature-gates/auxiliary/debugger-visualizer.natvis new file mode 100644 index 0000000000000..6eb47e3d85be4 --- /dev/null +++ b/src/test/ui/feature-gates/auxiliary/debugger-visualizer.natvis @@ -0,0 +1,3 @@ + + + diff --git a/src/test/ui/feature-gates/feature-gate-debugger-visualizer.rs b/src/test/ui/feature-gates/feature-gate-debugger-visualizer.rs index 3f9eb27a0d654..4c4dc450d18bc 100644 --- a/src/test/ui/feature-gates/feature-gate-debugger-visualizer.rs +++ b/src/test/ui/feature-gates/feature-gate-debugger-visualizer.rs @@ -1,3 +1,3 @@ -#![debugger_visualizer(natvis_file = "../foo.natvis")] //~ ERROR the `#[debugger_visualizer]` attribute is an experimental feature +#![debugger_visualizer(natvis_file = "auxiliary/debugger-visualizer.natvis")] //~ ERROR the `#[debugger_visualizer]` attribute is an experimental feature fn main() {} diff --git a/src/test/ui/feature-gates/feature-gate-debugger-visualizer.stderr b/src/test/ui/feature-gates/feature-gate-debugger-visualizer.stderr index 721b2b185dad0..e9367fbc6c96c 100644 --- a/src/test/ui/feature-gates/feature-gate-debugger-visualizer.stderr +++ b/src/test/ui/feature-gates/feature-gate-debugger-visualizer.stderr @@ -1,8 +1,8 @@ error[E0658]: the `#[debugger_visualizer]` attribute is an experimental feature --> $DIR/feature-gate-debugger-visualizer.rs:1:1 | -LL | #![debugger_visualizer(natvis_file = "../foo.natvis")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![debugger_visualizer(natvis_file = "auxiliary/debugger-visualizer.natvis")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #95939 for more information = help: add `#![feature(debugger_visualizer)]` to the crate attributes to enable diff --git a/src/test/ui/invalid/invalid-debugger-visualizer-option.rs b/src/test/ui/invalid/invalid-debugger-visualizer-option.rs index 5332298f0ef90..5645a30ccee7d 100644 --- a/src/test/ui/invalid/invalid-debugger-visualizer-option.rs +++ b/src/test/ui/invalid/invalid-debugger-visualizer-option.rs @@ -1,4 +1,7 @@ +// normalize-stderr-test: "foo.random:.*\(" -> "foo.random: $$FILE_NOT_FOUND_MSG (" +// normalize-stderr-test: "os error \d+" -> "os error $$FILE_NOT_FOUND_CODE" + #![feature(debugger_visualizer)] #![debugger_visualizer(random_file = "../foo.random")] //~ ERROR invalid argument - +#![debugger_visualizer(natvis_file = "../foo.random")] //~ ERROR fn main() {} diff --git a/src/test/ui/invalid/invalid-debugger-visualizer-option.stderr b/src/test/ui/invalid/invalid-debugger-visualizer-option.stderr index 24ad9361fe3a3..afb8d16ee960a 100644 --- a/src/test/ui/invalid/invalid-debugger-visualizer-option.stderr +++ b/src/test/ui/invalid/invalid-debugger-visualizer-option.stderr @@ -1,10 +1,18 @@ error: invalid argument - --> $DIR/invalid-debugger-visualizer-option.rs:2:1 + --> $DIR/invalid-debugger-visualizer-option.rs:5:24 | LL | #![debugger_visualizer(random_file = "../foo.random")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: expected: `natvis_file = "..."` + = note: OR + = note: expected: `gdb_script_file = "..."` -error: aborting due to previous error +error: couldn't read $DIR/../foo.random: $FILE_NOT_FOUND_MSG (os error $FILE_NOT_FOUND_CODE) + --> $DIR/invalid-debugger-visualizer-option.rs:6:24 + | +LL | #![debugger_visualizer(natvis_file = "../foo.random")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors diff --git a/src/test/ui/invalid/invalid-debugger-visualizer-target.rs b/src/test/ui/invalid/invalid-debugger-visualizer-target.rs index 7668d092e614b..f0aba6a75c4cb 100644 --- a/src/test/ui/invalid/invalid-debugger-visualizer-target.rs +++ b/src/test/ui/invalid/invalid-debugger-visualizer-target.rs @@ -1,5 +1,4 @@ #![feature(debugger_visualizer)] #[debugger_visualizer(natvis_file = "../foo.natvis")] //~ ERROR attribute should be applied to a module - fn main() {} diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 64b6df3567a4a..494c8d771b07b 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -929,6 +929,16 @@ impl<'test> TestCx<'test> { "add-auto-load-safe-path {}\n", rust_pp_module_abs_path.replace(r"\", r"\\") )); + + let output_base_dir = self.output_base_dir().to_str().unwrap().to_owned(); + + // Add the directory containing the output binary to + // include embedded pretty printers to GDB's script + // auto loading safe path + script_str.push_str(&format!( + "add-auto-load-safe-path {}\n", + output_base_dir.replace(r"\", r"\\") + )); } } _ => { From ab1f8ed2d912cfdefb5d6d734456732d5bb064e7 Mon Sep 17 00:00:00 2001 From: ridwanabdillahi <91507758+ridwanabdillahi@users.noreply.github.com> Date: Tue, 24 May 2022 11:22:14 -0700 Subject: [PATCH 2/3] Update documentation. --- compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs index 355ef88382d24..5186aee57fb12 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs @@ -53,7 +53,7 @@ pub fn get_or_insert_gdb_debug_scripts_section_global<'ll>(cx: &CodegenCx<'ll, ' 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 - // is defined inline as opposed to in a file standalone file. + // is defined inline as opposed to in a standalone file. section_contents.extend_from_slice(b"\x04"); let vis_name = format!("pretty-printer-{}-{}\n", crate_name.as_str(), index); section_contents.extend_from_slice(vis_name.as_bytes()); @@ -107,7 +107,7 @@ pub fn needs_gdb_debug_scripts_section(cx: &CodegenCx<'_, '_>) -> bool { false } CrateType::Rlib => { - // As per the above description, embedding visualizers for rlibs could + // As per the above description, embedding pretty printers for rlibs could // lead to ODR violations so we skip this crate type as well. false } From 7ac62ce75cef963017245287f0a7b140e09589d7 Mon Sep 17 00:00:00 2001 From: ridwanabdillahi <91507758+ridwanabdillahi@users.noreply.github.com> Date: Wed, 25 May 2022 10:44:56 -0700 Subject: [PATCH 3/3] Respond to PR comments. --- compiler/rustc_passes/src/debugger_visualizer.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_passes/src/debugger_visualizer.rs b/compiler/rustc_passes/src/debugger_visualizer.rs index 9b9ee93f63d99..e08683fe23b20 100644 --- a/compiler/rustc_passes/src/debugger_visualizer.rs +++ b/compiler/rustc_passes/src/debugger_visualizer.rs @@ -54,7 +54,14 @@ fn check_for_debugger_visualizer<'tcx>( debugger_visualizers .insert(DebuggerVisualizerFile::new(Arc::from(contents), visualizer_type)); } - _ => {} + Err(err) => { + tcx.sess + .struct_span_err( + meta_item.span, + &format!("couldn't read {}: {}", file.display(), err), + ) + .emit(); + } } } }