Skip to content

Commit b20b747

Browse files
authored
Enable NatvisDiagnostics for VS Debug Option (#1357)
* Enable NatvisDiagnostics for VS Debug Option This PR enables Natvis Diagnostics for VS. If the NatvisDiagnostics in the VS Debug Options is enabled, it will output to the debug pane.
1 parent fd190c0 commit b20b747

File tree

18 files changed

+537
-42
lines changed

18 files changed

+537
-42
lines changed

src/DebugEngineHost.Common/HostLogChannel.cs

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public enum LogLevel
3737
// This must match the interface in DebugEngineHost.ref.cs
3838
public interface ILogChannel
3939
{
40+
void SetLogLevel(LogLevel level);
41+
4042
void WriteLine(LogLevel level, string message);
4143

4244
void WriteLine(LogLevel level, string format, params object[] values);

src/DebugEngineHost.Stub/DebugEngineHost.ref.cs

+24-3
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,12 @@ public enum LogLevel
217217
/// </summary>
218218
public interface ILogChannel
219219
{
220+
/// <summary>
221+
/// Changes the log level of the channel
222+
/// </summary>
223+
/// <param name="newLevel">The new log level to use.</param>
224+
void SetLogLevel(LogLevel newLevel);
225+
220226
/// <summary>
221227
/// Writes the given message with a newline to the log channel.
222228
/// </summary>
@@ -248,8 +254,6 @@ public interface ILogChannel
248254
/// </summary>
249255
public static class HostLogger
250256
{
251-
// EnableNatvisLogger is only used in OpenDebugAD7
252-
253257
/// <summary>
254258
/// Enables engine logging if not already enabled.
255259
/// </summary>
@@ -260,6 +264,16 @@ public static void EnableHostLogging(Action<string> callback, LogLevel level = L
260264
throw new NotImplementedException();
261265
}
262266

267+
/// <summary>
268+
/// Enables natvis logging if not already enabled.
269+
/// </summary>
270+
/// <param name="callback">The callback to use to send the natvis log.</param>
271+
/// <param name="level">The level of the log to filter the channel on.</param>
272+
public static void EnableNatvisDiagnostics(Action<string> callback, LogLevel level = LogLevel.Verbose)
273+
{
274+
throw new NotImplementedException();
275+
}
276+
263277
/// <summary>
264278
/// Sets the log file to write to.
265279
/// </summary>
@@ -269,7 +283,6 @@ public static void SetEngineLogFile(string logFile)
269283
throw new NotImplementedException();
270284
}
271285

272-
273286
/// <summary>
274287
/// Gets the engine log channel created by 'EnableHostLogging'
275288
/// </summary>
@@ -439,6 +452,14 @@ public static void FindNatvis(NatvisLoader loader)
439452
throw new NotImplementedException();
440453
}
441454

455+
/// <summary>
456+
/// Enable's tracking the VS 'Natvis Diagnostic Messages (C++ only)' setting.
457+
/// </summary>
458+
public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger)
459+
{
460+
throw new NotImplementedException();
461+
}
462+
442463
/// <summary>
443464
/// Return the solution's root directory, null if no solution
444465
/// </summary>

src/DebugEngineHost.VSCode/HostLogger.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public static class HostLogger
1212

1313
private static string s_engineLogFile;
1414

15-
public static void EnableNatvisLogger(Action<string> callback, LogLevel level = LogLevel.Verbose)
15+
public static void EnableNatvisDiagnostics(Action<string> callback, LogLevel level = LogLevel.Verbose)
1616
{
1717
if (s_natvisLogChannel == null)
1818
{

src/DebugEngineHost.VSCode/HostNatvisProject.cs

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ public static void FindNatvis(NatvisLoader loader)
1414
// In-solution natvis is not supported for VS Code now, so do nothing.
1515
}
1616

17+
public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger)
18+
{
19+
// VS Code does not have a registry setting for Natvis Diagnostics
20+
return null;
21+
}
22+
1723
public static string FindSolutionRoot()
1824
{
1925
// This was added in MIEngine to support breakpoint sourcefile mapping.

src/DebugEngineHost/HostConfigurationSection.cs

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using Microsoft.Win32;
5+
using Microsoft.Win32.SafeHandles;
56
using System;
67
using System.Collections.Generic;
78
using System.Linq;
@@ -42,5 +43,7 @@ public IEnumerable<string> GetValueNames()
4243
{
4344
return _key.GetValueNames();
4445
}
46+
47+
public SafeRegistryHandle Handle => _key.Handle;
4548
}
4649
}

src/DebugEngineHost/HostConfigurationStore.cs

+45
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,23 @@
88
using System.Globalization;
99
using System.IO;
1010
using System.Linq;
11+
using System.Runtime.InteropServices;
1112
using System.Text;
1213
using System.Threading.Tasks;
14+
using static Microsoft.VisualStudio.Shell.RegistrationAttribute;
1315

1416
namespace Microsoft.DebugEngineHost
1517
{
1618
public sealed class HostConfigurationStore
1719
{
1820
private const string DebuggerSectionName = "Debugger";
1921
private const string LaunchersSectionName = "MILaunchers";
22+
private const string NatvisDiagnosticsSectionName = "NatvisDiagnostics";
2023

2124
private string _engineId;
2225
private string _registryRoot;
26+
27+
// HKLM RegistryKey
2328
private RegistryKey _configKey;
2429

2530
public HostConfigurationStore(string registryRoot)
@@ -131,5 +136,45 @@ private object GetOptionalValue(string section, string valueName)
131136
return key.GetValue(valueName);
132137
}
133138
}
139+
140+
/// <summary>
141+
/// This method grabs the Debugger Subkey in HKCU
142+
/// </summary>
143+
/// <returns>The subkey of Debugger if it exists. Returns null otherwise.</returns>
144+
public HostConfigurationSection GetCurrentUserDebuggerSection()
145+
{
146+
using (RegistryKey hkcuRoot = Registry.CurrentUser.OpenSubKey(_registryRoot))
147+
{
148+
RegistryKey debuggerSection = hkcuRoot.OpenSubKey(DebuggerSectionName);
149+
if (debuggerSection != null)
150+
{
151+
return new HostConfigurationSection(debuggerSection);
152+
}
153+
return null;
154+
}
155+
}
156+
157+
/// <summary>
158+
/// Grabs the Debugger/NatvisDiagnostic subkey in HKCU
159+
/// </summary>
160+
/// <returns>The NatvisDiagnostic subkey if it exists. Returns null otherwise.</returns>
161+
public HostConfigurationSection GetNatvisDiagnosticSection()
162+
{
163+
using (RegistryKey hkcuRoot = Registry.CurrentUser.OpenSubKey(_registryRoot))
164+
{
165+
using (RegistryKey debuggerSection = hkcuRoot.OpenSubKey(DebuggerSectionName))
166+
{
167+
if (debuggerSection != null)
168+
{
169+
RegistryKey natvisDiagnosticKey = debuggerSection.OpenSubKey(NatvisDiagnosticsSectionName);
170+
if (natvisDiagnosticKey != null)
171+
{
172+
return new HostConfigurationSection(natvisDiagnosticKey);
173+
}
174+
}
175+
}
176+
}
177+
return null;
178+
}
134179
}
135180
}

src/DebugEngineHost/HostLogger.cs

+13
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ public static void EnableHostLogging(Action<string> callback, LogLevel level = L
2424
}
2525
}
2626

