Description
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 HSTRING
s:
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
towin_nv
to prevent any sort of name clashes in .natvis lookup. .nvunloadall
followed by an explicit.nvload
with a copy ofwindows.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.,
>
->>
) 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.