Skip to content

Commit

Permalink
Enable NatvisDiagnostics for VS Debug Option (#1357)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
WardenGnaw authored Oct 10, 2022
1 parent fd190c0 commit b20b747
Show file tree
Hide file tree
Showing 18 changed files with 537 additions and 42 deletions.
2 changes: 2 additions & 0 deletions src/DebugEngineHost.Common/HostLogChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public enum LogLevel
// This must match the interface in DebugEngineHost.ref.cs
public interface ILogChannel
{
void SetLogLevel(LogLevel level);

void WriteLine(LogLevel level, string message);

void WriteLine(LogLevel level, string format, params object[] values);
Expand Down
27 changes: 24 additions & 3 deletions src/DebugEngineHost.Stub/DebugEngineHost.ref.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ public enum LogLevel
/// </summary>
public interface ILogChannel
{
/// <summary>
/// Changes the log level of the channel
/// </summary>
/// <param name="newLevel">The new log level to use.</param>
void SetLogLevel(LogLevel newLevel);

/// <summary>
/// Writes the given message with a newline to the log channel.
/// </summary>
Expand Down Expand Up @@ -248,8 +254,6 @@ public interface ILogChannel
/// </summary>
public static class HostLogger
{
// EnableNatvisLogger is only used in OpenDebugAD7

/// <summary>
/// Enables engine logging if not already enabled.
/// </summary>
Expand All @@ -260,6 +264,16 @@ public static void EnableHostLogging(Action<string> callback, LogLevel level = L
throw new NotImplementedException();
}

/// <summary>
/// Enables natvis logging if not already enabled.
/// </summary>
/// <param name="callback">The callback to use to send the natvis log.</param>
/// <param name="level">The level of the log to filter the channel on.</param>
public static void EnableNatvisDiagnostics(Action<string> callback, LogLevel level = LogLevel.Verbose)
{
throw new NotImplementedException();
}

/// <summary>
/// Sets the log file to write to.
/// </summary>
Expand All @@ -269,7 +283,6 @@ public static void SetEngineLogFile(string logFile)
throw new NotImplementedException();
}


/// <summary>
/// Gets the engine log channel created by 'EnableHostLogging'
/// </summary>
Expand Down Expand Up @@ -439,6 +452,14 @@ public static void FindNatvis(NatvisLoader loader)
throw new NotImplementedException();
}

/// <summary>
/// Enable's tracking the VS 'Natvis Diagnostic Messages (C++ only)' setting.
/// </summary>
public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger)
{
throw new NotImplementedException();
}

/// <summary>
/// Return the solution's root directory, null if no solution
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/DebugEngineHost.VSCode/HostLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static class HostLogger

private static string s_engineLogFile;

public static void EnableNatvisLogger(Action<string> callback, LogLevel level = LogLevel.Verbose)
public static void EnableNatvisDiagnostics(Action<string> callback, LogLevel level = LogLevel.Verbose)
{
if (s_natvisLogChannel == null)
{
Expand Down
6 changes: 6 additions & 0 deletions src/DebugEngineHost.VSCode/HostNatvisProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ public static void FindNatvis(NatvisLoader loader)
// In-solution natvis is not supported for VS Code now, so do nothing.
}

public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger)
{
// VS Code does not have a registry setting for Natvis Diagnostics
return null;
}

public static string FindSolutionRoot()
{
// This was added in MIEngine to support breakpoint sourcefile mapping.
Expand Down
3 changes: 3 additions & 0 deletions src/DebugEngineHost/HostConfigurationSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -42,5 +43,7 @@ public IEnumerable<string> GetValueNames()
{
return _key.GetValueNames();
}

public SafeRegistryHandle Handle => _key.Handle;
}
}
45 changes: 45 additions & 0 deletions src/DebugEngineHost/HostConfigurationStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static Microsoft.VisualStudio.Shell.RegistrationAttribute;

namespace Microsoft.DebugEngineHost
{
public sealed class HostConfigurationStore
{
private const string DebuggerSectionName = "Debugger";
private const string LaunchersSectionName = "MILaunchers";
private const string NatvisDiagnosticsSectionName = "NatvisDiagnostics";

private string _engineId;
private string _registryRoot;

// HKLM RegistryKey
private RegistryKey _configKey;

public HostConfigurationStore(string registryRoot)
Expand Down Expand Up @@ -131,5 +136,45 @@ private object GetOptionalValue(string section, string valueName)
return key.GetValue(valueName);
}
}

/// <summary>
/// This method grabs the Debugger Subkey in HKCU
/// </summary>
/// <returns>The subkey of Debugger if it exists. Returns null otherwise.</returns>
public HostConfigurationSection GetCurrentUserDebuggerSection()
{
using (RegistryKey hkcuRoot = Registry.CurrentUser.OpenSubKey(_registryRoot))
{
RegistryKey debuggerSection = hkcuRoot.OpenSubKey(DebuggerSectionName);
if (debuggerSection != null)
{
return new HostConfigurationSection(debuggerSection);
}
return null;
}
}

