Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
66 changes: 42 additions & 24 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Microsoft.CodeAnalysis.Debugging;
using System.IO.Compression;
using System.Reflection;
using System.Collections.Immutable;

namespace Microsoft.WebAssembly.Diagnostics
{
Expand Down Expand Up @@ -524,39 +525,57 @@ internal class AssemblyInfo
private Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
private readonly List<SourceFile> sources = new List<SourceFile>();
internal string Url { get; }
//The caller must keep the PEReader alive and undisposed throughout the lifetime of the metadata reader
internal PEReader peReader;
internal MetadataReader asmMetadataReader { get; }
internal MetadataReader pdbMetadataReader { get; set; }
internal List<MemoryStream> enCMemoryStream = new List<MemoryStream>();
internal List<MetadataReader> enCMetadataReader = new List<MetadataReader>();
internal PEReader peReader;
internal MemoryStream asmStream;
internal MemoryStream pdbStream;
public int DebugId { get; set; }

internal int PdbAge { get; }
internal System.Guid PdbGuid { get; }
internal string PdbName { get; }
internal bool PdbInformationAvailable { get; }
public bool TriedToLoadSymbolsOnDemand { get; set; }

public unsafe AssemblyInfo(string url, byte[] assembly, byte[] pdb)
public unsafe AssemblyInfo(MonoProxy monoProxy, SessionId sessionId, string url, byte[] assembly, byte[] pdb, CancellationToken token)
{
this.id = Interlocked.Increment(ref next_id);
asmStream = new MemoryStream(assembly);
using var asmStream = new MemoryStream(assembly);
peReader = new PEReader(asmStream);
var entries = peReader.ReadDebugDirectory();
if (entries.Length > 0)
{
var codeView = entries[0];
CodeViewDebugDirectoryData codeViewData = peReader.ReadCodeViewDebugDirectoryData(codeView);
PdbAge = codeViewData.Age;
PdbGuid = codeViewData.Guid;
PdbName = codeViewData.Path;
PdbInformationAvailable = true;
}
asmMetadataReader = PEReaderExtensions.GetMetadataReader(peReader);
var asmDef = asmMetadataReader.GetAssemblyDefinition();
Name = asmDef.GetAssemblyName().Name + ".dll";
if (pdb != null)
{
pdbStream = new MemoryStream(pdb);
pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
var pdbStream = new MemoryStream(pdb);
try
{
// MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream
pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
}
catch (BadImageFormatException)
{
monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {Name} (use DebugType=Portable/Embedded)", token);
}
}
else
{
var entries = peReader.ReadDebugDirectory();
var embeddedPdbEntry = entries.FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb);
if (embeddedPdbEntry.DataSize != 0)
{
pdbMetadataReader = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader();
}
}
Name = asmMetadataReader.GetAssemblyDefinition().GetAssemblyName().Name + ".dll";
AssemblyNameUnqualified = asmMetadataReader.GetAssemblyDefinition().GetAssemblyName().Name + ".dll";
Populate();
}

Expand All @@ -566,8 +585,6 @@ public bool EnC(byte[] meta, byte[] pdb)
MetadataReader asmMetadataReader = MetadataReaderProvider.FromMetadataStream(asmStream).GetMetadataReader();
var pdbStream = new MemoryStream(pdb);
MetadataReader pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
enCMemoryStream.Add(asmStream);
enCMemoryStream.Add(pdbStream);
enCMetadataReader.Add(asmMetadataReader);
enCMetadataReader.Add(pdbMetadataReader);
PopulateEnC(asmMetadataReader, pdbMetadataReader);
Expand Down Expand Up @@ -728,7 +745,7 @@ public TypeInfo GetTypeByName(string name)

internal void UpdatePdbInformation(Stream streamToReadFrom)
{
pdbStream = new MemoryStream();
var pdbStream = new MemoryStream();
streamToReadFrom.CopyTo(pdbStream);
pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
}
Expand Down Expand Up @@ -907,14 +924,16 @@ internal class DebugStore
internal List<AssemblyInfo> assemblies = new List<AssemblyInfo>();
private readonly HttpClient client;
private readonly ILogger logger;
private readonly MonoProxy monoProxy;

public DebugStore(ILogger logger, HttpClient client)
public DebugStore(MonoProxy monoProxy, ILogger logger, HttpClient client)
{
this.client = client;
this.logger = logger;
this.monoProxy = monoProxy;
}

public DebugStore(ILogger logger) : this(logger, new HttpClient())
public DebugStore(MonoProxy monoProxy, ILogger logger) : this(monoProxy, logger, new HttpClient())
{ }

private class DebugItem
Expand All @@ -933,12 +952,12 @@ public IEnumerable<MethodInfo> EnC(AssemblyInfo asm, byte[] meta_data, byte[] pd
}
}

