Skip to content

Problems with natvis support #2836

Closed
@kennykerr

Description

@kennykerr

Originally posted by @tim-weis in #2023 (comment)

Thanks for the feedback. This has been rather insightful.

I went ahead and did some more research. The TL;DR is: The HSTRING visualizer works with CDB but not with other debuggers I tried (WinDbg, Visual Studio).

First, though, the windows::core vs. windows_core path discrepancy was down to me copy-pasting outdated expected test output from #2023. This has since been changed, and all debuggers (and visualizers) agree on windows_core as the package-relative path prefix. That turned out to be a red herring.

For reference, here's the full repro (slightly modified from my previous comment).

Cargo.toml

[package]
name = "win_nv"
version = "0.0.0"
edition = "2021"

[dependencies]
windows = { version = "0.52.0", features = [] }

.cargo/config.toml

[build]
# Request that visualizers are embedded into the PDB
rustflags = ["--cfg=windows_debugger_visualizer"]

src/main.rs

use windows::core::HSTRING;

#[inline(never)]
fn __break() {}

fn main() {
    let empty = HSTRING::new();
    println!("{empty}");

    let hstring = HSTRING::from("This is an HSTRING");
    println!("{hstring}");

    __break();
}

This is following a pattern I first discovered in Ridwan's debugger_test crate: It introduces a function (__break()) for the sole purpose of having a symbol to set a breakpoint on, providing a convenient way to insert checkpoints at which execution pauses. This works in combination with the bm command (bm *!*::__break "gu") instructing the debugger to "Go Up" ("gu") whenever the function is hit, taking us back to the scope of interest.

With the crate set up the following command line launches right into the CDB debugger:

cargo b && cd target\debug && "%WindowsSdkDir%Debuggers\x64\cdb.exe" -o win_nv.exe

Once in the debugger, we can set the breakpoint, run to the checkpoint and inspect the HSTRINGs:

0:000> bm *!*::__break "gu"
*** WARNING: Unable to verify checksum for win_nv.exe
  1: 00007ff6`e62d1010 @!"win_nv!win_nv::__break"
0:000> g

This is an HSTRING
win_nv!win_nv::main+0x104:
00007ff6`e62d1124 eb00            jmp     win_nv!win_nv::main+0x106 (00007ff6`e62d1126)
0:000> dx empty
empty            : "" [Type: windows_core::strings::hstring::HSTRING]
    [<Raw View>]     [Type: windows_core::strings::hstring::HSTRING]
    [len]            : 0x0 [Type: unsigned int]
0:000> dx hstring
hstring          : "This is an HSTRING" [Type: windows_core::strings::hstring::HSTRING]
    [<Raw View>]     [Type: windows_core::strings::hstring::HSTRING]
    [len]            : 0x12 [Type: unsigned int]
    [ref_count]      : 1 [Type: windows_core::imp::ref_count::RefCount]
    [flags]          : 0x0 [Type: unsigned int]
    [chars]
0:000> q

That explains why the tests are succeeding. Moving to WinDbg had some surprises, though: Setting the breakpoint the same way as in CDB behaved differently. The "gu" command string went up (at least) one stack frame more than expected. I'm not sure what's up with that, but I just replaced the command string with "pt" ("Step to Next Return") which seemingly worked. For completeness: bm *!*::__break "pt".

Once there, the debugger produced unexpected results for the HSTRING variables:

0:000> dx empty
empty                 [Type: windows_core::strings::hstring::HSTRING]
    [<Raw View>]     [Type: windows_core::strings::hstring::HSTRING]
    [len]            : Unexpected failure to dereference object
0:000> dx hstring
hstring                 [Type: windows_core::strings::hstring::HSTRING]
    [<Raw View>]     [Type: windows_core::strings::hstring::HSTRING]
    [len]            : Unexpected failure to dereference object

Can anyone please verify my observations?


Rust: 1.75.0 (stable)
CDB: cdb version 10.0.22621.382
WinDbg: Debugger client version: 1.2308.2002.0; Debugger engine version: 10.0.25921.1001
Host OS: Windows 10 19045.3930


While this is starting to feel like I'm losing my mind, here are a few more things I tried to make sure I'm looking at the same thing the debugger is:

  • Renamed the executable from windows_natvis to win_nv to prevent any sort of name clashes in .natvis lookup.
  • .nvunloadall followed by an explicit .nvload with a copy of windows.natvis from this repo.
  • Verified that the .natvis was loaded using .nvlist.
  • Enabled natvis diagnostics in Visual Studio, which didn't uncover any obvious issues.
  • Dumped all visualizers in the PDB and compared them against the respective sources (4 standard Rust visualizers plus windows.natvis from here).
  • Replaced all reserved XML tokens with XML entities (e.g., > -> &gt;) and reloaded the modified .natvis, just to be sure.
  • Probably a few other things I forget...

None of the above had any observable effect so I'm confident that windows.natvis is actually loaded and evaluated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions