Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/AndroidDebugLauncher/Launcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ private LaunchOptions SetupForDebuggingWorker(CancellationToken token)

launchOptions.DebuggerMIMode = MIMode.Gdb;

launchOptions.VisualizerFile = "Microsoft.Android.natvis";
launchOptions.VisualizerFiles.Add("Microsoft.Android.natvis");
launchOptions.WaitDynamicLibLoad = _launchOptions.WaitDynamicLibLoad;

return launchOptions;
Expand Down
40 changes: 36 additions & 4 deletions src/MICore/JsonLaunchOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ public abstract partial class BaseOptions
public string TargetArchitecture { get; set; }

/// <summary>
/// .natvis file to be used when debugging this process. This option is not compatible with GDB pretty printing. Please also see "showDisplayString" if using this setting.
/// .natvis files to be used when debugging this process. This option is not compatible with GDB pretty printing. Please also see "showDisplayString" if using this setting.
/// </summary>
[JsonProperty("visualizerFile", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string VisualizerFile { get; set; }
[JsonConverter(typeof(VisualizerFileConverter))]
public List<string> VisualizerFile { get; set; }

/// <summary>
/// When a visualizerFile is specified, showDisplayString will enable the display string. Turning this option on can cause slower performance during debugging.
Expand Down Expand Up @@ -123,6 +124,37 @@ public abstract partial class BaseOptions
public UnknownBreakpointHandling? UnknownBreakpointHandling { get; set; }
}

internal class VisualizerFileConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
List<string> visualizerFile = new List<string>();
if (reader.TokenType == JsonToken.StartArray)
{
visualizerFile = serializer.Deserialize<List<string>>(reader);
}
else if (reader.TokenType == JsonToken.String)
{
visualizerFile.Add(reader.Value.ToString());
}
else
{
throw new Exception("Input is neither a List nor a String");
}
return visualizerFile;
}

public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

