Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm][debugger] Implement support to symbolOptions from dap. #79284

Merged
merged 26 commits into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fcf126b
Draft to implement support to symbolOptions from dap.
thaystg Dec 6, 2022
2d191e2
Merge remote-tracking branch 'upstream/main' into thays_support_symbo…
thaystg Dec 6, 2022
2850e02
removing debugger.launch
thaystg Dec 6, 2022
e00341e
Adding tests and fix compilation error
thaystg Dec 6, 2022
d9edba7
Adding test case.
thaystg Dec 6, 2022
524531f
Fix test cases, and implement support to PDBChecksum used on nuget.or…
thaystg Dec 8, 2022
fa2899a
Merge remote-tracking branch 'upstream/main' into thays_support_symbo…
thaystg Dec 8, 2022
2c3dddd
merge
thaystg Dec 12, 2022
8218e60
Fixing tests.
thaystg Dec 19, 2022
5e62ffe
Tests are timing out.
thaystg Dec 19, 2022
5374f0a
Apply suggestions from code review
thaystg Jan 6, 2023
4e6f443
adressing @radical comments.
thaystg Jan 6, 2023
21ff307
Addressing @radical comment.
thaystg Jan 6, 2023
a352202
Addressing @radical comments.
thaystg Jan 10, 2023
c954ad3
Apply suggestions from code review
thaystg Jan 17, 2023
9c384a4
Addressing @radical comments.
thaystg Jan 17, 2023
5648b8f
Addressing @radical comments
thaystg Jan 18, 2023
3366e20
Merge branch 'main' into thays_support_symbolOptions
thaystg Jan 18, 2023
8f00d90
use MicrosoftCodeAnalysisCSharpVersion for scripting package.
thaystg Jan 18, 2023
af713af
Adding more tests as asked by @radical
thaystg Jan 19, 2023
04e3372
Merge branch 'main' into thays_support_symbolOptions
thaystg Jan 19, 2023
ecc3730
Merge branch 'main' into thays_support_symbolOptions
radical Jan 20, 2023
9ad0aa5
[wasm] some cleanup
radical Jan 20, 2023
5ca16da
remove debug spew
radical Jan 21, 2023
b48ba60
Merge remote-tracking branch 'origin' into thays_support_symbolOptions
thaystg Jan 23, 2023
8bf7b71
Addressing radical comment.
thaystg Jan 23, 2023
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
7 changes: 4 additions & 3 deletions src/mono/mono/component/debugger-engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -975,10 +975,11 @@ mono_de_ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, void *tls,
if (minfo)
loc = mono_debug_method_lookup_location (minfo, sp->il_offset);

