From 524aaf2d60fb00de59d6f4574d7e3b1a0a22d0b4 Mon Sep 17 00:00:00 2001 From: Paolo Severini Date: Fri, 22 May 2026 12:28:29 +0200 Subject: [PATCH 1/7] Add support for Wasm symbol files --- .../KeyGenerators/FileKeyGenerator.cs | 1 + .../KeyGenerators/WasmFileKeyGenerator.cs | 267 ++++++++++++++++++ .../KeyGeneratorTests.cs | 59 ++++ .../TestBinaries/test_module.wasm | Bin 0 -> 35 bytes .../TestBinaries/test_module_no_buildid.wasm | Bin 0 -> 17 bytes .../TestBinaries/test_module_symbols.wasm | Bin 0 -> 53 bytes 6 files changed, 327 insertions(+) create mode 100644 src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs create mode 100644 src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module.wasm create mode 100644 src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module_no_buildid.wasm create mode 100644 src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module_symbols.wasm diff --git a/src/Microsoft.SymbolStore/KeyGenerators/FileKeyGenerator.cs b/src/Microsoft.SymbolStore/KeyGenerators/FileKeyGenerator.cs index ebab6cdfa1..52d1cb31a5 100644 --- a/src/Microsoft.SymbolStore/KeyGenerators/FileKeyGenerator.cs +++ b/src/Microsoft.SymbolStore/KeyGenerators/FileKeyGenerator.cs @@ -58,6 +58,7 @@ private IEnumerable GetGenerators() yield return new PDBFileKeyGenerator(Tracer, _file); yield return new PortablePDBFileKeyGenerator(Tracer, _file); yield return new PerfMapFileKeyGenerator(Tracer, _file); + yield return new WasmFileKeyGenerator(Tracer, _file); } } } diff --git a/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs new file mode 100644 index 0000000000..c953139a06 --- /dev/null +++ b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs @@ -0,0 +1,267 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace Microsoft.SymbolStore.KeyGenerators +{ + public class WasmFileKeyGenerator : KeyGenerator + { + /// + /// Wasm binary magic number: '\0asm' + /// + private static readonly byte[] s_wasmMagic = new byte[] { 0x00, 0x61, 0x73, 0x6D }; + + /// + /// Wasm binary format version 1 + /// + private static readonly byte[] s_wasmVersion = new byte[] { 0x01, 0x00, 0x00, 0x00 }; + + /// + /// Custom section ID in Wasm binary format + /// + private const byte CustomSectionId = 0; + + /// + /// The name of the custom section containing the build ID + /// + private const string BuildIdSectionName = "build_id"; + + private const string IdentityPrefix = "wasm-buildid"; + private const string SymbolPrefix = "wasm-buildid-sym"; + + private readonly SymbolStoreFile _file; + private byte[] _buildId; + private bool _parsed; + private bool _isValid; + + public WasmFileKeyGenerator(ITracer tracer, SymbolStoreFile file) + : base(tracer) + { + _file = file ?? throw new ArgumentNullException(nameof(file)); + } + + public override bool IsValid() + { + ParseWasmFile(); + return _isValid; + } + + public override IEnumerable GetKeys(KeyTypeFlags flags) + { + if (IsValid()) + { + if ((flags & KeyTypeFlags.IdentityKey) != 0) + { + bool isSymbolFile = IsSymbolFile(); + yield return GetKey(_file.FileName, _buildId, isSymbolFile); + } + } + } + + /// + /// Create a symbol store key for a Wasm file with a build ID. + /// + /// file name and path + /// build ID bytes from the buildId custom section + /// if true, this is a symbol file (contains DWARF sections) + /// symbol store key + public static SymbolStoreKey GetKey(string path, byte[] buildId, bool symbolFile) + { + Debug.Assert(path != null); + Debug.Assert(buildId != null && buildId.Length > 0); + string prefix = symbolFile ? SymbolPrefix : IdentityPrefix; + return BuildKey(path, prefix, buildId); + } + + /// + /// Determines whether this Wasm module is a symbol file by checking + /// for the presence of DWARF debug custom sections. + /// + private bool IsSymbolFile() + { + try + { + Stream stream = _file.Stream; + stream.Position = 8; // Skip magic and version + + while (stream.Position < stream.Length) + { + int sectionId = stream.ReadByte(); + if (sectionId == -1) + { + break; + } + + uint sectionSize = ReadLEB128Unsigned(stream); + long sectionEnd = stream.Position + sectionSize; + + if (sectionId == CustomSectionId) + { + long nameStart = stream.Position; + string name = ReadWasmString(stream); + if (name != null && name.StartsWith(".debug_")) + { + return true; + } + } + + stream.Position = sectionEnd; + } + } + catch (Exception ex) when (ex is IOException || ex is OverflowException || ex is ArgumentOutOfRangeException) + { + Tracer.Verbose("Error checking Wasm symbol sections in {0}: {1}", _file.FileName, ex.Message); + } + + return false; + } + + /// + /// Parses the Wasm file to validate the header and find the buildId custom section. + /// + private void ParseWasmFile() + { + if (_parsed) + { + return; + } + _parsed = true; + _isValid = false; + + try + { + Stream stream = _file.Stream; + stream.Position = 0; + + // Validate magic number + byte[] magic = new byte[4]; + if (stream.Read(magic, 0, 4) != 4) + { + return; + } + for (int i = 0; i < 4; i++) + { + if (magic[i] != s_wasmMagic[i]) + { + return; + } + } + + // Validate version + byte[] version = new byte[4]; + if (stream.Read(version, 0, 4) != 4) + { + return; + } + for (int i = 0; i < 4; i++) + { + if (version[i] != s_wasmVersion[i]) + { + return; + } + } + + // Scan sections for the buildId custom section + while (stream.Position < stream.Length) + { + int sectionId = stream.ReadByte(); + if (sectionId == -1) + { + break; + } + + uint sectionSize = ReadLEB128Unsigned(stream); + long sectionEnd = stream.Position + sectionSize; + + if (sectionId == CustomSectionId) + { + long nameStart = stream.Position; + string name = ReadWasmString(stream); + if (name == BuildIdSectionName) + { + // The remainder of the section payload is the build ID + int buildIdLength = (int)(sectionEnd - stream.Position); + if (buildIdLength > 0) + { + _buildId = new byte[buildIdLength]; + if (stream.Read(_buildId, 0, buildIdLength) == buildIdLength) + { + _isValid = true; + return; + } + } + } + } + + stream.Position = sectionEnd; + } + } + catch (Exception ex) when (ex is IOException || ex is OverflowException || ex is ArgumentOutOfRangeException) + { + Tracer.Verbose("Error parsing Wasm file {0}: {1}", _file.FileName, ex.Message); + } + } + + /// + /// Reads an unsigned LEB128-encoded integer from the stream. + /// + private static uint ReadLEB128Unsigned(Stream stream) + { + uint result = 0; + int shift = 0; + + while (true) + { + int b = stream.ReadByte(); + if (b == -1) + { + throw new IOException("Unexpected end of stream reading LEB128 value."); + } + + result |= (uint)(b & 0x7F) << shift; + if ((b & 0x80) == 0) + { + break; + } + + shift += 7; + if (shift >= 35) + { + throw new OverflowException("LEB128 value too large for uint32."); + } + } + + return result; + } + + /// + /// Reads a Wasm string (LEB128 length prefix followed by UTF-8 bytes). + /// + private static string ReadWasmString(Stream stream) + { + uint length = ReadLEB128Unsigned(stream); + if (length == 0) + { + return string.Empty; + } + if (length > int.MaxValue) + { + return null; + } + + byte[] bytes = new byte[length]; + int bytesRead = stream.Read(bytes, 0, (int)length); + if (bytesRead != (int)length) + { + return null; + } + + return Encoding.UTF8.GetString(bytes); + } + } +} diff --git a/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs b/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs index 85509b5b96..d40d6f310b 100644 --- a/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs +++ b/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs @@ -33,6 +33,7 @@ public void FileKeyGenerator() PEFileKeyGeneratorInternal(fileGenerator: true); PortablePDBFileKeyGeneratorInternal(fileGenerator: true); PerfMapFileKeyGeneratorInternal(fileGenerator: true); + WasmFileKeyGeneratorInternal(fileGenerator: true); } @@ -531,5 +532,63 @@ public void SourceFileKeyGenerator() Assert.True(clrKeys.Count() == 0); } } + [Fact] + public void WasmFileKeyGenerator() + { + WasmFileKeyGeneratorInternal(fileGenerator: false); + } + + private void WasmFileKeyGeneratorInternal(bool fileGenerator) + { + // Test 1: Plain Wasm module with build_id (not a symbol file) + const string WasmModulePath = "TestBinaries/test_module.wasm"; + using (Stream stream = File.OpenRead(WasmModulePath)) + { + var file = new SymbolStoreFile(stream, WasmModulePath); + KeyGenerator generator = fileGenerator ? (KeyGenerator)new FileKeyGenerator(_tracer, file) : new WasmFileKeyGenerator(_tracer, file); + + Assert.True(generator.IsValid()); + + IEnumerable identityKey = generator.GetKeys(KeyTypeFlags.IdentityKey); + Assert.True(identityKey.Count() == 1); + Assert.True(identityKey.First().Index == "test_module.wasm/wasm-buildid-deadbeef0123456789abcdeffedcba98/test_module.wasm"); + + IEnumerable symbolKey = generator.GetKeys(KeyTypeFlags.SymbolKey); + Assert.True(!symbolKey.Any()); + + IEnumerable clrKeys = generator.GetKeys(KeyTypeFlags.ClrKeys); + Assert.True(!clrKeys.Any()); + } + + // Test 2: Wasm symbol file with build_id and .debug_info section + const string WasmSymbolPath = "TestBinaries/test_module_symbols.wasm"; + using (Stream stream = File.OpenRead(WasmSymbolPath)) + { + var file = new SymbolStoreFile(stream, WasmSymbolPath); + KeyGenerator generator = fileGenerator ? (KeyGenerator)new FileKeyGenerator(_tracer, file) : new WasmFileKeyGenerator(_tracer, file); + + Assert.True(generator.IsValid()); + + IEnumerable identityKey = generator.GetKeys(KeyTypeFlags.IdentityKey); + Assert.True(identityKey.Count() == 1); + Assert.True(identityKey.First().Index == "test_module_symbols.wasm/wasm-buildid-sym-deadbeef0123456789abcdeffedcba98/test_module_symbols.wasm"); + + IEnumerable symbolKey = generator.GetKeys(KeyTypeFlags.SymbolKey); + Assert.True(!symbolKey.Any()); + } + + // Test 3: Wasm file without build_id should be invalid + const string WasmNoBuildIdPath = "TestBinaries/test_module_no_buildid.wasm"; + using (Stream stream = File.OpenRead(WasmNoBuildIdPath)) + { + var file = new SymbolStoreFile(stream, WasmNoBuildIdPath); + var generator = new WasmFileKeyGenerator(_tracer, file); + + Assert.False(generator.IsValid()); + + IEnumerable identityKey = generator.GetKeys(KeyTypeFlags.IdentityKey); + Assert.True(!identityKey.Any()); + } + } } } diff --git a/src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module.wasm b/src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d6726a97845ec772da8b8ac73b3389572084d181 GIT binary patch literal 35 qcmZQbEY4+Q00K#lq|(fsl=#e)du#W-XH<4g?_7QM{l7cAW&i-d&kn=@ literal 0 HcmV?d00001 diff --git a/src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module_no_buildid.wasm b/src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module_no_buildid.wasm new file mode 100644 index 0000000000000000000000000000000000000000..02402cb034d2db29b03a86716efa4108a8bc9799 GIT binary patch literal 17 WcmZQbEY4+Q00MTFyu{p8MkW9nl>+hr literal 0 HcmV?d00001 diff --git a/src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module_symbols.wasm b/src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module_symbols.wasm new file mode 100644 index 0000000000000000000000000000000000000000..cf5e2aef9c5f3d9bbf8a0753fa831b248954c368 GIT binary patch literal 53 zcmZQbEY4+Q00K#lq|(fsl=#e)du#W-XH<4g?_7QM{l7cAW-thF>!qY7m8QpM=B4E` IFfuU%0QHa)B>(^b literal 0 HcmV?d00001 From 9cdb842875694157517e4845611df3f919084926 Mon Sep 17 00:00:00 2001 From: Paolo Severini Date: Tue, 26 May 2026 11:45:40 +0200 Subject: [PATCH 2/7] Address AI review comments --- .../KeyGenerators/WasmFileKeyGenerator.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs index c953139a06..c68d1e48c3 100644 --- a/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs +++ b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs @@ -34,6 +34,12 @@ public class WasmFileKeyGenerator : KeyGenerator private const string IdentityPrefix = "wasm-buildid"; private const string SymbolPrefix = "wasm-buildid-sym"; + /// + /// Maximum reasonable build ID length (256 bytes). Protects against + /// malformed input causing large allocations. + /// + private const int MaxBuildIdLength = 256; + private readonly SymbolStoreFile _file; private byte[] _buildId; private bool _parsed; @@ -100,11 +106,15 @@ private bool IsSymbolFile() uint sectionSize = ReadLEB128Unsigned(stream); long sectionEnd = stream.Position + sectionSize; + if (sectionEnd > stream.Length) + { + break; + } + if (sectionId == CustomSectionId) { - long nameStart = stream.Position; string name = ReadWasmString(stream); - if (name != null && name.StartsWith(".debug_")) + if (name != null && name.StartsWith(".debug_", StringComparison.Ordinal)) { return true; } @@ -178,15 +188,20 @@ private void ParseWasmFile() uint sectionSize = ReadLEB128Unsigned(stream); long sectionEnd = stream.Position + sectionSize; + // Validate that the section doesn't extend beyond the stream + if (sectionEnd > stream.Length) + { + break; + } + if (sectionId == CustomSectionId) { - long nameStart = stream.Position; string name = ReadWasmString(stream); if (name == BuildIdSectionName) { // The remainder of the section payload is the build ID int buildIdLength = (int)(sectionEnd - stream.Position); - if (buildIdLength > 0) + if (buildIdLength > 0 && buildIdLength <= MaxBuildIdLength) { _buildId = new byte[buildIdLength]; if (stream.Read(_buildId, 0, buildIdLength) == buildIdLength) @@ -254,9 +269,10 @@ private static string ReadWasmString(Stream stream) return null; } - byte[] bytes = new byte[length]; - int bytesRead = stream.Read(bytes, 0, (int)length); - if (bytesRead != (int)length) + int stringLength = (int)length; + byte[] bytes = new byte[stringLength]; + int bytesRead = stream.Read(bytes, 0, stringLength); + if (bytesRead != stringLength) { return null; } From a04056eda29d6bb87de6066bd45ced9ff28b57ba Mon Sep 17 00:00:00 2001 From: Paolo Severini Date: Tue, 26 May 2026 12:19:55 +0200 Subject: [PATCH 3/7] Refactor key format for Wasm according to SSQP spec --- .../KeyGenerators/WasmFileKeyGenerator.cs | 13 ++++++++----- .../KeyGeneratorTests.cs | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs index c68d1e48c3..c0b4c6b2db 100644 --- a/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs +++ b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs @@ -31,8 +31,7 @@ public class WasmFileKeyGenerator : KeyGenerator /// private const string BuildIdSectionName = "build_id"; - private const string IdentityPrefix = "wasm-buildid"; - private const string SymbolPrefix = "wasm-buildid-sym"; + private const string SymbolFileSuffix = ".s"; /// /// Maximum reasonable build ID length (256 bytes). Protects against @@ -73,15 +72,19 @@ public override IEnumerable GetKeys(KeyTypeFlags flags) /// Create a symbol store key for a Wasm file with a build ID. /// /// file name and path - /// build ID bytes from the buildId custom section + /// build ID bytes from the build_id custom section /// if true, this is a symbol file (contains DWARF sections) /// symbol store key public static SymbolStoreKey GetKey(string path, byte[] buildId, bool symbolFile) { Debug.Assert(path != null); Debug.Assert(buildId != null && buildId.Length > 0); - string prefix = symbolFile ? SymbolPrefix : IdentityPrefix; - return BuildKey(path, prefix, buildId); + string file = GetFileName(path).ToLowerInvariant(); + if (symbolFile) + { + file += SymbolFileSuffix; + } + return BuildKey(path, prefix: null, buildId, file); } /// diff --git a/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs b/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs index d40d6f310b..89c2150fe8 100644 --- a/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs +++ b/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs @@ -551,7 +551,7 @@ private void WasmFileKeyGeneratorInternal(bool fileGenerator) IEnumerable identityKey = generator.GetKeys(KeyTypeFlags.IdentityKey); Assert.True(identityKey.Count() == 1); - Assert.True(identityKey.First().Index == "test_module.wasm/wasm-buildid-deadbeef0123456789abcdeffedcba98/test_module.wasm"); + Assert.True(identityKey.First().Index == "test_module.wasm/deadbeef0123456789abcdeffedcba98/test_module.wasm"); IEnumerable symbolKey = generator.GetKeys(KeyTypeFlags.SymbolKey); Assert.True(!symbolKey.Any()); @@ -571,7 +571,7 @@ private void WasmFileKeyGeneratorInternal(bool fileGenerator) IEnumerable identityKey = generator.GetKeys(KeyTypeFlags.IdentityKey); Assert.True(identityKey.Count() == 1); - Assert.True(identityKey.First().Index == "test_module_symbols.wasm/wasm-buildid-sym-deadbeef0123456789abcdeffedcba98/test_module_symbols.wasm"); + Assert.True(identityKey.First().Index == "test_module_symbols.wasm.s/deadbeef0123456789abcdeffedcba98/test_module_symbols.wasm.s"); IEnumerable symbolKey = generator.GetKeys(KeyTypeFlags.SymbolKey); Assert.True(!symbolKey.Any()); From 3d0c30be08b1ad4e1da033aa607dd2ec9fc7ea12 Mon Sep 17 00:00:00 2001 From: Paolo Severini Date: Tue, 26 May 2026 19:07:50 +0200 Subject: [PATCH 4/7] Remove '.s' extension for symbol file key --- .../KeyGenerators/WasmFileKeyGenerator.cs | 59 +------------------ .../KeyGeneratorTests.cs | 2 +- 2 files changed, 3 insertions(+), 58 deletions(-) diff --git a/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs index c0b4c6b2db..59841c02da 100644 --- a/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs +++ b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs @@ -31,8 +31,6 @@ public class WasmFileKeyGenerator : KeyGenerator /// private const string BuildIdSectionName = "build_id"; - private const string SymbolFileSuffix = ".s"; - /// /// Maximum reasonable build ID length (256 bytes). Protects against /// malformed input causing large allocations. @@ -62,8 +60,7 @@ public override IEnumerable GetKeys(KeyTypeFlags flags) { if ((flags & KeyTypeFlags.IdentityKey) != 0) { - bool isSymbolFile = IsSymbolFile(); - yield return GetKey(_file.FileName, _buildId, isSymbolFile); + yield return GetKey(_file.FileName, _buildId); } } } @@ -73,67 +70,15 @@ public override IEnumerable GetKeys(KeyTypeFlags flags) /// /// file name and path /// build ID bytes from the build_id custom section - /// if true, this is a symbol file (contains DWARF sections) /// symbol store key - public static SymbolStoreKey GetKey(string path, byte[] buildId, bool symbolFile) + public static SymbolStoreKey GetKey(string path, byte[] buildId) { Debug.Assert(path != null); Debug.Assert(buildId != null && buildId.Length > 0); string file = GetFileName(path).ToLowerInvariant(); - if (symbolFile) - { - file += SymbolFileSuffix; - } return BuildKey(path, prefix: null, buildId, file); } - /// - /// Determines whether this Wasm module is a symbol file by checking - /// for the presence of DWARF debug custom sections. - /// - private bool IsSymbolFile() - { - try - { - Stream stream = _file.Stream; - stream.Position = 8; // Skip magic and version - - while (stream.Position < stream.Length) - { - int sectionId = stream.ReadByte(); - if (sectionId == -1) - { - break; - } - - uint sectionSize = ReadLEB128Unsigned(stream); - long sectionEnd = stream.Position + sectionSize; - - if (sectionEnd > stream.Length) - { - break; - } - - if (sectionId == CustomSectionId) - { - string name = ReadWasmString(stream); - if (name != null && name.StartsWith(".debug_", StringComparison.Ordinal)) - { - return true; - } - } - - stream.Position = sectionEnd; - } - } - catch (Exception ex) when (ex is IOException || ex is OverflowException || ex is ArgumentOutOfRangeException) - { - Tracer.Verbose("Error checking Wasm symbol sections in {0}: {1}", _file.FileName, ex.Message); - } - - return false; - } - /// /// Parses the Wasm file to validate the header and find the buildId custom section. /// diff --git a/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs b/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs index 89c2150fe8..3254fcc238 100644 --- a/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs +++ b/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs @@ -571,7 +571,7 @@ private void WasmFileKeyGeneratorInternal(bool fileGenerator) IEnumerable identityKey = generator.GetKeys(KeyTypeFlags.IdentityKey); Assert.True(identityKey.Count() == 1); - Assert.True(identityKey.First().Index == "test_module_symbols.wasm.s/deadbeef0123456789abcdeffedcba98/test_module_symbols.wasm.s"); + Assert.True(identityKey.First().Index == "test_module_symbols.wasm/deadbeef0123456789abcdeffedcba98/test_module_symbols.wasm"); IEnumerable symbolKey = generator.GetKeys(KeyTypeFlags.SymbolKey); Assert.True(!symbolKey.Any()); From c451fab8ea187d41fc5789086f98f7f9947c3f2e Mon Sep 17 00:00:00 2001 From: Paolo Severini Date: Tue, 26 May 2026 19:57:17 +0200 Subject: [PATCH 5/7] Address review comments --- .../KeyGenerators/WasmFileKeyGenerator.cs | 27 ++++++++++++++---- .../KeyGeneratorTests.cs | 14 +++++++++ .../test_module_long_section_name.wasm | Bin 0 -> 141 bytes 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module_long_section_name.wasm diff --git a/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs index 59841c02da..a90b58de52 100644 --- a/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs +++ b/src/Microsoft.SymbolStore/KeyGenerators/WasmFileKeyGenerator.cs @@ -49,6 +49,11 @@ public WasmFileKeyGenerator(ITracer tracer, SymbolStoreFile file) } public override bool IsValid() + { + return HasIndexableWasmBuildId(); + } + + public bool HasIndexableWasmBuildId() { ParseWasmFile(); return _isValid; @@ -91,9 +96,10 @@ private void ParseWasmFile() _parsed = true; _isValid = false; + Stream stream = _file.Stream; + long prevPosition = stream.Position; try { - Stream stream = _file.Stream; stream.Position = 0; // Validate magic number @@ -124,7 +130,7 @@ private void ParseWasmFile() } } - // Scan sections for the buildId custom section + // Scan sections for the build_id custom section while (stream.Position < stream.Length) { int sectionId = stream.ReadByte(); @@ -144,7 +150,7 @@ private void ParseWasmFile() if (sectionId == CustomSectionId) { - string name = ReadWasmString(stream); + string name = ReadWasmString(stream, sectionEnd); if (name == BuildIdSectionName) { // The remainder of the section payload is the build ID @@ -168,6 +174,10 @@ private void ParseWasmFile() { Tracer.Verbose("Error parsing Wasm file {0}: {1}", _file.FileName, ex.Message); } + finally + { + stream.Position = prevPosition; + } } /// @@ -202,17 +212,24 @@ private static uint ReadLEB128Unsigned(Stream stream) return result; } + /// + /// Maximum section name length we'll read. Names longer than this are + /// skipped since they cannot match the sections we're looking for. + /// + private const int MaxSectionNameLength = 64; + /// /// Reads a Wasm string (LEB128 length prefix followed by UTF-8 bytes). + /// Returns null if the string is too long or extends past the section boundary. /// - private static string ReadWasmString(Stream stream) + private static string ReadWasmString(Stream stream, long sectionEnd) { uint length = ReadLEB128Unsigned(stream); if (length == 0) { return string.Empty; } - if (length > int.MaxValue) + if (length > MaxSectionNameLength || stream.Position + length > sectionEnd) { return null; } diff --git a/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs b/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs index 3254fcc238..428893488d 100644 --- a/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs +++ b/src/tests/Microsoft.SymbolStore.UnitTests/KeyGeneratorTests.cs @@ -589,6 +589,20 @@ private void WasmFileKeyGeneratorInternal(bool fileGenerator) IEnumerable identityKey = generator.GetKeys(KeyTypeFlags.IdentityKey); Assert.True(!identityKey.Any()); } + + // Test 4: Wasm file with a custom section name longer than 64 chars before build_id + const string WasmLongNamePath = "TestBinaries/test_module_long_section_name.wasm"; + using (Stream stream = File.OpenRead(WasmLongNamePath)) + { + var file = new SymbolStoreFile(stream, WasmLongNamePath); + var generator = new WasmFileKeyGenerator(_tracer, file); + + Assert.True(generator.IsValid()); + + IEnumerable identityKey = generator.GetKeys(KeyTypeFlags.IdentityKey); + Assert.True(identityKey.Count() == 1); + Assert.True(identityKey.First().Index == "test_module_long_section_name.wasm/deadbeef0123456789abcdeffedcba98/test_module_long_section_name.wasm"); + } } } } diff --git a/src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module_long_section_name.wasm b/src/tests/Microsoft.SymbolStore.UnitTests/TestBinaries/test_module_long_section_name.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3ccb4188e4608d693d298b88e3837a15ee01015e GIT binary patch literal 141 zcmZQbEY4+Q0D_E^L^=RQCT0dnj-=AeoRs*?lzVIUy=PQ*P48TN_Wi#*yJi3Yn))?* literal 0 HcmV?d00001 From dfaab7a8b973fb265b7489b46d14b8e9cbc1f5f5 Mon Sep 17 00:00:00 2001 From: Paolo Severini Date: Thu, 4 Jun 2026 19:04:00 +0200 Subject: [PATCH 6/7] Update documentation --- documentation/symbols/SSQP_Key_Conventions.md | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/documentation/symbols/SSQP_Key_Conventions.md b/documentation/symbols/SSQP_Key_Conventions.md index 6a4c222d1e..1bebb40100 100644 --- a/documentation/symbols/SSQP_Key_Conventions.md +++ b/documentation/symbols/SSQP_Key_Conventions.md @@ -195,12 +195,26 @@ Example: ### WASM (WebAssembly) Modules -WebAssembly symbols, which can be used by browser developer tools to provide source-level debugging experiences, are based on the DWARF format. These are indexed by their DWARF Build ID -(built with `-Wl,--build-id` arguments) and the name of the module being debugged via the -[buildId property](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#event-scriptParsed), and the symbol file itself is suffixed with `.s` to disambiguate from the WASM file. +WebAssembly symbols, which can be used by browser developer tools to provide source-level debugging experiences, are based on the DWARF format. These are indexed by the Build ID stored in the `build_id` custom section of the Wasm binary. The Build ID is a byte sequence typically produced by the linker (e.g., with `-Wl,--build-id` arguments). Both the module and its corresponding symbol file contain the same `build_id` section, allowing them to be matched. + +The key uses the actual filename of the file being indexed. For split symbol files, toolchains such as Emscripten produce a separate file (e.g., `foo.debug.wasm`) that contains the DWARF debug sections (`.debug_info`, `.debug_line`, etc.) stripped from the original module. + +The final key is formatted as follows: + +`//` + +Example (module): **File name:** `main.wasm` -**Build ID of file:** `e3b0c44298fc1c149afbf4c8996fb92427ae41e4` +**Build ID bytes:** `0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4` + +**Lookup key:** `main.wasm/e3b0c44298fc1c149afbf4c8996fb92427ae41e4/main.wasm` + +Example (split symbol file): + +**File name:** `main.debug.wasm` + +**Build ID bytes:** `(same as module)` -**Lookup key:**: `main.wasm.s/e3b0c44298fc1c149afbf4c8996fb92427ae41e4/main.wasm.s` +**Lookup key:** `main.debug.wasm/e3b0c44298fc1c149afbf4c8996fb92427ae41e4/main.debug.wasm` From 7f8e140bc7f820b084dc94b7f9bad1f2593ae3e8 Mon Sep 17 00:00:00 2001 From: Paolo Severini Date: Wed, 17 Jun 2026 16:17:55 +0200 Subject: [PATCH 7/7] Trigger build pipeline