about summary refs log tree commit diff
diff options
context:
space:
mode:
authorridwanabdillahi <91507758+ridwanabdillahi@users.noreply.github.com>2022-05-24 11:14:48 -0700
committerridwanabdillahi <91507758+ridwanabdillahi@users.noreply.github.com>2022-05-24 11:14:48 -0700
commit60458b97e750452409aa7e7e2307be6205512167 (patch)
treeb1f2bdb75228661297b753044c3f7e1e3fb0ba42
parentee9726cb10399724c943a16277c78a07e53f505c (diff)
downloadrust-60458b97e750452409aa7e7e2307be6205512167.tar.gz
rust-60458b97e750452409aa7e7e2307be6205512167.zip
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.
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs56
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs74
-rw-r--r--compiler/rustc_codegen_ssa/src/back/linker.rs6
-rw-r--r--compiler/rustc_codegen_ssa/src/base.rs61
-rw-r--r--compiler/rustc_codegen_ssa/src/lib.rs3
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs2
-rw-r--r--compiler/rustc_passes/src/check_attr.rs56
-rw-r--r--compiler/rustc_passes/src/debugger_visualizer.rs54
-rw-r--r--compiler/rustc_span/src/lib.rs1
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--src/doc/unstable-book/src/language-features/debugger-visualizer.md4
-rw-r--r--src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.natvis10
-rw-r--r--src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.py23
-rw-r--r--src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.rs19
-rw-r--r--src/test/debuginfo/embedded-visualizer-point.natvis (renamed from src/test/debuginfo/msvc-embedded-natvis.natvis)10
-rw-r--r--src/test/debuginfo/embedded-visualizer-point.py23
-rw-r--r--src/test/debuginfo/embedded-visualizer.natvis10
-rw-r--r--src/test/debuginfo/embedded-visualizer.py23
-rw-r--r--src/test/debuginfo/embedded-visualizer.rs112
-rw-r--r--src/test/debuginfo/msvc-embedded-natvis.rs64
-rw-r--r--src/test/ui/feature-gates/auxiliary/debugger-visualizer.natvis3
-rw-r--r--src/test/ui/feature-gates/feature-gate-debugger-visualizer.rs2
-rw-r--r--src/test/ui/feature-gates/feature-gate-debugger-visualizer.stderr4
-rw-r--r--src/test/ui/invalid/invalid-debugger-visualizer-option.rs5
-rw-r--r--src/test/ui/invalid/invalid-debugger-visualizer-option.stderr14
-rw-r--r--src/test/ui/invalid/invalid-debugger-visualizer-target.rs1
-rw-r--r--src/tools/compiletest/src/runtest.rs10
27 files changed, 453 insertions, 198 deletions
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs
index 31a09242c5a..355ef88382d 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 04ec1e7f3c1..aecbdffe499 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<DebuggerVisualizerFile>,
 ) -> Vec<PathBuf> {
-    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 2a71377d2f1..e4236876463 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 d11f1534153..420adec456f 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<DebuggerVisualizerFile> {
+    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::<BTreeSet<_>>()
+}
+
 pub fn codegen_crate<B: ExtraBackendMethods>(
     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 9e1fe588c53..7fde700be39 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<CrateNum, Vec<LangItem>>,
     pub dependency_formats: Lrc<Dependencies>,
     pub windows_subsystem: Option<String>,
-    pub debugger_visualizers: FxHashMap<CrateNum, Vec<DebuggerVisualizerFile>>,
+    pub natvis_debugger_visualizers: BTreeSet<DebuggerVisualizerFile>,
 }
 
 #[derive(Encodable, Decodable)]
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 097493e8985..e021e9603f3 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 3d5da114ecf..3d38ff00eab 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 8305830bc98..9b9ee93f63d 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 8737e45487e..4827eb0eaeb 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 5c9c16350e4..e2a7ff3f92e 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 4ab482fffb9..c7a0414b676 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 00000000000..5900fcc01a6
--- /dev/null
+++ b/src/test/debuginfo/auxiliary/dependency-with-embedded-visualizers.natvis
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
+  <Type Name="dependency_with_embedded_visualizers::Person">
+    <DisplayString>{name} is {age} years old.</DisplayString>
+    <Expand>
+      <Item Name="[name]">name</Item>
+      <Item Name="[age]">age</Item>
+    </Expand>
+  </Type>
+</AutoVisualizer>
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 00000000000..2635ed487c8
--- /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 00000000000..327515b10af
--- /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
index 201d014b520..d7bf6885dd4 100644
--- a/src/test/debuginfo/msvc-embedded-natvis.natvis
+++ b/src/test/debuginfo/embedded-visualizer-point.natvis
@@ -1,18 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
-  <Type Name="msvc_embedded_natvis::Point">
+  <Type Name="embedded_visualizer::point::Point">
     <DisplayString>({x}, {y})</DisplayString>
     <Expand>
       <Item Name="[x]">x</Item>
       <Item Name="[y]">y</Item>
     </Expand>
   </Type>
-
-  <Type Name="msvc_embedded_natvis::Line">
-    <DisplayString>({a}, {b})</DisplayString>
-    <Expand>
-      <Item Name="[a]">a</Item>
-      <Item Name="[b]">b</Item>
-    </Expand>
-  </Type>
 </AutoVisualizer>
diff --git a/src/test/debuginfo/embedded-visualizer-point.py b/src/test/debuginfo/embedded-visualizer-point.py
new file mode 100644
index 00000000000..d6b1af00785
--- /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 00000000000..100437f90e5
--- /dev/null
+++ b/src/test/debuginfo/embedded-visualizer.natvis
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
+  <Type Name="embedded_visualizer::Line">
+    <DisplayString>({a}, {b})</DisplayString>
+    <Expand>
+      <Item Name="[a]">a</Item>
+      <Item Name="[b]">b</Item>
+    </Expand>
+  </Type>
+</AutoVisualizer>
diff --git a/src/test/debuginfo/embedded-visualizer.py b/src/test/debuginfo/embedded-visualizer.py
new file mode 100644
index 00000000000..8e4fab61614
--- /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 00000000000..0269015b466
--- /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:    [<Raw View>]     [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:    [<Raw View>]     [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:    [<Raw View>]     [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:    [<Raw View>]     [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 f714fb2ad17..00000000000
--- 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:    [<Raw View>]     [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:    [<Raw View>]     [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:    [<Raw View>]     [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 00000000000..6eb47e3d85b
--- /dev/null
+++ b/src/test/ui/feature-gates/auxiliary/debugger-visualizer.natvis
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
+</AutoVisualizer>
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 3f9eb27a0d6..4c4dc450d18 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 721b2b185da..e9367fbc6c9 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 <https://github.com/rust-lang/rust/issues/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 5332298f0ef..5645a30ccee 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 24ad9361fe3..afb8d16ee96 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 7668d092e61..f0aba6a75c4 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 64b6df3567a..494c8d771b0 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"\\")
+                        ));
                     }
                 }
                 _ => {