if (!loc) {
PRINT_DEBUG_MSG (1, "[%p] No line number info for il offset %x, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), sp->il_offset);
if (!loc) { //we should not continue single stepping because the client side can have symbols loaded dynamically
PRINT_DEBUG_MSG (1, "[%p] No line number info for il offset %x, don't know if it's in the same line single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), sp->il_offset);
req->last_method = method;
hit = FALSE;
req->last_line = -1;
return hit;
radical marked this conversation as resolved.
Show resolved Hide resolved
} else if (loc && method == req->last_method && loc->row == req->last_line) {
int nframes;
rt_callbacks.ss_calculate_framecount (tls, ctx, FALSE, NULL, &nframes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Newtonsoft.Json.dll" />
<_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.Scripting.dll" />
<_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.Scripting.dll" />
<_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.SymbolStore.dll" />
<_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.FileFormats.dll" />

<PackageFile Include="@(_browserDebugHostFiles)" TargetPath="tools\$(NetCoreAppCurrent)\" />
</ItemGroup>
Expand Down
3 changes: 1 addition & 2 deletions src/mono/wasm/debugger/BrowserDebugHost/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,7 @@ async Task ConnectProxy(HttpContext context)
try
{
var loggerFactory = context.RequestServices.GetService<ILoggerFactory>();
context.Request.Query.TryGetValue("urlSymbolServer", out StringValues urlSymbolServerList);
var proxy = new DebuggerProxy(loggerFactory, urlSymbolServerList.ToList(), runtimeId, options: options);
var proxy = new DebuggerProxy(loggerFactory, runtimeId, options: options);

System.Net.WebSockets.WebSocket ideSocket = await context.WebSockets.AcceptWebSocketAsync();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.7" />
<PackageReference Include="Microsoft.SymbolStore" Version="1.0.357801" />
radical marked this conversation as resolved.
Show resolved Hide resolved
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0" />
radical marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>
Expand Down
402 changes: 265 additions & 137 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ public class DebuggerProxy : DebuggerProxyBase
{
internal MonoProxy MonoProxy { get; }

public DebuggerProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList, int runtimeId = 0, string loggerId = "", ProxyOptions options = null)
public DebuggerProxy(ILoggerFactory loggerFactory, int runtimeId = 0, string loggerId = "", ProxyOptions options = null)
{
string suffix = loggerId.Length > 0 ? $"-{loggerId}" : string.Empty;
MonoProxy = new MonoProxy(loggerFactory.CreateLogger($"DevToolsProxy{suffix}"), urlSymbolServerList, runtimeId, loggerId, options);
MonoProxy = new MonoProxy(loggerFactory.CreateLogger($"DevToolsProxy{suffix}"), runtimeId, loggerId, options);
}

public Task Run(Uri browserUri, WebSocket ideSocket, CancellationTokenSource cts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.WebAssembly.Diagnostics;

internal sealed class FirefoxMonoProxy : MonoProxy
{
public FirefoxMonoProxy(ILogger logger, string loggerId = null, ProxyOptions options = null) : base(logger, null, loggerId: loggerId, options: options)
public FirefoxMonoProxy(ILogger logger, string loggerId = null, ProxyOptions options = null) : base(logger, loggerId: loggerId, options: options)
{
}

Expand Down
108 changes: 46 additions & 62 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ namespace Microsoft.WebAssembly.Diagnostics
{
internal class MonoProxy : DevToolsProxy
{
private IList<string> urlSymbolServerList;
internal List<string> UrlSymbolServerList { get; private set; }
internal string CachePathSymbolServer { get; private set; }
private HashSet<SessionId> sessions = new HashSet<SessionId>();
private static readonly string[] s_executionContextIndependentCDPCommandNames = { "DotnetDebugger.setDebuggerProperty" };
protected Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext>();
Expand All @@ -32,9 +33,9 @@ internal class MonoProxy : DevToolsProxy

protected readonly ProxyOptions _options;

public MonoProxy(ILogger logger, IList<string> urlSymbolServerList, int runtimeId = 0, string loggerId = "", ProxyOptions options = null) : base(logger, loggerId)
public MonoProxy(ILogger logger, int runtimeId = 0, string loggerId = "", ProxyOptions options = null) : base(logger, loggerId)
{
this.urlSymbolServerList = urlSymbolServerList ?? new List<string>();
UrlSymbolServerList = new List<string>();
RuntimeId = runtimeId;
_options = options;
_defaultPauseOnExceptions = PauseOnExceptionsKind.Unset;
Expand Down Expand Up @@ -76,10 +77,10 @@ internal void SendLog(SessionId sessionId, string message, CancellationToken tok
{
type,
args = new JArray(JObject.FromObject(new
{
type = "string",
value = message,
})),
{
type = "string",
value = message,
})),
executionContextId = context.Id
});
SendEvent(sessionId, "Runtime.consoleAPICalled", o, token);
Expand Down Expand Up @@ -164,7 +165,7 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, JObject par
bool? is_default = aux_data["isDefault"]?.Value<bool>();
if (is_default == true)
{
await OnDefaultContext(sessionId, new ExecutionContext(new MonoSDBHelper (this, logger, sessionId), id, aux_data, _defaultPauseOnExceptions), token);
await OnDefaultContext(sessionId, new ExecutionContext(new MonoSDBHelper(this, logger, sessionId), id, aux_data, _defaultPauseOnExceptions), token);
}
}
return true;
Expand Down Expand Up @@ -261,7 +262,7 @@ protected override async Task<bool> AcceptCommand(MessageId id, JObject parms, C

if (!contexts.TryGetValue(id, out ExecutionContext context) && !s_executionContextIndependentCDPCommandNames.Contains(method))
{
if (method == "Debugger.setPauseOnExceptions")
if (method == "Debugger.setPauseOnExceptions")
{
string state = args["state"].Value<string>();
var pauseOnException = GetPauseOnExceptionsStatusFromString(state);
Expand Down Expand Up @@ -511,11 +512,11 @@ protected override async Task<bool> AcceptCommand(MessageId id, JObject parms, C
switch (property.Key)
{
case "JustMyCodeStepping":
SetJustMyCode(id, (bool) property.Value, token);
break;
await SetJustMyCode(id, (bool)property.Value, context, token);
break;
default:
logger.LogDebug($"DotnetDebugger.setDebuggerProperty failed for {property.Key} with value {property.Value}");
break;
break;
}
}
return true;
Expand All @@ -540,11 +541,17 @@ protected override async Task<bool> AcceptCommand(MessageId id, JObject parms, C
SendResponse(id, Result.Err("ApplyUpdate failed."), token);
return true;
}
case "DotnetDebugger.addSymbolServerUrl":
case "DotnetDebugger.setSymbolOptions":
radical marked this conversation as resolved.
Show resolved Hide resolved
{
string url = args["url"]?.Value<string>();
if (!string.IsNullOrEmpty(url) && !urlSymbolServerList.Contains(url))
urlSymbolServerList.Add(url);
SendResponse(id, Result.OkFromObject(new { }), token);
CachePathSymbolServer = args["symbolOptions"]?["cachePath"]?.Value<string>();
var urls = args["symbolOptions"]?["searchPaths"]?.Value<JArray>();
if (urls == null)
return true;
UrlSymbolServerList.Clear();
UrlSymbolServerList.AddRange(urls.Values<string>());
if (!JustMyCode)
return await LoadSymbolsFromSymbolServer(id, context, token);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the user had !JustMyCode, and we loaded symbols from the symbol server. Then the user sets JustMyCode=true, and we still have the symbols from the symbol server loaded. How does that affect the UX?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method ShouldSkipMethod together with the new attribute HasNonUser code and the JustMyCode setting will know that even having the symbols, as it's not a user code and the justmycode is enabled, the method should be skipped while stepping.

return true;
}
case "DotnetDebugger.getMethodLocation":
Expand All @@ -571,6 +578,21 @@ protected override async Task<bool> AcceptCommand(MessageId id, JObject parms, C
return method.StartsWith("DotnetDebugger.", StringComparison.OrdinalIgnoreCase);
}

private async Task<bool> LoadSymbolsFromSymbolServer(MessageId id, ExecutionContext context, CancellationToken token)
{
if (!await IsRuntimeAlreadyReadyAlready(id, token))
return true;
DebugStore store = await RuntimeReady(id, token);
store.CreateSymbolServer();
var asmList = await store.LoadPDBFromSymbolServer(token);
foreach (var asm in asmList)
{
foreach (var source in asm.Sources)
await OnSourceFileAdded(id, source, context, token);
}
return true;
}

private async Task<bool> ApplyUpdates(MessageId id, JObject args, CancellationToken token)
{
var context = GetContext(id);
Expand All @@ -583,8 +605,13 @@ private async Task<bool> ApplyUpdates(MessageId id, JObject args, CancellationTo
return applyUpdates;
}

private void SetJustMyCode(MessageId id, bool isEnabled, CancellationToken token)
private async Task SetJustMyCode(MessageId id, bool isEnabled, ExecutionContext context, CancellationToken token)
{
if (JustMyCode != isEnabled && isEnabled == false)
{
JustMyCode = isEnabled;
await LoadSymbolsFromSymbolServer(id, context, token);
}
JustMyCode = isEnabled;
SendResponse(id, Result.OkFromObject(new { justMyCodeEnabled = JustMyCode }), token);
}
Expand Down Expand Up @@ -895,7 +922,7 @@ protected virtual async Task<bool> ShouldSkipMethod(SessionId sessionId, Executi
if (shouldReturn)
return true;

if (j == 0 && method?.Info.DebuggerAttrInfo.DoAttributesAffectCallStack(JustMyCode) == true)
if (j == 0 && method?.Info?.DebuggerAttrInfo?.DoAttributesAffectCallStack(JustMyCode) == true)
{
if (method.Info.DebuggerAttrInfo.ShouldStepOut(event_kind))
{
Expand Down Expand Up @@ -1122,49 +1149,6 @@ internal async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObje
return false;
}

internal async Task<MethodInfo> LoadSymbolsOnDemand(AssemblyInfo asm, int method_token, SessionId sessionId, CancellationToken token)
{
ExecutionContext context = GetContext(sessionId);
if (urlSymbolServerList.Count == 0)
return null;
if (asm.TriedToLoadSymbolsOnDemand || !asm.CodeViewInformationAvailable)
return null;
asm.TriedToLoadSymbolsOnDemand = true;
var pdbName = Path.GetFileName(asm.PdbName);

foreach (string urlSymbolServer in urlSymbolServerList)
{
string downloadURL = $"{urlSymbolServer}/{pdbName}/{asm.PdbGuid.ToString("N").ToUpperInvariant() + asm.PdbAge}/{pdbName}";

try
{
using HttpResponseMessage response = await HttpClient.GetAsync(downloadURL, token);
if (!response.IsSuccessStatusCode)
{
Log("info", $"Unable to download symbols on demand url:{downloadURL} assembly: {asm.Name}");
continue;
}

using Stream streamToReadFrom = await response.Content.ReadAsStreamAsync(token);
asm.UpdatePdbInformation(streamToReadFrom);
foreach (SourceFile source in asm.Sources)
{
var scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData));
await SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token);
}
return asm.GetMethodByToken(method_token);
}
catch (Exception e)
{
Log("info", $"Unable to load symbols on demand exception: {e} url:{downloadURL} assembly: {asm.Name}");
}
break;
}

Log("info", $"Unable to load symbols on demand assembly: {asm.Name}");
return null;
}

protected void OnDefaultContextUpdate(SessionId sessionId, ExecutionContext context)
{
if (UpdateContext(sessionId, context, out ExecutionContext previousContext))
Expand Down Expand Up @@ -1565,7 +1549,7 @@ private static IEnumerable<IGrouping<SourceId, SourceLocation>> GetBPReqLocation
{
var comparer = new SourceLocation.LocationComparer();
// if column is specified the frontend wants the exact matches
// and will clear the bp if it isn't close enoug
// and will clear the bp if it isn't close enough
var bpLocations = store.FindBreakpointLocations(req, ifNoneFoundThenFindNext);
IEnumerable<IGrouping<SourceId, SourceLocation>> locations = bpLocations.Distinct(comparer)
.OrderBy(l => l.Column)
Expand Down
13 changes: 0 additions & 13 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -890,19 +890,6 @@ public async Task<MethodInfoWithDebugInformation> GetMethodInfo(int methodId, Ca

var method = asm.GetMethodByToken(methodToken);

if (method == null && !asm.HasSymbols)
{
try
{
method = await proxy.LoadSymbolsOnDemand(asm, methodToken, sessionId, token);
}
catch (Exception e)
{
logger.LogDebug($"Unable to find method token: {methodToken} assembly name: {asm.Name} exception: {e}");
return null;
}
}

string methodName = await GetMethodName(methodId, token);
//get information from runtime
method ??= await CreateMethodInfoFromRuntimeInformation(asm, methodId, methodName, methodToken, token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public async Task StartBrowserAndProxyAsync(HttpContext context,

_logger.LogInformation($"{messagePrefix} launching proxy for {con_str}");

_debuggerProxy = new DebuggerProxy(loggerFactory, null, loggerId: Id);
_debuggerProxy = new DebuggerProxy(loggerFactory, loggerId: Id);
TestHarnessProxy.RegisterNewProxy(Id, _debuggerProxy);
var browserUri = new Uri(con_str);
WebSocket? ideSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,14 @@ internal async Task SetJustMyCode(bool enabled)
Assert.Equal(res.Value["justMyCodeEnabled"], enabled);
}


internal async Task SetSymbolOptions(JObject param)
{
var res = await cli.SendCommand("DotnetDebugger.setSymbolOptions", param, token);
Assert.True(res.IsOk);
}

thaystg marked this conversation as resolved.
Show resolved Hide resolved

internal async Task CheckEvaluateFail(string id, params (string expression, string message)[] args)
{
foreach (var arg in args)
Expand Down
33 changes: 32 additions & 1 deletion src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ await EvaluateAndCheck(
"dotnet://debugger-test.dll/debugger-async-test.cs", line_pause, column_pause,
$"DebuggerTests.AsyncTests.ContinueWithTests.{method_name}");
}

[ConditionalTheory(nameof(RunningOnChrome))]
[InlineData(112, 16, 114, 16, "HiddenLinesInAnAsyncBlock")]
[InlineData(130, 16, 133, 16, "HiddenLinesJustBeforeANestedAsyncBlock")]
Expand Down Expand Up @@ -1037,5 +1037,36 @@ await EvaluateAndCheck(
step_into2["callFrames"][0]["location"]["lineNumber"].Value<int>()
);
}

[ConditionalTheory(nameof(RunningOnChrome))]
[InlineData(true)]
[InlineData(false)]
public async Task SteppingIntoLibrarySymbolsLoadedFromSymbolServer(bool justMyCode)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also:

  • a test that first hits the bp, and steps with no symbol server set. Then in the same test, sets the symbol server, and steps again
  • A separate test that (a) sets a symbol server; (b) steps; (c) adds another symbol server; (d) steps again
  • A separate test that (a) adds multiple symbol servers; (b) steps; (c) removes one of the symbol servers; (d) steps again

{
string cachePath = System.IO.Path.GetTempPath();
var searchPaths = new JArray();
searchPaths.Add("https://symbols.nuget.org/download/symbols");
var waitForScript = WaitForScriptParsedEventsAsync(new string [] { "JArray.cs" });
var symbolOptions = JObject.FromObject(new { symbolOptions = JObject.FromObject(new { cachePath, searchPaths })});
await SetJustMyCode(justMyCode);
await SetSymbolOptions(symbolOptions);

await EvaluateAndCheck(
"window.setTimeout(function() { invoke_static_method ('[debugger-test] TestLoadSymbols:Run'); }, 1);",
"dotnet://debugger-test.dll/debugger-test.cs", 1516, 8,
"TestLoadSymbols.Run"
);
if (!justMyCode)
await waitForScript;
await StepAndCheck(StepKind.Into, justMyCode ? "dotnet://debugger-test.dll/debugger-test.cs" : "dotnet://Newtonsoft.Json.dll/JArray.cs", justMyCode ? 1519 : 350, justMyCode ? 8 : 12, justMyCode ? "TestLoadSymbols.Run" : "Newtonsoft.Json.Linq.JArray.Add",
locals_fn: async (locals) =>
{
if (!justMyCode)
await CheckObject(locals, "this", "Newtonsoft.Json.Linq.JArray", description: "[]");
else
await CheckObject(locals, "array", "Newtonsoft.Json.Linq.JArray", description: "[\n \"Manual text\"\n]");
}, times: 2
);
}
}
}
4 changes: 2 additions & 2 deletions src/mono/wasm/debugger/Wasm.Debugger.Tests/wasm.helix.targets
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<NeedsToRunOnBrowser>true</NeedsToRunOnBrowser>
<WorkItemPrefix>$(DebuggerHost)-</WorkItemPrefix>
<UseDotNetCliVersionFromGlobalJson>true</UseDotNetCliVersionFromGlobalJson>
<_DebuggerTestsWorkItemTimeout Condition="'$(Scenario)' == 'WasmDebuggerTests'">00:20:00</_DebuggerTestsWorkItemTimeout>
<_DebuggerTestsWorkItemTimeout Condition="'$(Scenario)' == 'WasmDebuggerTests' and '$(BrowserHost)' == 'windows'">00:30:00</_DebuggerTestsWorkItemTimeout>
<_DebuggerTestsWorkItemTimeout Condition="'$(Scenario)' == 'WasmDebuggerTests'">00:30:00</_DebuggerTestsWorkItemTimeout>
<_DebuggerTestsWorkItemTimeout Condition="'$(Scenario)' == 'WasmDebuggerTests' and '$(BrowserHost)' == 'windows'">00:40:00</_DebuggerTestsWorkItemTimeout>

<HelixExtensionTargets>$(HelixExtensionTargets);_AddWorkItemsForWasmDebuggerTests</HelixExtensionTargets>
</PropertyGroup>
Expand Down
Loading