From ae99bb2e7a0a5db7c2bff129322965fd3c6e820d Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Fri, 14 Jul 2023 10:46:00 -0300 Subject: [PATCH] [wasm][debugger] Improve debugger performance based on JMC (#86982) * Improve debugger performance. * Loading assembly bytes if JMC is disabled after the debugger session is started. * Fix CI. * Load symbols on demand if JMC is enabled, we don't need to spend a lot of time loading information from 149 assemblies if we will probably not need all of them. * Impriving the performance sending only metadata and not the full assembly. * Fixing compilation error on tvos * Apply suggestions from code review Co-authored-by: Ankit Jain * Apply suggestions from code review Co-authored-by: Ankit Jain * fix changes from code review * addressing @radical comments * addressing @radical and @lewing comments --------- Co-authored-by: Ankit Jain --- src/mono/mono/component/debugger-agent.c | 208 ++++++++++++++------- src/mono/mono/component/debugger-protocol.h | 2 +- src/mono/mono/component/mini-wasm-debugger.c | 22 --- .../wasm/debugger/BrowserDebugProxy/DebugStore.cs | 159 +++++++++++----- .../wasm/debugger/BrowserDebugProxy/MonoProxy.cs | 3 +- .../debugger/BrowserDebugProxy/MonoSDBHelper.cs | 84 ++++++++- 6 files changed, 327 insertions(+), 151 deletions(-) diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index f1c6ac7..50f23db 100644 --- a/src/mono/mono/component/debugger-agent.c +++ b/src/mono/mono/component/debugger-agent.c @@ -99,6 +99,7 @@ #include #include #include +#include #ifdef HAVE_UCONTEXT_H #include @@ -6945,6 +6946,88 @@ valid_memory_address (gpointer addr, gint size) return ret; } +static MonoAssembly* +find_assembly_by_name (char* assembly_name) +{ + //we get 'foo.dll' but mono_assembly_load expects 'foo' so we strip the last dot + char *lookup_name = g_strdup (assembly_name); + for (int i = ((int)strlen (lookup_name) - 1); i >= 0; --i) { + if (lookup_name [i] == '.') { + lookup_name [i] = 0; + break; + } + } + + //resolve the assembly + MonoImageOpenStatus status; + MonoAssemblyName* aname = mono_assembly_name_new (lookup_name); + g_free (lookup_name); + if (!aname) { + PRINT_DEBUG_MSG (1, "Could not resolve assembly %s\n", assembly_name); + return NULL; + } + + MonoAssemblyByNameRequest byname_req; + mono_assembly_request_prepare_byname (&byname_req, mono_alc_get_default ()); + MonoAssembly *assembly = mono_assembly_request_byname (aname, &byname_req, &status); + if (!assembly) { + GPtrArray *assemblies = mono_alc_get_all_loaded_assemblies (); + for (guint i = 0; i < assemblies->len; ++i) { + MonoAssembly *assemblyOnALC = (MonoAssembly*)g_ptr_array_index (assemblies, i); + if (!strcmp(assemblyOnALC->aname.name, aname->name)) { + assembly = assemblyOnALC; + break; + } + } + g_ptr_array_free (assemblies, TRUE); + if (!assembly) { + PRINT_DEBUG_MSG (1, "Could not resolve assembly %s\n", assembly_name); + goto exit; + } + } +exit: + mono_assembly_name_free_internal (aname); + return assembly; +} + +static void +send_debug_information (MonoAssembly *ass, Buffer *buf) +{ + guint8 pe_guid [16]; + gint32 pe_age; + gint32 pe_timestamp; + guint8 *ppdb_data = NULL; + int ppdb_size = 0, ppdb_compressed_size = 0; + char *ppdb_path; + GArray *pdb_checksum_hash_type = g_array_new (FALSE, TRUE, sizeof (char*)); + GArray *pdb_checksum = g_array_new (FALSE, TRUE, sizeof (guint8*)); + gboolean has_debug_info = mono_get_pe_debug_info_full (ass->image, pe_guid, &pe_age, &pe_timestamp, &ppdb_data, &ppdb_size, &ppdb_compressed_size, &ppdb_path, pdb_checksum_hash_type, pdb_checksum); + if (!has_debug_info || ppdb_size > 0) + { + buffer_add_byte (buf, 0); + g_array_free (pdb_checksum_hash_type, TRUE); + g_array_free (pdb_checksum, TRUE); + return; + } + buffer_add_byte (buf, 1); + buffer_add_int (buf, pe_age); + buffer_add_byte_array (buf, pe_guid, 16); + buffer_add_string (buf, ppdb_path); + buffer_add_int (buf, pdb_checksum_hash_type->len); + for (int i = 0 ; i < pdb_checksum_hash_type->len; ++i) { + char* checksum_hash_type = g_array_index (pdb_checksum_hash_type, char*, i); + buffer_add_string (buf, checksum_hash_type); + if (!strcmp (checksum_hash_type, "SHA256")) + buffer_add_byte_array (buf, g_array_index (pdb_checksum, guint8*, i), 32); + else if (!strcmp (checksum_hash_type, "SHA384")) + buffer_add_byte_array (buf, g_array_index (pdb_checksum, guint8*, i), 48); + else if (!strcmp (checksum_hash_type, "SHA512")) + buffer_add_byte_array (buf, g_array_index (pdb_checksum, guint8*, i), 64); + } + g_array_free (pdb_checksum_hash_type, TRUE); + g_array_free (pdb_checksum, TRUE); +} + static ErrorCode vm_commands (int command, int id, guint8 *p, guint8 *end, Buffer *buf) { @@ -7303,45 +7386,9 @@ vm_commands (int command, int id, guint8 *p, guint8 *end, Buffer *buf) } case MDBGPROT_CMD_GET_ASSEMBLY_BY_NAME: { char* assembly_name = decode_string (p, &p, end); - //we get 'foo.dll' but mono_assembly_load expects 'foo' so we strip the last dot - char *lookup_name = g_strdup (assembly_name); - for (int i = ((int)strlen (lookup_name) - 1); i >= 0; --i) { - if (lookup_name [i] == '.') { - lookup_name [i] = 0; - break; - } - } - - //resolve the assembly - MonoImageOpenStatus status; - MonoAssemblyName* aname = mono_assembly_name_new (lookup_name); - if (!aname) { - PRINT_DEBUG_MSG (1, "Could not resolve assembly %s\n", assembly_name); - buffer_add_int(buf, -1); - break; - } - MonoAssemblyByNameRequest byname_req; - mono_assembly_request_prepare_byname (&byname_req, mono_alc_get_default ()); - MonoAssembly *assembly = mono_assembly_request_byname (aname, &byname_req, &status); - g_free (lookup_name); - if (!assembly) { - GPtrArray *assemblies = mono_alc_get_all_loaded_assemblies (); - for (guint i = 0; i < assemblies->len; ++i) { - MonoAssembly *assemblyOnALC = (MonoAssembly*)g_ptr_array_index (assemblies, i); - if (!strcmp(assemblyOnALC->aname.name, aname->name)) { - assembly = assemblyOnALC; - break; - } - } - g_ptr_array_free (assemblies, TRUE); - if (!assembly) { - PRINT_DEBUG_MSG (1, "Could not resolve assembly %s\n", assembly_name); - buffer_add_int(buf, -1); - mono_assembly_name_free_internal (aname); - break; - } - } - mono_assembly_name_free_internal (aname); + MonoAssembly* assembly = find_assembly_by_name (assembly_name); + if (!assembly) + buffer_add_int (buf, -1); buffer_add_assemblyid (buf, mono_get_root_domain (), assembly); break; } @@ -7418,6 +7465,55 @@ vm_commands (int command, int id, guint8 *p, guint8 *end, Buffer *buf) } break; } + case MDBGPROT_CMD_GET_ASSEMBLY_BYTES: { //only used by wasm +#ifdef HOST_WASM + char* assembly_name = m_dbgprot_decode_string (p, &p, end); + if (assembly_name == NULL) + { + m_dbgprot_buffer_add_int (buf, 0); + m_dbgprot_buffer_add_int (buf, 0); + m_dbgprot_buffer_add_int (buf, 0); + } + else + { + int ppdb_size = 0; + const unsigned char* assembly_bytes = NULL; + unsigned int assembly_size = 0; + const unsigned char* pdb_bytes = NULL; + unsigned int symfile_size = 0; + mono_bundled_resources_get_assembly_resource_symbol_values (assembly_name, &pdb_bytes, &symfile_size); + MonoAssembly* assembly = find_assembly_by_name (assembly_name); + assembly_size = assembly->image->image_info->cli_cli_header.ch_metadata.size; + assembly_bytes = (const unsigned char*) assembly->image->raw_metadata; + if (symfile_size == 0) //try to send embedded pdb data + { + guint8 pe_guid [16]; + gint32 pe_age; + gint32 pe_timestamp; + guint8 *ppdb_data = NULL; + int ppdb_compressed_size = 0; + char *ppdb_path; + mono_get_pe_debug_info_full (assembly->image, pe_guid, &pe_age, &pe_timestamp, &ppdb_data, &ppdb_size, &ppdb_compressed_size, &ppdb_path, NULL, NULL); + if (ppdb_compressed_size > 0) + { + symfile_size = ppdb_compressed_size; + pdb_bytes = ppdb_data; + } + } + m_dbgprot_buffer_init (buf, assembly_size + symfile_size + 1024); + m_dbgprot_buffer_add_byte_array (buf, (uint8_t *) assembly_bytes, assembly_size); + m_dbgprot_buffer_add_int (buf, ppdb_size); + m_dbgprot_buffer_add_byte_array (buf, (uint8_t *) pdb_bytes, symfile_size); + if (assembly) + send_debug_information (assembly, buf); + } +#else + m_dbgprot_buffer_add_int (buf, 0); + m_dbgprot_buffer_add_int (buf, 0); + m_dbgprot_buffer_add_int (buf, 0); +#endif + break; + } default: return ERR_NOT_IMPLEMENTED; } @@ -8042,39 +8138,7 @@ assembly_commands (int command, guint8 *p, guint8 *end, Buffer *buf) break; } case MDBGPROT_CMD_ASSEMBLY_GET_DEBUG_INFORMATION: { - guint8 pe_guid [16]; - gint32 pe_age; - gint32 pe_timestamp; - guint8 *ppdb_data = NULL; - int ppdb_size = 0, ppdb_compressed_size = 0; - char *ppdb_path; - GArray *pdb_checksum_hash_type = g_array_new (FALSE, TRUE, sizeof (char*)); - GArray *pdb_checksum = g_array_new (FALSE, TRUE, sizeof (guint8*)); - gboolean has_debug_info = mono_get_pe_debug_info_full (ass->image, pe_guid, &pe_age, &pe_timestamp, &ppdb_data, &ppdb_size, &ppdb_compressed_size, &ppdb_path, pdb_checksum_hash_type, pdb_checksum); - if (!has_debug_info || ppdb_size > 0) - { - buffer_add_byte (buf, 0); - g_array_free (pdb_checksum_hash_type, TRUE); - g_array_free (pdb_checksum, TRUE); - return ERR_NONE; - } - buffer_add_byte (buf, 1); - buffer_add_int (buf, pe_age); - buffer_add_byte_array (buf, pe_guid, 16); - buffer_add_string (buf, ppdb_path); - buffer_add_int (buf, pdb_checksum_hash_type->len); - for (int i = 0 ; i < pdb_checksum_hash_type->len; ++i) { - char* checksum_hash_type = g_array_index (pdb_checksum_hash_type, char*, i); - buffer_add_string (buf, checksum_hash_type); - if (!strcmp (checksum_hash_type, "SHA256")) - buffer_add_byte_array (buf, g_array_index (pdb_checksum, guint8*, i), 32); - else if (!strcmp (checksum_hash_type, "SHA384")) - buffer_add_byte_array (buf, g_array_index (pdb_checksum, guint8*, i), 48); - else if (!strcmp (checksum_hash_type, "SHA512")) - buffer_add_byte_array (buf, g_array_index (pdb_checksum, guint8*, i), 64); - } - g_array_free (pdb_checksum_hash_type, TRUE); - g_array_free (pdb_checksum, TRUE); + send_debug_information (ass, buf); break; } case MDBGPROT_CMD_ASSEMBLY_HAS_DEBUG_INFO_LOADED: { diff --git a/src/mono/mono/component/debugger-protocol.h b/src/mono/mono/component/debugger-protocol.h index cbcecb4..9a8cc5e1 100644 --- a/src/mono/mono/component/debugger-protocol.h +++ b/src/mono/mono/component/debugger-protocol.h @@ -11,7 +11,7 @@ */ #define MAJOR_VERSION 2 -#define MINOR_VERSION 63 +#define MINOR_VERSION 64 typedef enum { MDBGPROT_CMD_COMPOSITE = 100 diff --git a/src/mono/mono/component/mini-wasm-debugger.c b/src/mono/mono/component/mini-wasm-debugger.c index b73db55..0e737f4 100644 --- a/src/mono/mono/component/mini-wasm-debugger.c +++ b/src/mono/mono/component/mini-wasm-debugger.c @@ -432,28 +432,6 @@ mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command, invoke_data.flags = INVOKE_FLAG_DISABLE_BREAKPOINTS_AND_STEPPING; error = mono_do_invoke_method (tls, &buf, &invoke_data, data, &data); } - else if (command_set == MDBGPROT_CMD_SET_VM && (command == MDBGPROT_CMD_GET_ASSEMBLY_BYTES)) - { - char* assembly_name = m_dbgprot_decode_string (data, &data, data + size); - if (assembly_name == NULL) - { - m_dbgprot_buffer_init (&buf, 128); - m_dbgprot_buffer_add_int (&buf, 0); - m_dbgprot_buffer_add_int (&buf, 0); - } - else - { - const unsigned char* assembly_bytes = NULL; - unsigned int assembly_size = 0; - mono_bundled_resources_get_assembly_resource_values (assembly_name, &assembly_bytes, &assembly_size); - const unsigned char* pdb_bytes = NULL; - unsigned int symfile_size = 0; - mono_bundled_resources_get_assembly_resource_symbol_values (assembly_name, &pdb_bytes, &symfile_size); - m_dbgprot_buffer_init (&buf, assembly_size + symfile_size); - m_dbgprot_buffer_add_byte_array (&buf, (uint8_t *) assembly_bytes, assembly_size); - m_dbgprot_buffer_add_byte_array (&buf, (uint8_t *) pdb_bytes, symfile_size); - } - } else { m_dbgprot_buffer_init (&buf, 128); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 96aad27..5d62190 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -26,6 +26,8 @@ using Microsoft.SymbolStore.SymbolStores; using Microsoft.FileFormats.PE; using Microsoft.Extensions.Primitives; using Microsoft.NET.WebAssembly.Webcil; +using System.Net.Security; +using Microsoft.FileFormats.PDB; namespace Microsoft.WebAssembly.Diagnostics { @@ -845,66 +847,109 @@ namespace Microsoft.WebAssembly.Diagnostics private readonly List sources = new List(); internal string Url { get; } //The caller must keep the PEReader alive and undisposed throughout the lifetime of the metadata reader - private readonly IDisposable peReaderOrWebcilReader; - internal MetadataReader asmMetadataReader { get; } + private IDisposable peReaderOrWebcilReader; + internal MetadataReader asmMetadataReader { get; set; } internal MetadataReader pdbMetadataReader { get; set; } internal List<(MetadataReader asm, MetadataReader pdb)> enCMetadataReader = new(); private int debugId; - internal int PdbAge { get; } - internal System.Guid PdbGuid { get; } - internal bool IsPortableCodeView { get; } - internal string PdbName { get; } - internal bool CodeViewInformationAvailable { get; } + internal int PdbAge { get; private set; } + internal System.Guid PdbGuid { get; private set; } + internal bool IsPortableCodeView { get; set; } + internal string PdbName { get; set; } public bool TriedToLoadSymbolsOnDemand { get; set; } private readonly Dictionary _documentIdToSourceFileTable = new Dictionary(); - public PdbChecksum[] PdbChecksums { get; } + public PdbChecksum[] PdbChecksums { get; set; } - public static AssemblyInfo FromBytes(MonoProxy monoProxy, SessionId sessionId, byte[] assembly, byte[] pdb, ILogger logger, CancellationToken token) + public void LoadInfoFromBytes(MonoProxy monoProxy, SessionId sessionId, AssemblyAndPdbData assemblyAndPdbData, CancellationToken token) { - // First try to read it as a PE file, otherwise try it as a WebCIL file - using var asmStream = new MemoryStream(assembly); - try + using var asmStream = new MemoryStream(assemblyAndPdbData.AsmBytes); + if (assemblyAndPdbData.IsAsmMetadataOnly) { - var peReader = new PEReader(asmStream); - if (!peReader.HasMetadata) - throw new BadImageFormatException(); - return FromPEReader(monoProxy, sessionId, peReader, pdb, logger, token); + FromAssemblyAndPdbData(asmStream, assemblyAndPdbData); } - catch (BadImageFormatException) + else { - // This is a WebAssembly file - asmStream.Seek(0, SeekOrigin.Begin); - var webcilReader = new WebcilReader(asmStream); - return FromWebcilReader(monoProxy, sessionId, webcilReader, pdb, logger, token); + try + { + // First try to read it as a PE file, otherwise try it as a WebCIL file + var peReader = new PEReader(asmStream); + if (!peReader.HasMetadata) + throw new BadImageFormatException(); + FromPEReader(monoProxy, sessionId, peReader, assemblyAndPdbData.PdbBytes, logger, token); + } + catch (BadImageFormatException) + { + // This is a WebAssembly file + asmStream.Seek(0, SeekOrigin.Begin); + var webcilReader = new WebcilReader(asmStream); + FromWebcilReader(monoProxy, sessionId, webcilReader, assemblyAndPdbData.PdbBytes, logger, token); + } } } + public static AssemblyInfo FromBytes(MonoProxy monoProxy, SessionId sessionId, AssemblyAndPdbData assemblyAndPdbData, ILogger logger, CancellationToken token) + { + var assemblyInfo = new AssemblyInfo(logger); + assemblyInfo.LoadInfoFromBytes(monoProxy, sessionId, assemblyAndPdbData, token); + return assemblyInfo; + } + public static AssemblyInfo WithoutDebugInfo(ILogger logger) { return new AssemblyInfo(logger); } + public static AssemblyInfo WithoutDebugInfo(string name, ILogger logger) => new AssemblyInfo(logger) { Name = name }; + private AssemblyInfo(ILogger logger) { debugId = -1; this.id = Interlocked.Increment(ref next_id); this.logger = logger; } - private static AssemblyInfo FromPEReader(MonoProxy monoProxy, SessionId sessionId, PEReader peReader, byte[] pdb, ILogger logger, CancellationToken token) + private void FromAssemblyAndPdbData(Stream _stream, AssemblyAndPdbData assemblyAndPdbData) { + var asmMetadataReader = MetadataReaderProvider.FromMetadataStream(_stream, MetadataStreamOptions.LeaveOpen).GetMetadataReader(); + Name = ReadAssemblyName(asmMetadataReader); + if (assemblyAndPdbData.PdbBytes != null) + { + if (assemblyAndPdbData.PdbUncompressedSize > 0) + { + byte[] decompressedBuffer; + using var compressedStream = new MemoryStream(assemblyAndPdbData.PdbBytes, writable: false); + using var deflateStream = new System.IO.Compression.DeflateStream(compressedStream, System.IO.Compression.CompressionMode.Decompress, leaveOpen: true); + decompressedBuffer = GC.AllocateUninitializedArray(assemblyAndPdbData.PdbUncompressedSize); + using var decompressedStream = new MemoryStream(decompressedBuffer, writable: true); + deflateStream.CopyTo(decompressedStream); + this.pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(new MemoryStream(decompressedBuffer, writable: false)).GetMetadataReader(); + } + else + { + this.pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(new MemoryStream(assemblyAndPdbData.PdbBytes, writable: false)).GetMetadataReader(); + } + } + this.asmMetadataReader = asmMetadataReader; + PdbAge = assemblyAndPdbData.PdbAge; + PdbGuid = assemblyAndPdbData.PdbGuid; + PdbName = assemblyAndPdbData.PdbPath; + IsPortableCodeView = assemblyAndPdbData.IsPortableCodeView; + PdbChecksums = assemblyAndPdbData.PdbChecksums.ToArray(); + Populate(); + } + private void FromPEReader(MonoProxy monoProxy, SessionId sessionId, PEReader peReader, byte[] pdb, ILogger logger, CancellationToken token) + { var debugProvider = new PortableExecutableDebugMetadataProvider(peReader); var asmMetadataReader = PEReaderExtensions.GetMetadataReader(peReader); string name = ReadAssemblyName(asmMetadataReader); var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token); - var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, summary, logger); - return assemblyInfo; + LoadAssemblyInfo(peReader, name, asmMetadataReader, summary, logger); } - private static AssemblyInfo FromWebcilReader(MonoProxy monoProxy, SessionId sessionId, WebcilReader wcReader, byte[] pdb, ILogger logger, CancellationToken token) + private void FromWebcilReader(MonoProxy monoProxy, SessionId sessionId, WebcilReader wcReader, byte[] pdb, ILogger logger, CancellationToken token) { var debugProvider = new WebcilDebugMetadataProvider(wcReader); var asmMetadataReader = wcReader.GetMetadataReader(); @@ -912,8 +957,7 @@ namespace Microsoft.WebAssembly.Diagnostics var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token); - var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, summary, logger); - return assemblyInfo; + LoadAssemblyInfo(wcReader, name, asmMetadataReader, summary, logger); } private static string ReadAssemblyName(MetadataReader asmMetadataReader) @@ -922,8 +966,7 @@ namespace Microsoft.WebAssembly.Diagnostics return asmDef.GetAssemblyName().Name + ".dll"; } - private unsafe AssemblyInfo(IDisposable owningReader, string name, MetadataReader asmMetadataReader, MetadataDebugSummary summary, ILogger logger) - : this(logger) + private unsafe void LoadAssemblyInfo(IDisposable owningReader, string name, MetadataReader asmMetadataReader, MetadataDebugSummary summary, ILogger logger) { peReaderOrWebcilReader = owningReader; var codeViewData = summary.CodeViewData; @@ -932,7 +975,6 @@ namespace Microsoft.WebAssembly.Diagnostics PdbAge = codeViewData.Value.Age; PdbGuid = codeViewData.Value.Guid; PdbName = codeViewData.Value.Path; - CodeViewInformationAvailable = true; } IsPortableCodeView = summary.IsPortableCodeView; PdbChecksums = summary.PdbChecksums; @@ -1173,11 +1215,6 @@ namespace Microsoft.WebAssembly.Diagnostics private void Populate() { - foreach (DocumentHandle dh in asmMetadataReader.Documents) - { - asmMetadataReader.GetDocument(dh); - } - if (pdbMetadataReader != null) ProcessSourceLink(); @@ -1188,7 +1225,6 @@ namespace Microsoft.WebAssembly.Diagnostics foreach (MethodDefinitionHandle method in typeDefinition.GetMethods()) { - var methodDefinition = asmMetadataReader.GetMethodDefinition(method); SourceFile source = null; if (pdbMetadataReader != null) { @@ -1252,7 +1288,7 @@ namespace Microsoft.WebAssembly.Diagnostics public Dictionary TypesByName { get; } = new(); public Dictionary TypesByToken { get; } = new(); public int Id => id; - public string Name { get; } + public string Name { get; set; } public bool HasSymbols => pdbMetadataReader != null; // "System.Threading", instead of "System.Threading, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" @@ -1275,12 +1311,17 @@ namespace Microsoft.WebAssembly.Diagnostics return res; } - internal async Task LoadPDBFromSymbolServer(DebugStore debugStore, CancellationToken token) + internal async Task LoadPDBFromSymbolServer(MonoProxy proxy, MonoSDBHelper sdbHelper, SessionId id, DebugStore debugStore, CancellationToken token) { try { if (TriedToLoadSymbolsOnDemand) return; + if (asmMetadataReader is null) //it means that the assembly was not loaded before because JMC was enabled + { + var ret = await sdbHelper.GetDataFromAssemblyAndPdbAsync(Name, false, token); + LoadInfoFromBytes(proxy, id, ret, token); + } var pdbName = Path.GetFileName(PdbName); var pdbGuid = PdbGuid.ToString("N").ToUpperInvariant() + (IsPortableCodeView ? "FFFFFFFF" : PdbAge); var key = $"{pdbName}/{pdbGuid}/{pdbName}"; @@ -1569,8 +1610,10 @@ namespace Microsoft.WebAssembly.Diagnostics private sealed class DebugItem { public string Url { get; set; } - public Task Data { get; set; } + public Task DataTask { get; set; } + public Task ByteArrayTask { get; set; } } + public static IEnumerable EnC(MonoSDBHelper sdbAgent, AssemblyInfo asm, byte[] meta_data, byte[] pdb_data) { asm.EnC(sdbAgent, meta_data, pdb_data); @@ -1586,12 +1629,12 @@ namespace Microsoft.WebAssembly.Diagnostics } } - public IEnumerable Add(SessionId id, byte[] assembly_data, byte[] pdb_data, CancellationToken token) + public IEnumerable Add(SessionId id, AssemblyAndPdbData assemblyAndPdbData, CancellationToken token) { AssemblyInfo assembly; try { - assembly = AssemblyInfo.FromBytes(monoProxy, id, assembly_data, pdb_data, logger, token); + assembly = AssemblyInfo.FromBytes(monoProxy, id, assemblyAndPdbData, logger, token); } catch (Exception e) { @@ -1642,7 +1685,7 @@ namespace Microsoft.WebAssembly.Diagnostics new DebugItem { Url = url, - Data = Task.WhenAll(MonoProxy.HttpClient.GetByteArrayAsync(url, token), pdb != null ? MonoProxy.HttpClient.GetByteArrayAsync(pdb, token) : Task.FromResult(null)) + ByteArrayTask = Task.WhenAll(MonoProxy.HttpClient.GetByteArrayAsync(url, token), pdb != null ? MonoProxy.HttpClient.GetByteArrayAsync(pdb, token) : Task.FromResult(null)), }); } catch (Exception e) @@ -1664,7 +1707,7 @@ namespace Microsoft.WebAssembly.Diagnostics new DebugItem { Url = file_name, - Data = context.SdbAgent.GetBytesFromAssemblyAndPdb(Path.GetFileName(unescapedFileName), token) + DataTask = context.SdbAgent.GetDataFromAssemblyAndPdbAsync(Path.GetFileName(unescapedFileName), false, token) }); } catch (Exception e) @@ -1679,13 +1722,24 @@ namespace Microsoft.WebAssembly.Diagnostics AssemblyInfo assembly = null; try { - byte[][] bytes = await step.Data.ConfigureAwait(false); - if (bytes[0] == null) + AssemblyAndPdbData assemblyAndPdbData; + if (step.ByteArrayTask != null) { + byte[][] byteArray = await step.ByteArrayTask.ConfigureAwait(false); + assemblyAndPdbData = new AssemblyAndPdbData(byteArray[0], byteArray[1]); + } + else + { + assemblyAndPdbData = await step.DataTask.ConfigureAwait(false); + } + if (assemblyAndPdbData == null || assemblyAndPdbData.AsmBytes == null) + { + var unescapedFileName = Uri.UnescapeDataString(step.Url); + assemblies.Add(AssemblyInfo.WithoutDebugInfo(Path.GetFileName(unescapedFileName), logger)); logger.LogDebug($"Bytes from assembly {step.Url} is NULL"); continue; } - assembly = AssemblyInfo.FromBytes(monoProxy, id, bytes[0], bytes[1], logger, token); + assembly = AssemblyInfo.FromBytes(monoProxy, id, assemblyAndPdbData, logger, token); } catch (Exception e) { @@ -1710,7 +1764,16 @@ namespace Microsoft.WebAssembly.Diagnostics public SourceFile GetFileById(SourceId id) => AllSources().SingleOrDefault(f => f.SourceId.Equals(id)); - public AssemblyInfo GetAssemblyByName(string name) => assemblies.FirstOrDefault(a => a.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + public AssemblyInfo GetAssemblyByName(string name) + { + var nameOnly = Path.GetFileNameWithoutExtension(name.AsSpan()); + foreach (var asm in assemblies) + { + if (MemoryExtensions.Equals(nameOnly, Path.GetFileNameWithoutExtension(asm.Name.AsSpan()), StringComparison.InvariantCultureIgnoreCase)) + return asm; + } + return null; + } /* V8 uses zero based indexing for both line and column. @@ -1877,7 +1940,7 @@ namespace Microsoft.WebAssembly.Diagnostics foreach (var asm in assemblies.Where(asm => asm.pdbMetadataReader == null)) { asm.TriedToLoadSymbolsOnDemand = false; //force to load again because added another symbol server - await asm.LoadPDBFromSymbolServer(this, token); + await asm.LoadPDBFromSymbolServer(monoProxy, context.SdbAgent, id, this, token); foreach (var source in asm.Sources) await monoProxy.OnSourceFileAdded(id, source, context, token); } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 3eb360c..dd4dc6e 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -674,7 +674,6 @@ namespace Microsoft.WebAssembly.Diagnostics // GetAssemblyByName seems to work on file names AssemblyInfo assembly = store.GetAssemblyByName(aname); - assembly ??= store.GetAssemblyByName(aname + ".exe"); assembly ??= store.GetAssemblyByName(aname + ".dll"); if (assembly == null) { @@ -1334,7 +1333,7 @@ namespace Microsoft.WebAssembly.Diagnostics var pdb_data = string.IsNullOrEmpty(pdb_b64) ? null : Convert.FromBase64String(pdb_b64); var context = Contexts.GetCurrentContext(sessionId); - foreach (var source in store.Add(sessionId, assembly_data, pdb_data, token)) + foreach (var source in store.Add(sessionId, new AssemblyAndPdbData(assembly_data, pdb_data), token)) { await OnSourceFileAdded(sessionId, source, context, token); } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index a620f69..11a0c68 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -17,6 +17,7 @@ using System.Reflection; using System.Text; using System.Runtime.CompilerServices; using BrowserDebugProxy; +using Microsoft.FileFormats.PE; namespace Microsoft.WebAssembly.Diagnostics { @@ -204,6 +205,7 @@ namespace Microsoft.WebAssembly.Diagnostics GetTypeFromToken = 11, GetMethodFromToken = 12, HasDebugInfo = 13, + HasDebugInfoLoaded = 18 } internal enum CmdModule { @@ -362,6 +364,31 @@ namespace Microsoft.WebAssembly.Diagnostics LineColumn } + internal sealed class AssemblyAndPdbData + { + public bool IsAsmMetadataOnly { get; init; } + public byte[] AsmBytes { get; set; } + public byte[] PdbBytes { get; set; } + public bool HasDebugInfo { get; set; } + public int PdbAge { get; set; } + public Guid PdbGuid { get; set; } + public string PdbPath { get; set; } + public int PdbUncompressedSize { get; set; } + public bool IsPortableCodeView { get; init; } + public List PdbChecksums { get; init; } + internal AssemblyAndPdbData(byte[] asm, byte[] pdb) + { + AsmBytes = asm; + PdbBytes = pdb; + } + internal AssemblyAndPdbData() + { + IsPortableCodeView = true; + IsAsmMetadataOnly = true; + PdbChecksums = new(); + } + } + internal sealed record ArrayDimensions { internal int Rank { get; } @@ -878,6 +905,15 @@ namespace Microsoft.WebAssembly.Diagnostics logger.LogDebug($"Created assembly without debug information: {assemblyName}"); } } + else + { + if (asm.asmMetadataReader is null && proxy.JustMyCode) //load on demand + { + var assemblyAndPdbData = await GetDataFromAssemblyAndPdbAsync(asm.Name, true, token); + if (assemblyAndPdbData is not null) + asm.LoadInfoFromBytes(proxy, sessionId, assemblyAndPdbData, token); + } + } asm.SetDebugId(assemblyId); assemblies[assemblyId] = asm; return asm; @@ -1909,7 +1945,7 @@ namespace Microsoft.WebAssembly.Diagnostics foreach (var methodId in methodIds) { var methodInfoFromRuntime = await GetMethodInfo(methodId, token); - if (methodInfoFromRuntime.Info.GetParametersInfo().Length > 0) + if (methodInfoFromRuntime?.Info?.GetParametersInfo()?.Length > 0) continue; var retMethod = await InvokeMethod(objectId, methodId, isValueType, token); return retMethod["value"]?["value"].Value(); @@ -2475,23 +2511,59 @@ namespace Microsoft.WebAssembly.Diagnostics return true; } - public async Task GetBytesFromAssemblyAndPdb(string assemblyName, CancellationToken token) + public async Task HasDebugInfoLoadedByRuntimeAsync(string assemblyName, CancellationToken token) { + var assemblyId = await GetAssemblyId(assemblyName, token); + using var commandParamsWriter = new MonoBinaryWriter(); + commandParamsWriter.Write(assemblyId); + using var retDebuggerCmdReader1 = await SendDebuggerAgentCommand(CmdAssembly.HasDebugInfoLoaded, commandParamsWriter, token); + return retDebuggerCmdReader1.ReadByte() == 1; + } + + public async Task GetDataFromAssemblyAndPdbAsync(string assemblyName, bool ignoreJMC, CancellationToken token) + { + if (!ignoreJMC && proxy.JustMyCode && !assemblyName.StartsWith("System.Private.CoreLib", StringComparison.Ordinal) && !(await HasDebugInfoLoadedByRuntimeAsync(assemblyName, token))) + return null; //only load symbols if JustMyCode is disabled, or it's corelib or has debug info loaded by runtime which mean it's an user assembly using var commandParamsWriter = new MonoBinaryWriter(); byte[] assembly_buf = null; byte[] pdb_buf = null; + var pdbUncompressedSize = 0; + (int MajorVersion, int MinorVersion) = await GetVMVersion(token); + commandParamsWriter.Write(assemblyName); var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.GetAssemblyAndPdbBytes, commandParamsWriter, token); int assembly_size = retDebuggerCmdReader.ReadInt32(); if (assembly_size > 0) assembly_buf = retDebuggerCmdReader.ReadBytes(assembly_size); + if (MajorVersion == 2 && MinorVersion >= 64 || MajorVersion >= 2) + pdbUncompressedSize = retDebuggerCmdReader.ReadInt32(); int pdb_size = retDebuggerCmdReader.ReadInt32(); if (pdb_size > 0) pdb_buf = retDebuggerCmdReader.ReadBytes(pdb_size); - byte[][] ret = new byte[2][]; - ret[0] = assembly_buf; - ret[1] = pdb_buf; - return ret; + + if (!(MajorVersion == 2 && MinorVersion >= 64 || MajorVersion >= 2)) //versions older than 2.64 do not support this new format of GetAssemblyAndPdbBytes + return new(assembly_buf, pdb_buf); + + AssemblyAndPdbData data = new(); + data.AsmBytes = assembly_buf; + data.PdbBytes = pdb_buf; + data.HasDebugInfo = retDebuggerCmdReader.ReadBoolean(); + data.PdbUncompressedSize = pdbUncompressedSize; + if (!data.HasDebugInfo) + return data; + + data.PdbAge = retDebuggerCmdReader.ReadInt32(); + var pdbGuidSize = retDebuggerCmdReader.ReadInt32(); + data.PdbGuid = new Guid(retDebuggerCmdReader.ReadBytes(pdbGuidSize)); + data.PdbPath = retDebuggerCmdReader.ReadString(); + var pdbChecksumCount = retDebuggerCmdReader.ReadInt32(); + for (int i = 0; i < pdbChecksumCount; i++) + { + var algorithmName = retDebuggerCmdReader.ReadString(); + var pdbChecksumSize = retDebuggerCmdReader.ReadInt32(); + data.PdbChecksums.Add(new PdbChecksum(algorithmName, retDebuggerCmdReader.ReadBytes(pdbChecksumSize))); + } + return data; } private static readonly string[] s_primitiveTypeNames = new[] { -- 2.7.4