27+
public static void EnableNatvisDiagnostics(Action<string> callback, LogLevel level = LogLevel.Verbose)
28+
{
29+
if (s_natvisLogChannel== null)
30+
{
31+
s_natvisLogChannel = new HostLogChannel(callback, null, level);
32+
}
33+
}
34+
35+
public static void DisableNatvisDiagnostics()
36+
{
37+
s_natvisLogChannel = null;
38+
}
39+
2740
public static void SetEngineLogFile(string logFile)
2841
{
2942
s_engineLogFile = logFile;

src/DebugEngineHost/HostNatvisProject.cs

+124
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,26 @@
1818
using Microsoft.VisualStudio.Workspace;
1919
using Microsoft.VisualStudio.Workspace.Indexing;
2020
using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts;
21+
using Microsoft.Win32;
2122

2223
namespace Microsoft.DebugEngineHost
2324
{
25+
internal class RegisterMonitorWrapper : IDisposable
26+
{
27+
public RegistryMonitor CurrentMonitor { get; set; }
28+
29+
internal RegisterMonitorWrapper(RegistryMonitor currentMonitor)
30+
{
31+
CurrentMonitor = currentMonitor;
32+
}
33+
34+
public void Dispose()
35+
{
36+
CurrentMonitor.Dispose();
37+
CurrentMonitor = null;
38+
}
39+
}
40+
2441
/// <summary>
2542
/// Provides interactions with the host's source workspace to locate and load any natvis files
2643
/// in the project.
@@ -49,6 +66,113 @@ public static void FindNatvis(NatvisLoader loader)
4966
paths.ForEach((s) => loader(s));
5067
}
5168

69+
public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger)
70+
{
71+
RegisterMonitorWrapper rmw = null;
72+
73+
HostConfigurationSection natvisDiagnosticSection = configStore.GetNatvisDiagnosticSection();
74+
if (natvisDiagnosticSection != null)
75+
{
76+
// DiagnosticSection exists, set current log level and watch for changes.
77+
SetNatvisLogLevel(natvisDiagnosticSection);
78+
79+
rmw = new RegisterMonitorWrapper(CreateAndStartNatvisDiagnosticMonitor(natvisDiagnosticSection, natvisLogger));
80+
}
81+
else
82+
{
83+
// NatvisDiagnostic section has not been created, we need to watch for the creation.
84+
HostConfigurationSection debuggerSection = configStore.GetCurrentUserDebuggerSection();
85+
86+
if (debuggerSection != null)
87+
{
88+
// We only care about the debugger subkey's keys since we are waiting for the NatvisDiagnostics
89+
// section to be created.
90+
RegistryMonitor rm = new RegistryMonitor(debuggerSection, false, natvisLogger);
91+
92+
rmw = new RegisterMonitorWrapper(rm);
93+
94+
rm.RegChanged += (sender, e) =>
95+
{
96+
HostConfigurationSection checkForSection = configStore.GetNatvisDiagnosticSection();
97+
98+
if (checkForSection != null)
99+
{
100+
// NatvisDiagnostic section found. Update the logger
101+
SetNatvisLogLevel(checkForSection);
102+
103+
// Remove debugger section tracking
104+
IDisposable disposable = rmw.CurrentMonitor;
105+
106+
// Watch NatvisDiagnostic section
107+
rmw = new RegisterMonitorWrapper(CreateAndStartNatvisDiagnosticMonitor(natvisDiagnosticSection, natvisLogger));
108+
109+
disposable.Dispose();
110+
}
111+
};
112+
113+
rm.Start();
114+
}
115+
}
116+
117+
118+
return rmw;
119+
}
120+
121+
private static RegistryMonitor CreateAndStartNatvisDiagnosticMonitor(HostConfigurationSection natvisDiagnosticSection, ILogChannel natvisLogger)
122+
{
123+
RegistryMonitor rm = new RegistryMonitor(natvisDiagnosticSection, true, natvisLogger);
124+
125+
rm.RegChanged += (sender, e) =>
126+
{
127+
SetNatvisLogLevel(natvisDiagnosticSection);
128+
};
129+
130+
rm.Start();
131+
132+
return rm;
133+
}
134+
135+
private static void SetNatvisLogLevel(HostConfigurationSection natvisDiagnosticSection)
136+
{
137+
string level = natvisDiagnosticSection.GetValue("Level") as string;
138+
if (level != null)
139+
{
140+
level = level.ToLower(CultureInfo.InvariantCulture);
141+
}
142+
LogLevel logLevel;
143+
switch (level)
144+
{
145+
case "off":
146+
logLevel = LogLevel.None;
147+
break;
148+
case "error":
149+
logLevel = LogLevel.Error;
150+
break;
151+
case "warning":
152+
logLevel = LogLevel.Warning;
153+
break;
154+
case "verbose":
155+
logLevel = LogLevel.Verbose;
156+
break;
157+
default: // Unknown, default to Warning
158+
logLevel = LogLevel.Warning;
159+
break;
160+
}
161+
162+
if (logLevel == LogLevel.None)
163+
{
164+
HostLogger.DisableNatvisDiagnostics();
165+
}
166+
else
167+
{
168+
HostLogger.EnableNatvisDiagnostics((message) => {
169+
string formattedMessage = string.Format(CultureInfo.InvariantCulture, "Natvis: {0}", message);
170+
HostOutputWindow.WriteLaunchError(formattedMessage);
171+
}, logLevel);
172+
HostLogger.GetNatvisLogChannel().SetLogLevel(logLevel);
173+
}
174+
}
175+
52176
public static string FindSolutionRoot()
53177
{
54178
string path = null;

0 commit comments

Comments
 (0)