public partial class AttachOptions : BaseOptions
{
#region Public Properties for Serialization
Expand All @@ -144,7 +176,7 @@ public AttachOptions(
int processId,
string type = null,
string targetArchitecture = null,
string visualizerFile = null,
List<string> visualizerFile = null,
bool? showDisplayString = null,
string additionalSOLibSearchPath = null,
string MIMode = null,
Expand Down Expand Up @@ -408,7 +440,7 @@ public LaunchOptions(
List<SetupCommand> postRemoteConnectCommands = null,
List<SetupCommand> customLaunchSetupCommands = null,
LaunchCompleteCommand? launchCompleteCommand = null,
string visualizerFile = null,
List<string> visualizerFile = null,
bool? showDisplayString = null,
List<Environment> environment = null,
string additionalSOLibSearchPath = null,
Expand Down
26 changes: 10 additions & 16 deletions src/MICore/LaunchOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -960,19 +960,10 @@ public string AdditionalSOLibSearchPath
}
}

private string _visualizerFile;
/// <summary>
/// [Optional] Natvis file name - from install location
/// Collection of natvis files to use when evaluating
/// </summary>
public string VisualizerFile
{
get { return _visualizerFile; }
set
{
VerifyCanModifyProperty(nameof(VisualizerFile));
_visualizerFile = value;
}
}
public List<string> VisualizerFiles { get; } = new List<string>();

private bool _waitDynamicLibLoad = true;
/// <summary>
Expand Down Expand Up @@ -1579,9 +1570,9 @@ private void Merge(AttachOptionsForConnection suppOptions)
{
DebugChildProcesses = suppOptions.DebugChildProcesses;
}
if (string.IsNullOrWhiteSpace(VisualizerFile))
if (!this.VisualizerFiles.Contains(suppOptions.VisualizerFile))
{
VisualizerFile = suppOptions.VisualizerFile;
this.VisualizerFiles.Add(suppOptions.VisualizerFile);
}
if (suppOptions.ShowDisplayStringSpecified)
{
Expand Down Expand Up @@ -1771,7 +1762,10 @@ protected void InitializeCommonOptions(Json.LaunchOptions.BaseOptions options)
this.TargetArchitecture = ConvertTargetArchitectureAttribute(options.TargetArchitecture);
}

this.VisualizerFile = options.VisualizerFile;
if (options.VisualizerFile != null && options.VisualizerFile.Count > 0)
{
this.VisualizerFiles.AddRange(options.VisualizerFile);
}
this.ShowDisplayString = options.ShowDisplayString.GetValueOrDefault(false);

this.AdditionalSOLibSearchPath = String.IsNullOrEmpty(this.AdditionalSOLibSearchPath) ?
Expand Down Expand Up @@ -1841,8 +1835,8 @@ protected void InitializeCommonOptions(Xml.LaunchOptions.BaseLaunchOptions sourc
if (string.IsNullOrEmpty(this.WorkingDirectory))
this.WorkingDirectory = source.WorkingDirectory;

if (string.IsNullOrEmpty(this.VisualizerFile))
this.VisualizerFile = source.VisualizerFile;
if (!string.IsNullOrEmpty(source.VisualizerFile))
this.VisualizerFiles.Add(source.VisualizerFile);

this.ShowDisplayString = source.ShowDisplayString;
this.WaitDynamicLibLoad = source.WaitDynamicLibLoad;
Expand Down
2 changes: 1 addition & 1 deletion src/MICoreUnitTests/BasicLaunchOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ internal void VerifyCoreApisPresent()
launchOptions.WorkingDirectory = "/home/user";
launchOptions.DebuggerMIMode = MIMode.Gdb;
launchOptions.WaitDynamicLibLoad = false;
launchOptions.VisualizerFile = @"c:\myproject\file.natvis";
launchOptions.VisualizerFiles.Add(@"c:\myproject\file.natvis");
launchOptions.SourceMap = new ReadOnlyCollection<SourceMapEntry>(new List<SourceMapEntry>());
launchOptions.Environment = new ReadOnlyCollection<EnvironmentEntry>(new List<EnvironmentEntry>());
Microsoft.DebugEngineHost.HostConfigurationStore configStore = null;
Expand Down
2 changes: 1 addition & 1 deletion src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ private async Task EnsureModulesLoaded()
public async Task Initialize(HostWaitLoop waitLoop, CancellationToken token)
{
bool success = false;
Natvis.Initialize(_launchOptions.VisualizerFile);
Natvis.Initialize(_launchOptions.VisualizerFiles);
int total = 1;

await this.WaitForConsoleDebuggerInitialize(token);
Expand Down
83 changes: 48 additions & 35 deletions src/MIDebugEngine/Natvis.Impl/Natvis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ internal Natvis(DebuggedProcess process, bool showDisplayString, HostConfigurati
_configStore = configStore;
}

public void Initialize(string fileName)
private void InitializeNatvisServices()
{
try
{
Expand All @@ -271,52 +271,65 @@ public void Initialize(string fileName)
{
// failed to find the VS Service
}
}

if (!string.IsNullOrEmpty(fileName))
/*
* Handle multiple Natvis files
*/
public void Initialize(List<string> fileNames)
{
InitializeNatvisServices();
if (fileNames != null && fileNames.Count > 0)
{
if (!Path.IsPathRooted(fileName))
foreach (var fileName in fileNames)
{
string globalVisualizersDirectory = _process.Engine.GetMetric("GlobalVisualizersDirectory") as string;
string globalNatVisPath = null;
if (!string.IsNullOrEmpty(globalVisualizersDirectory) && !string.IsNullOrEmpty(fileName))
{
globalNatVisPath = Path.Combine(globalVisualizersDirectory, fileName);
}

// For local launch, try and load natvis next to the target exe if it exists and if
// the exe is rooted. If the file doesn't exist, and also doesn't exist in the global folder fail.
if (_process.LaunchOptions is LocalLaunchOptions)
if (!string.IsNullOrEmpty(fileName))
{
string exePath = (_process.LaunchOptions as LocalLaunchOptions).ExePath;
if (Path.IsPathRooted(exePath))
if (!Path.IsPathRooted(fileName))
{
string localNatvisPath = Path.Combine(Path.GetDirectoryName(exePath), fileName);
string globalVisualizersDirectory = _process.Engine.GetMetric("GlobalVisualizersDirectory") as string;
string globalNatVisPath = null;
if (!string.IsNullOrEmpty(globalVisualizersDirectory) && !string.IsNullOrEmpty(fileName))
{
globalNatVisPath = Path.Combine(globalVisualizersDirectory, fileName);
}

if (File.Exists(localNatvisPath))
// For local launch, try and load natvis next to the target exe if it exists and if
// the exe is rooted. If the file doesn't exist, and also doesn't exist in the global folder fail.
if (_process.LaunchOptions is LocalLaunchOptions)
{
LoadFile(localNatvisPath);
return;
string exePath = (_process.LaunchOptions as LocalLaunchOptions).ExePath;
if (Path.IsPathRooted(exePath))
{
string localNatvisPath = Path.Combine(Path.GetDirectoryName(exePath), fileName);

if (File.Exists(localNatvisPath))
{
LoadFile(localNatvisPath);
return;
}
else if (globalNatVisPath == null || !File.Exists(globalNatVisPath))
{
// Neither local or global path exists, report an error.
_process.WriteOutput(String.Format(CultureInfo.CurrentCulture, ResourceStrings.FileNotFound, localNatvisPath));
return;
}
}
}
else if (globalNatVisPath == null || !File.Exists(globalNatVisPath))

// Local wasn't supported or the file didn't exist. Try and load from globally registered visualizer directory if local didn't work
// or wasn't supported by the launch options
if (!string.IsNullOrEmpty(globalNatVisPath))
{
// Neither local or global path exists, report an error.
_process.WriteOutput(String.Format(CultureInfo.CurrentCulture, ResourceStrings.FileNotFound, localNatvisPath));
return;
LoadFile(globalNatVisPath);
}
}
else
{
// Full path to the natvis file.. Just try the load
LoadFile(fileName);
}
}

// Local wasn't supported or the file didn't exist. Try and load from globally registered visualizer directory if local didn't work
// or wasn't supported by the launch options
if (!string.IsNullOrEmpty(globalNatVisPath))
{
LoadFile(globalNatVisPath);
}
}
else
{
// Full path to the natvis file.. Just try the load
LoadFile(fileName);
}
}
}
Expand Down
5 changes: 1 addition & 4 deletions test/CppTests/CppTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,7 @@
</ItemGroup>

<Target Name="Copy Config and Debuggees" BeforeTargets="DropFiles">
<Copy
SourceFiles="@(ContentFiles)"
DestinationFolder="$(DropDir)\contentFiles\%(RecursiveDir)"
/>
<Copy SourceFiles="@(ContentFiles)" DestinationFolder="$(DropDir)\contentFiles\%(RecursiveDir)" />
</Target>

<ItemGroup>
Expand Down
13 changes: 10 additions & 3 deletions test/CppTests/OpenDebug/CrossPlatCpp/LaunchCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using DebuggerTesting.OpenDebug.Commands;
using DebuggerTesting.OpenDebug.CrossPlatCpp;
using DebuggerTesting.Utilities;
using Newtonsoft.Json;

Expand Down Expand Up @@ -34,7 +35,8 @@ public sealed class CppLaunchCommandArgs : LaunchCommandArgs
public string MIMode;

[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string VisualizerFile;
public object VisualizerFile;


[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool ShowDisplayString;
Expand All @@ -50,8 +52,13 @@ public class LaunchCommand : LaunchCommand<CppLaunchCommandArgs>
/// <param name="program">The full path to the program to launch</param>
/// <param name="architecture">The architecture of the program</param>
/// <param name="args">[OPTIONAL] Args to pass to the program</param>
public LaunchCommand(IDebuggerSettings settings, string program, string visualizerFile = null, bool isAttach = false, params string[] args)
public LaunchCommand(IDebuggerSettings settings, string program, object visualizerFile = null, bool isAttach = false, params string[] args)
{
if (!(visualizerFile == null || visualizerFile is string || visualizerFile is List<string>))
{
throw new ArgumentOutOfRangeException(nameof(visualizerFile));
}

this.Timeout = TimeSpan.FromSeconds(15);

this.Args.name = CreateName(settings);
Expand All @@ -75,7 +82,7 @@ public LaunchCommand(IDebuggerSettings settings, string program, string visualiz
this.Args.targetArchitecture = settings.DebuggeeArchitecture.ToArchitectureString();
this.Args.MIMode = settings.MIMode;
this.Args.VisualizerFile = visualizerFile;
this.Args.ShowDisplayString = !string.IsNullOrEmpty(visualizerFile);
this.Args.ShowDisplayString = visualizerFile != null;
}
}

Expand Down
68 changes: 68 additions & 0 deletions test/CppTests/Tests/NatvisTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,74 @@ public void TestCommaFormatWithSquareBrackets(ITestSettings settings)
}
}

[Theory]
[DependsOnTest(nameof(CompileNatvisDebuggee))]
[RequiresTestSettings]
// Disable on macOS
// Error:
// C-style cast from 'int' to 'int [10]' is not allowed
// (int[10])*(((vec)._start))
[UnsupportedDebugger(SupportedDebugger.Lldb, SupportedArchitecture.x64 | SupportedArchitecture.x86)]
public void TestMultipleNatvisFiles(ITestSettings settings)
{
this.TestPurpose("This test checks if multiple Natvis files can be used.");
this.WriteSettings(settings);

IDebuggee debuggee = Debuggee.Open(this, settings.CompilerSettings, NatvisName, DebuggeeMonikers.Natvis.Default);

using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings))
{
this.Comment("Configure launch");
List<string> visFile = new List<string>();
visFile.Add(Path.Join(debuggee.SourceRoot, "visualizer_files", "Simple1.natvis"));
visFile.Add(Path.Join(debuggee.SourceRoot, "visualizer_files", "Simple2.natvis"));

LaunchCommand launch = new LaunchCommand(settings.DebuggerSettings, debuggee.OutputPath, visFile, false);
runner.RunCommand(launch);

this.Comment("Set Breakpoint");
SourceBreakpoints writerBreakpoints = debuggee.Breakpoints(NatvisSourceName, ReturnSourceLine);
runner.SetBreakpoints(writerBreakpoints);

runner.Expects.StoppedEvent(StoppedReason.Breakpoint).AfterConfigurationDone();

using (IThreadInspector threadInspector = runner.GetThreadInspector())
{
IFrameInspector currentFrame = threadInspector.Stack.First();

// Simple1.natvis
this.Comment("Verifying ArrayItems natvis");
var ll = currentFrame.GetVariable("vec");
Assert.Equal("{ size=2000 }", ll.Value);

// Custom Item in natvis
Assert.Equal("2000", ll.GetVariable("Size").Value);

// Index element for ArrayItems
Assert.Equal("20", ll.GetVariable("[5]").Value);
Assert.Equal("51", ll.GetVariable("[More...]").GetVariable("[51]").Value);

// Simple2.natvis
this.Comment("Verifying TreeItems natvis");
var map = currentFrame.GetVariable("map");

// Custom Item in natvis
Assert.Equal("6", map.GetVariable("Count").Value);

// Index element for TreeItems
// Visualized map will show the BST in a flat ordered list.
// Values are inserted as [0, -100, 15, -35, 4, -72]
// Expected visualize list to be [-100, -72, -35, 0, 4, 15]
Assert.Equal("-100", map.GetVariable("[0]").Value);
Assert.Equal("0", map.GetVariable("[3]").Value);
Assert.Equal("15", map.GetVariable("[5]").Value);
}

runner.Expects.ExitedEvent(exitCode: 0).TerminatedEvent().AfterContinue();
runner.DisconnectAndVerify();
}
}

#endregion
}
}
Loading