/// <summary>
/// Grabs the Debugger/NatvisDiagnostic subkey in HKCU
/// </summary>
/// <returns>The NatvisDiagnostic subkey if it exists. Returns null otherwise.</returns>
public HostConfigurationSection GetNatvisDiagnosticSection()
{
using (RegistryKey hkcuRoot = Registry.CurrentUser.OpenSubKey(_registryRoot))
{
using (RegistryKey debuggerSection = hkcuRoot.OpenSubKey(DebuggerSectionName))
{
if (debuggerSection != null)
{
RegistryKey natvisDiagnosticKey = debuggerSection.OpenSubKey(NatvisDiagnosticsSectionName);
if (natvisDiagnosticKey != null)
{
return new HostConfigurationSection(natvisDiagnosticKey);
}
}
}
}
return null;
}
}
}
13 changes: 13 additions & 0 deletions src/DebugEngineHost/HostLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ public static void EnableHostLogging(Action<string> callback, LogLevel level = L
}
}

public static void EnableNatvisDiagnostics(Action<string> callback, LogLevel level = LogLevel.Verbose)
{
if (s_natvisLogChannel== null)
{
s_natvisLogChannel = new HostLogChannel(callback, null, level);
}
}

public static void DisableNatvisDiagnostics()
{
s_natvisLogChannel = null;
}

public static void SetEngineLogFile(string logFile)
{
s_engineLogFile = logFile;
Expand Down
124 changes: 124 additions & 0 deletions src/DebugEngineHost/HostNatvisProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,26 @@
using Microsoft.VisualStudio.Workspace;
using Microsoft.VisualStudio.Workspace.Indexing;
using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts;
using Microsoft.Win32;

namespace Microsoft.DebugEngineHost
{
internal class RegisterMonitorWrapper : IDisposable
{
public RegistryMonitor CurrentMonitor { get; set; }

internal RegisterMonitorWrapper(RegistryMonitor currentMonitor)
{
CurrentMonitor = currentMonitor;
}

public void Dispose()
{
CurrentMonitor.Dispose();
CurrentMonitor = null;
}
}

/// <summary>
/// Provides interactions with the host's source workspace to locate and load any natvis files
/// in the project.
Expand Down Expand Up @@ -49,6 +66,113 @@ public static void FindNatvis(NatvisLoader loader)
paths.ForEach((s) => loader(s));
}

public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger)
{
RegisterMonitorWrapper rmw = null;

HostConfigurationSection natvisDiagnosticSection = configStore.GetNatvisDiagnosticSection();
if (natvisDiagnosticSection != null)
{
// DiagnosticSection exists, set current log level and watch for changes.
SetNatvisLogLevel(natvisDiagnosticSection);

rmw = new RegisterMonitorWrapper(CreateAndStartNatvisDiagnosticMonitor(natvisDiagnosticSection, natvisLogger));
}
else
{
// NatvisDiagnostic section has not been created, we need to watch for the creation.
HostConfigurationSection debuggerSection = configStore.GetCurrentUserDebuggerSection();

if (debuggerSection != null)
{
// We only care about the debugger subkey's keys since we are waiting for the NatvisDiagnostics
// section to be created.
RegistryMonitor rm = new RegistryMonitor(debuggerSection, false, natvisLogger);

rmw = new RegisterMonitorWrapper(rm);

rm.RegChanged += (sender, e) =>
{
HostConfigurationSection checkForSection = configStore.GetNatvisDiagnosticSection();

if (checkForSection != null)
{
// NatvisDiagnostic section found. Update the logger
SetNatvisLogLevel(checkForSection);

// Remove debugger section tracking
IDisposable disposable = rmw.CurrentMonitor;

// Watch NatvisDiagnostic section
rmw = new RegisterMonitorWrapper(CreateAndStartNatvisDiagnosticMonitor(natvisDiagnosticSection, natvisLogger));

disposable.Dispose();
}
};

rm.Start();
}
}


return rmw;
}

private static RegistryMonitor CreateAndStartNatvisDiagnosticMonitor(HostConfigurationSection natvisDiagnosticSection, ILogChannel natvisLogger)
{
RegistryMonitor rm = new RegistryMonitor(natvisDiagnosticSection, true, natvisLogger);

rm.RegChanged += (sender, e) =>
{
SetNatvisLogLevel(natvisDiagnosticSection);
};

rm.Start();

return rm;
}

private static void SetNatvisLogLevel(HostConfigurationSection natvisDiagnosticSection)
{
string level = natvisDiagnosticSection.GetValue("Level") as string;
if (level != null)
{
level = level.ToLower(CultureInfo.InvariantCulture);
}
LogLevel logLevel;
switch (level)
{
case "off":
logLevel = LogLevel.None;
break;
case "error":
logLevel = LogLevel.Error;
break;
case "warning":
logLevel = LogLevel.Warning;
break;
case "verbose":
logLevel = LogLevel.Verbose;
break;
default: // Unknown, default to Warning
logLevel = LogLevel.Warning;
break;
}

if (logLevel == LogLevel.None)
{
HostLogger.DisableNatvisDiagnostics();
}
else
{
HostLogger.EnableNatvisDiagnostics((message) => {
string formattedMessage = string.Format(CultureInfo.InvariantCulture, "Natvis: {0}", message);
HostOutputWindow.WriteLaunchError(formattedMessage);
}, logLevel);
HostLogger.GetNatvisLogChannel().SetLogLevel(logLevel);
}
}

public static string FindSolutionRoot()
{
string path = null;
Expand Down
Loading

0 comments on commit b20b747

Please sign in to comment.