Rollup merge of #97028 - ridwanabdillahi:pretty-printer, r=michaelwoerister
Add support for embedding pretty printers via `#[debugger_visualizer]` attribute Initial support for [RFC 3191](https://github.com/rust-lang/rfcs/pull/3191) in PR https://github.com/rust-lang/rust/pull/91779 was scoped to supporting embedding NatVis files using a new attribute. This PR implements the pretty printer support as stated in the RFC mentioned above. This change includes embedding pretty printers in the `.debug_gdb_scripts` just as the pretty printers for rustc are embedded today. Also added additional tests for embedded pretty printers. Additionally cleaned up error checking so all error checking is done up front regardless of the current target. RFC: https://github.com/rust-lang/rfcs/pull/3191
This commit is contained in:
commit
239287f013
@ -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 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 pretty printers 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
|
||||
}
|
||||
|
@ -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));
|
||||
|
||||
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(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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(),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
visualizer_paths
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
),
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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,35 @@ fn check_for_debugger_visualizer<'tcx>(
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let file = match (meta_item.name_or_empty(), meta_item.value_str()) {
|
||||
(sym::natvis_file, Some(value)) => {
|
||||
match resolve_path(&tcx.sess.parse_sess, value.as_str(), attr.span) {
|
||||
Ok(file) => file,
|
||||
Err(mut err) => {
|
||||
err.emit();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
(_, _) => continue,
|
||||
let visualizer_type = match meta_item.name_or_empty() {
|
||||
sym::natvis_file => DebuggerVisualizerType::Natvis,
|
||||
sym::gdb_script_file => DebuggerVisualizerType::GdbPrettyPrinter,
|
||||
_ => 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;
|
||||
let file = match meta_item.value_str() {
|
||||
Some(value) => {
|
||||
match resolve_path(&tcx.sess.parse_sess, value.as_str(), attr.span) {
|
||||
Ok(file) => file,
|
||||
_ => continue,
|
||||
}
|
||||
};
|
||||
}
|
||||
None => 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));
|
||||
}
|
||||
Err(err) => {
|
||||
tcx.sess
|
||||
.struct_span_err(
|
||||
meta_item.span,
|
||||
&format!("couldn't read {}: {}", file.display(), err),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -722,6 +722,7 @@ symbols! {
|
||||
fundamental,
|
||||
future,
|
||||
future_trait,
|
||||
gdb_script_file,
|
||||
ge,
|
||||
gen_future,
|
||||
gen_kill,
|
||||
|
@ -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.
|
||||
|
@ -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>
|
@ -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)
|
@ -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 }
|
||||
}
|
||||
}
|
@ -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>
|
23
src/test/debuginfo/embedded-visualizer-point.py
Normal file
23
src/test/debuginfo/embedded-visualizer-point.py
Normal file
@ -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)
|
10
src/test/debuginfo/embedded-visualizer.natvis
Normal file
10
src/test/debuginfo/embedded-visualizer.natvis
Normal file
@ -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>
|
23
src/test/debuginfo/embedded-visualizer.py
Normal file
23
src/test/debuginfo/embedded-visualizer.py
Normal file
@ -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)
|
112
src/test/debuginfo/embedded-visualizer.rs
Normal file
112
src/test/debuginfo/embedded-visualizer.rs
Normal file
@ -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() {
|
||||
()
|
||||
}
|
@ -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() {
|
||||
()
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
</AutoVisualizer>
|
@ -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() {}
|
||||
|
@ -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
|
||||
|
@ -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() {}
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![feature(debugger_visualizer)]
|
||||
|
||||
#[debugger_visualizer(natvis_file = "../foo.natvis")] //~ ERROR attribute should be applied to a module
|
||||
|
||||
fn main() {}
|
||||
|
@ -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"\\")
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user