public IEnumerable<SourceFile> Add(string name, byte[] assembly_data, byte[] pdb_data)
public IEnumerable<SourceFile> Add(SessionId id, string name, byte[] assembly_data, byte[] pdb_data, CancellationToken token)
{
AssemblyInfo assembly = null;
try
{
assembly = new AssemblyInfo(name, assembly_data, pdb_data);
assembly = new AssemblyInfo(monoProxy, id, name, assembly_data, pdb_data, token);
}
catch (Exception e)
{
Expand All @@ -949,7 +968,7 @@ public IEnumerable<SourceFile> Add(string name, byte[] assembly_data, byte[] pdb
if (assembly == null)
yield break;

if (GetAssemblyByUnqualifiedName(assembly.AssemblyNameUnqualified) != null)
if (GetAssemblyByName(assembly.Name) != null)
{
logger.LogDebug($"Skipping adding {assembly.Name} into the debug store, as it already exists");
yield break;
Expand All @@ -962,7 +981,7 @@ public IEnumerable<SourceFile> Add(string name, byte[] assembly_data, byte[] pdb
}
}

public async IAsyncEnumerable<SourceFile> Load(string[] loaded_files, [EnumeratorCancellation] CancellationToken token)
public async IAsyncEnumerable<SourceFile> Load(SessionId id, string[] loaded_files, [EnumeratorCancellation] CancellationToken token)
{
var asm_files = new List<string>();
var pdb_files = new List<string>();
Expand Down Expand Up @@ -1001,7 +1020,7 @@ public async IAsyncEnumerable<SourceFile> Load(string[] loaded_files, [Enumerato
try
{
byte[][] bytes = await step.Data.ConfigureAwait(false);
assembly = new AssemblyInfo(step.Url, bytes[0], bytes[1]);
assembly = new AssemblyInfo(monoProxy, id, step.Url, bytes[0], bytes[1], token);
}
catch (Exception e)
{
Expand All @@ -1010,7 +1029,7 @@ public async IAsyncEnumerable<SourceFile> Load(string[] loaded_files, [Enumerato
if (assembly == null)
continue;

if (GetAssemblyByUnqualifiedName(assembly.AssemblyNameUnqualified) != null)
if (GetAssemblyByName(assembly.Name) != null)
{
logger.LogDebug($"Skipping loading {assembly.Name} into the debug store, as it already exists");
continue;
Expand All @@ -1028,7 +1047,6 @@ public async IAsyncEnumerable<SourceFile> Load(string[] loaded_files, [Enumerato

public AssemblyInfo GetAssemblyByName(string name) => assemblies.FirstOrDefault(a => a.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));

public AssemblyInfo GetAssemblyByUnqualifiedName(string name) => assemblies.FirstOrDefault(a => a.AssemblyNameUnqualified.Equals(name, StringComparison.InvariantCultureIgnoreCase));

/*
V8 uses zero based indexing for both line and column.
Expand Down
49 changes: 35 additions & 14 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,34 @@ private bool UpdateContext(SessionId sessionId, ExecutionContext executionContex

internal Task<Result> SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token);

internal void SendLog(SessionId sessionId, string message, CancellationToken token)
{
if (!contexts.TryGetValue(sessionId, out ExecutionContext context))
return;
/*var o = JObject.FromObject(new
{
entry = JObject.FromObject(new
{
source = "recommendation",
level = "warning",
text = message
})
});
SendEvent(id, "Log.enabled", null, token);
SendEvent(id, "Log.entryAdded", o, token);*/
var o = JObject.FromObject(new
{
type = "warning",
args = new JArray(JObject.FromObject(new
{
type = "string",
value = message,
})),
executionContextId = context.Id
});
SendEvent(sessionId, "Runtime.consoleAPICalled", o, token);
}

protected override async Task<bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
{
switch (method)
Expand Down Expand Up @@ -936,21 +964,14 @@ internal async Task<MethodInfo> LoadSymbolsOnDemand(AssemblyInfo asm, int method
ExecutionContext context = GetContext(sessionId);
if (urlSymbolServerList.Count == 0)
return null;
if (asm.TriedToLoadSymbolsOnDemand)
if (asm.TriedToLoadSymbolsOnDemand || !asm.PdbInformationAvailable)
return null;
asm.TriedToLoadSymbolsOnDemand = true;
var peReader = asm.peReader;
var entries = peReader.ReadDebugDirectory();
var codeView = entries[0];
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(codeView);
int pdbAge = codeViewData.Age;
var pdbGuid = codeViewData.Guid;
string pdbName = codeViewData.Path;
pdbName = Path.GetFileName(pdbName);
var pdbName = Path.GetFileName(asm.PdbName);

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

try
{
Expand Down Expand Up @@ -1064,7 +1085,7 @@ private async Task<bool> OnAssemblyLoadedJSEvent(SessionId sessionId, JObject ev
var store = await LoadStore(sessionId, token);
var assembly_name = eventArgs?["assembly_name"]?.Value<string>();

if (store.GetAssemblyByUnqualifiedName(assembly_name) != null)
if (store.GetAssemblyByName(assembly_name) != null)
{
Log("debug", $"Got AssemblyLoaded event for {assembly_name}, but skipping it as it has already been loaded.");
return true;
Expand All @@ -1083,7 +1104,7 @@ private async Task<bool> OnAssemblyLoadedJSEvent(SessionId sessionId, JObject ev
var pdb_data = string.IsNullOrEmpty(pdb_b64) ? null : Convert.FromBase64String(pdb_b64);

var context = GetContext(sessionId);
foreach (var source in store.Add(assembly_name, assembly_data, pdb_data))
foreach (var source in store.Add(sessionId, assembly_name, assembly_data, pdb_data, token))
{
await OnSourceFileAdded(sessionId, source, context, token);
}
Expand Down Expand Up @@ -1211,7 +1232,7 @@ internal async Task<DebugStore> LoadStore(SessionId sessionId, CancellationToken
{
ExecutionContext context = GetContext(sessionId);

if (Interlocked.CompareExchange(ref context.store, new DebugStore(logger), null) != null)
if (Interlocked.CompareExchange(ref context.store, new DebugStore(this, logger), null) != null)
return await context.Source.Task;

try
Expand All @@ -1225,7 +1246,7 @@ internal async Task<DebugStore> LoadStore(SessionId sessionId, CancellationToken
}

await
foreach (SourceFile source in context.store.Load(loaded_files, token).WithCancellation(token))
foreach (SourceFile source in context.store.Load(sessionId, loaded_files, token).WithCancellation(token))
{
await OnSourceFileAdded(sessionId, source, context, token);
}
Expand Down
29 changes: 29 additions & 0 deletions src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,35 @@ await EvaluateAndCheck(
}
);
}

[Fact]
public async Task InspectLocalsUsingClassFromLibraryUsingDebugTypeFull()
{
byte[] bytes = File.ReadAllBytes(Path.Combine(DebuggerTestAppPath, "debugger-test-with-full-debug-type.dll"));
string asm_base64 = Convert.ToBase64String(bytes);

string pdb_base64 = null;
bytes = File.ReadAllBytes(Path.Combine(DebuggerTestAppPath, "debugger-test-with-full-debug-type.pdb"));
pdb_base64 = Convert.ToBase64String(bytes);

var expression = $"{{ let asm_b64 = '{asm_base64}'; let pdb_b64 = '{pdb_base64}'; invoke_static_method('[debugger-test] DebugTypeFull:CallToEvaluateLocal', asm_b64, pdb_b64); }}";

await EvaluateAndCheck(
"window.setTimeout(function() {" + expression + "; }, 1);",
"dotnet://debugger-test.dll/debugger-test.cs", 860, 8,
"CallToEvaluateLocal",
wait_for_event_fn: async (pause_location) =>
{
var a_props = await GetObjectOnFrame(pause_location["callFrames"][0], "a");
await CheckProps(a_props, new
{
a = TNumber(10),
b = TNumber(20),
c = TNumber(30)
}, "a");
}
);
}
//TODO add tests covering basic stepping behavior as step in/out/over
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DebugType>full</DebugType>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace DebuggerTests
{
public class ClassToInspectWithDebugTypeFull
{
int a;
int b;
int c;
public ClassToInspectWithDebugTypeFull()
{
a = 10;
b = 20;
c = 30;
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
}
}
}
12 changes: 12 additions & 0 deletions src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -849,3 +849,15 @@ public static void RunDebuggerBreak()
VisibleMethodDebuggerBreak();
}
}

public class DebugTypeFull
{
public static void CallToEvaluateLocal(string asm_base64, string pdb_base64)
{
var asm = System.Reflection.Assembly.LoadFrom("debugger-test-with-full-debug-type.dll");
var myType = asm.GetType("DebuggerTests.ClassToInspectWithDebugTypeFull");
var myMethod = myType.GetConstructor(new Type[] { });
var a = myMethod.Invoke(new object[]{});
System.Diagnostics.Debugger.Break();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<ProjectReference Include="..\library-dependency-debugger-test2\library-dependency-debugger-test2.csproj" Private="true"/>
<ProjectReference Include="..\debugger-test-with-source-link\debugger-test-with-source-link.csproj" Private="true"/>
<ProjectReference Include="..\ApplyUpdateReferencedAssembly\ApplyUpdateReferencedAssembly.csproj" />
<ProjectReference Include="..\debugger-test-with-full-debug-type\debugger-test-with-full-debug-type.csproj" Private="true"/>
</ItemGroup>

<Target Name="PrepareForWasmBuildApp" DependsOnTargets="RebuildWasmAppBuilder;Build">
Expand All @@ -38,6 +39,7 @@
<ItemGroup>
<WasmAssembliesToBundle Include="$(OutDir)\$(TargetFileName)" />
<WasmAssembliesToBundle Include="$(OutDir)\debugger-test-with-source-link.dll" />
<WasmAssembliesToBundle Include="$(OutDir)\debugger-test-with-full-debug-type.dll" />
<WasmAssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackRidDir)native"/>
<WasmAssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackRidDir)lib\$(NetCoreAppCurrent)"/>

Expand Down