Skip to content

Commit a4f221f

Browse files
Symbolication for Single File Apps (getsentry#2425)
Added support to capture Symbolication for Single File Apps targeting net5.0 and later
1 parent f2fe486 commit a4f221f

File tree

14 files changed

+678
-94
lines changed

14 files changed

+678
-94
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#6230)
1919
- [diff](https://github.com/getsentry/sentry-java/compare/6.22.0...6.23.0)
2020

21+
### Features
22+
23+
- Symbolication for Single File Apps ([#2425](https://github.com/getsentry/sentry-dotnet/pull/2425))
24+
2125
## 3.33.1
2226

2327
### Fixes

GlobalUsings.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
global using System.Diagnostics.CodeAnalysis;
1111
global using System.Globalization;
1212
global using System.IO.Compression;
13+
global using System.IO.MemoryMappedFiles;
1314
global using System.Net;
1415
global using System.Net.Http.Headers;
1516
global using System.Net.NetworkInformation;
@@ -22,8 +23,8 @@
2223
global using System.Runtime.Serialization;
2324
global using System.Security;
2425
global using System.Security.Authentication;
25-
global using System.Security.Principal;
2626
global using System.Security.Claims;
2727
global using System.Security.Cryptography;
28+
global using System.Security.Principal;
2829
global using System.Text;
2930
global using System.Text.RegularExpressions;

Sentry.sln

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".root", ".root", "{9D7E2F87
2828
CONTRIBUTING.md = CONTRIBUTING.md
2929
Directory.Build.props = Directory.Build.props
3030
Directory.Build.targets = Directory.Build.targets
31-
README.md = README.md
3231
global.json = global.json
3332
GlobalUsings.cs = GlobalUsings.cs
33+
README.md = README.md
3434
EndProjectSection
3535
EndProject
3636
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{77454495-55EE-4B40-A089-71B9E8F82E89}"
@@ -134,31 +134,33 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.DiagnosticSource", "
134134
EndProject
135135
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.DiagnosticSource.Tests", "test\Sentry.DiagnosticSource.Tests\Sentry.DiagnosticSource.Tests.csproj", "{D870B028-16ED-4551-8B0F-5529479D04C9}"
136136
EndProject
137-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.DiagnosticSource.IntegrationTests", "test\Sentry.DiagnosticSource.IntegrationTests\Sentry.DiagnosticSource.IntegrationTests.csproj", "{F8120B9C-D4CA-43DA-B5E1-1CFBA7C36E3B}"
137+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.DiagnosticSource.IntegrationTests", "test\Sentry.DiagnosticSource.IntegrationTests\Sentry.DiagnosticSource.IntegrationTests.csproj", "{F8120B9C-D4CA-43DA-B5E1-1CFBA7C36E3B}"
138138
EndProject
139139
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Maui", "src\Sentry.Maui\Sentry.Maui.csproj", "{FFFC74C5-680B-43E3-9C42-A7A23B589CB6}"
140140
EndProject
141141
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Maui.Tests", "test\Sentry.Maui.Tests\Sentry.Maui.Tests.csproj", "{143076C0-8D6B-4054-9F45-06B21655F417}"
142142
EndProject
143-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Testing.CrashableApp", "test\Sentry.Testing.CrashableApp\Sentry.Testing.CrashableApp.csproj", "{DA3CECBB-83BE-441A-852C-077809C48307}"
143+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Testing.CrashableApp", "test\Sentry.Testing.CrashableApp\Sentry.Testing.CrashableApp.csproj", "{DA3CECBB-83BE-441A-852C-077809C48307}"
144144
EndProject
145-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Samples.AspNetCore.Blazor.Server", "samples\Sentry.Samples.AspNetCore.Blazor.Server\Sentry.Samples.AspNetCore.Blazor.Server.csproj", "{70066C6C-0A18-4322-A02A-9A0DFE59C02B}"
145+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Samples.AspNetCore.Blazor.Server", "samples\Sentry.Samples.AspNetCore.Blazor.Server\Sentry.Samples.AspNetCore.Blazor.Server.csproj", "{70066C6C-0A18-4322-A02A-9A0DFE59C02B}"
146146
EndProject
147-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AspNetCore.TestUtils", "test\Sentry.AspNetCore.TestUtils\Sentry.AspNetCore.TestUtils.csproj", "{C96CB65D-3D2D-404E-85C0-69A3FC03B48F}"
147+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.AspNetCore.TestUtils", "test\Sentry.AspNetCore.TestUtils\Sentry.AspNetCore.TestUtils.csproj", "{C96CB65D-3D2D-404E-85C0-69A3FC03B48F}"
148148
EndProject
149-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Android.AssemblyReader", "src\Sentry.Android.AssemblyReader\Sentry.Android.AssemblyReader.csproj", "{ED5E4F7E-8267-4F3C-BD2A-779AC9BF3D7C}"
149+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Android.AssemblyReader", "src\Sentry.Android.AssemblyReader\Sentry.Android.AssemblyReader.csproj", "{ED5E4F7E-8267-4F3C-BD2A-779AC9BF3D7C}"
150150
EndProject
151151
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Profiling", "src\Sentry.Profiling\Sentry.Profiling.csproj", "{BD6CEF44-E05E-4C22-8D2F-0558A93DD2D6}"
152152
EndProject
153153
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Profiling.Tests", "test\Sentry.Profiling.Tests\Sentry.Profiling.Tests.csproj", "{2E750A7C-561D-4959-B967-042755139D84}"
154154
EndProject
155155
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Samples.Console.Profiling", "samples\Sentry.Samples.Console.Profiling\Sentry.Samples.Console.Profiling.csproj", "{0F84C0BB-FDD4-43A9-B594-923EB10C8E3F}"
156156
EndProject
157-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AzureFunctions.Worker", "src\Sentry.AzureFunctions.Worker\Sentry.AzureFunctions.Worker.csproj", "{203C4556-16DE-46CE-9FAF-C93D8B30D04A}"
157+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.AzureFunctions.Worker", "src\Sentry.AzureFunctions.Worker\Sentry.AzureFunctions.Worker.csproj", "{203C4556-16DE-46CE-9FAF-C93D8B30D04A}"
158158
EndProject
159-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Samples.AzureFunctions.Worker", "samples\Sentry.Samples.AzureFunctions.Worker\Sentry.Samples.AzureFunctions.Worker.csproj", "{243B75FF-1501-4DB7-B933-EE43D960EB90}"
159+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.Samples.AzureFunctions.Worker", "samples\Sentry.Samples.AzureFunctions.Worker\Sentry.Samples.AzureFunctions.Worker.csproj", "{243B75FF-1501-4DB7-B933-EE43D960EB90}"
160160
EndProject
161-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AzureFunctions.Worker.Tests", "test\Sentry.AzureFunctions.Worker.Tests\Sentry.AzureFunctions.Worker.Tests.csproj", "{8639DB06-1F74-4890-8974-613305C1B6C5}"
161+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentry.AzureFunctions.Worker.Tests", "test\Sentry.AzureFunctions.Worker.Tests\Sentry.AzureFunctions.Worker.Tests.csproj", "{8639DB06-1F74-4890-8974-613305C1B6C5}"
162+
EndProject
163+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SingleFileTestApp", "test\SingleFileTestApp\SingleFileTestApp.csproj", "{08C99C2F-08D8-44A3-981F-768DC84DCEC7}"
162164
EndProject
163165
Global
164166
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -334,10 +336,10 @@ Global
334336
{143076C0-8D6B-4054-9F45-06B21655F417}.Debug|Any CPU.Build.0 = Debug|Any CPU
335337
{143076C0-8D6B-4054-9F45-06B21655F417}.Release|Any CPU.ActiveCfg = Release|Any CPU
336338
{143076C0-8D6B-4054-9F45-06B21655F417}.Release|Any CPU.Build.0 = Release|Any CPU
337-
{DA3CECBB-83BE-441A-852C-077809C48307}.Release|Any CPU.ActiveCfg = Release|Any CPU
338-
{DA3CECBB-83BE-441A-852C-077809C48307}.Release|Any CPU.Build.0 = Release|Any CPU
339339
{DA3CECBB-83BE-441A-852C-077809C48307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
340340
{DA3CECBB-83BE-441A-852C-077809C48307}.Debug|Any CPU.Build.0 = Debug|Any CPU
341+
{DA3CECBB-83BE-441A-852C-077809C48307}.Release|Any CPU.ActiveCfg = Release|Any CPU
342+
{DA3CECBB-83BE-441A-852C-077809C48307}.Release|Any CPU.Build.0 = Release|Any CPU
341343
{70066C6C-0A18-4322-A02A-9A0DFE59C02B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
342344
{70066C6C-0A18-4322-A02A-9A0DFE59C02B}.Debug|Any CPU.Build.0 = Debug|Any CPU
343345
{70066C6C-0A18-4322-A02A-9A0DFE59C02B}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -374,6 +376,10 @@ Global
374376
{8639DB06-1F74-4890-8974-613305C1B6C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
375377
{8639DB06-1F74-4890-8974-613305C1B6C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
376378
{8639DB06-1F74-4890-8974-613305C1B6C5}.Release|Any CPU.Build.0 = Release|Any CPU
379+
{08C99C2F-08D8-44A3-981F-768DC84DCEC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
380+
{08C99C2F-08D8-44A3-981F-768DC84DCEC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
381+
{08C99C2F-08D8-44A3-981F-768DC84DCEC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
382+
{08C99C2F-08D8-44A3-981F-768DC84DCEC7}.Release|Any CPU.Build.0 = Release|Any CPU
377383
EndGlobalSection
378384
GlobalSection(SolutionProperties) = preSolution
379385
HideSolutionNode = FALSE
@@ -434,6 +440,7 @@ Global
434440
{203C4556-16DE-46CE-9FAF-C93D8B30D04A} = {AF6AF4C7-8AA2-4D59-8064-2D79560904EB}
435441
{243B75FF-1501-4DB7-B933-EE43D960EB90} = {77454495-55EE-4B40-A089-71B9E8F82E89}
436442
{8639DB06-1F74-4890-8974-613305C1B6C5} = {83263231-1A2A-4733-B759-EEFF14E8C5D5}
443+
{08C99C2F-08D8-44A3-981F-768DC84DCEC7} = {83263231-1A2A-4733-B759-EEFF14E8C5D5}
437444
EndGlobalSection
438445
GlobalSection(ExtensibilityGlobals) = postSolution
439446
SolutionGuid = {0C652B1A-DF72-4EE5-A98B-194FE2C054F6}

src/Sentry/Internal/DebugStackTrace.cs

Lines changed: 38 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Sentry.Internal.Extensions;
22
using Sentry.Extensibility;
3+
using Sentry.Internal.ILSpy;
34

45
namespace Sentry.Internal;
56

@@ -377,14 +378,23 @@ private static void DemangleLambdaReturnType(SentryStackFrame frame)
377378
}
378379
}
379380

380-
private static PEReader? TryReadAssembly(string assemblyName, SentryOptions options)
381+
private static PEReader? TryReadAssemblyFromDisk(Module module, SentryOptions options, out string? assemblyName)
381382
{
383+
assemblyName = module.FullyQualifiedName;
382384
if (options.AssemblyReader is { } reader)
383385
{
384386
return reader.Invoke(assemblyName);
385387
}
386388

387-
return File.Exists(assemblyName) ? new PEReader(File.OpenRead(assemblyName)) : null;
389+
try
390+
{
391+
var assembly = File.OpenRead(assemblyName);
392+
return new PEReader(assembly);
393+
}
394+
catch (Exception)
395+
{
396+
return null;
397+
}
388398
}
389399

390400
private int? AddDebugImage(Module module)
@@ -412,94 +422,40 @@ private static void DemangleLambdaReturnType(SentryStackFrame frame)
412422

413423
internal static DebugImage? GetDebugImage(Module module, SentryOptions options)
414424
{
415-
var assemblyName = module.FullyQualifiedName;
416-
using var peReader = TryReadAssembly(assemblyName, options);
417-
if (peReader is null)
418-
{
419-
options.LogDebug("Skipping debug image for module '{0}' because assembly wasn't found: '{1}'",
420-
module.Name, assemblyName);
421-
return null;
422-
}
423-
424-
var headers = peReader.PEHeaders;
425-
var codeId = headers.PEHeader is { } peHeader
426-
? $"{headers.CoffHeader.TimeDateStamp:X8}{peHeader.SizeOfImage:x}"
427-
: null;
428-
429-
string? debugId = null;
430-
string? debugFile = null;
431-
string? debugChecksum = null;
432-
433-
var debugDirectoryEntries = peReader.ReadDebugDirectory();
434-
435-
foreach (var entry in debugDirectoryEntries)
425+
// Try to get it from disk (most common use case)
426+
var moduleName = module.GetNameOrScopeName();
427+
using var peDiskReader = TryReadAssemblyFromDisk(module, options, out var assemblyName);
428+
if (peDiskReader is not null)
436429
{
437-
switch (entry.Type)
438-
{
439-
case DebugDirectoryEntryType.PdbChecksum:
440-
{
441-
var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry);
442-
var checksumHex = checksum.Checksum.AsSpan().ToHexString();
443-
debugChecksum = $"{checksum.AlgorithmName}:{checksumHex}";
444-
break;
445-
}
446-
447-
case DebugDirectoryEntryType.CodeView:
448-
{
449-
var codeView = peReader.ReadCodeViewDebugDirectoryData(entry);
450-
debugFile = codeView.Path;
451-
452-
// Specification:
453-
// https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md#codeview-debug-directory-entry-type-2
454-
//
455-
// See also:
456-
// https://learn.microsoft.com/dotnet/csharp/language-reference/compiler-options/code-generation#debugtype
457-
//
458-
// Note: Matching PDB ID is stored in the #Pdb stream of the .pdb file.
459-
460-
if (entry.IsPortableCodeView)
461-
{
462-
// Portable PDB Format
463-
// Version Major=any, Minor=0x504d
464-
debugId = $"{codeView.Guid}-{entry.Stamp:x8}";
465-
}
466-
else
467-
{
468-
// Full PDB Format (Windows only)
469-
// Version Major=0, Minor=0
470-
debugId = $"{codeView.Guid}-{codeView.Age}";
471-
}
472-
473-
break;
474-
}
475-
}
476-
477-
if (debugId != null && debugChecksum != null)
430+
if (peDiskReader.TryGetPEDebugImageData().ToDebugImage(assemblyName, module.ModuleVersionId) is not { } debugImage)
478431
{
479-
// No need to keep looking, once we have both.
480-
break;
432+
options.LogInfo("Skipping debug image for module '{0}' because the Debug ID couldn't be determined", moduleName);
433+
return null;
481434
}
482-
}
483435

484-
if (debugId == null)
485-
{
486-
options.LogInfo("Skipping debug image for module '{0}' because the Debug ID couldn't be determined", module.Name);
487-
return null;
436+
options.LogDebug("Got debug image for '{0}' having Debug ID: {1}", moduleName, debugImage.DebugId);
437+
return debugImage;
488438
}
489439

490-
var debugImage = new DebugImage
440+
#if NET5_0_OR_GREATER && PLATFORM_NEUTRAL
441+
// Maybe we're dealing with a single file assembly
442+
// https://github.com/getsentry/sentry-dotnet/issues/2362
443+
if (SingleFileApp.MainModule.IsBundle())
491444
{
492-
Type = "pe_dotnet",
493-
CodeId = codeId,
494-
CodeFile = assemblyName,
495-
DebugId = debugId,
496-
DebugChecksum = debugChecksum,
497-
DebugFile = debugFile,
498-
ModuleVersionId = module.ModuleVersionId,
499-
};
445+
if (SingleFileApp.MainModule?.GetDebugImage(module) is not { } embeddedDebugImage)
446+
{
447+
options.LogInfo("Skipping embedded debug image for module '{0}' because the Debug ID couldn't be determined", moduleName);
448+
return null;
449+
}
500450

501-
options.LogDebug("Got debug image for '{0}' having Debug ID: {1}", module.Name, debugId);
451+
options.LogDebug("Got embedded debug image for '{0}' having Debug ID: {1}", moduleName, embeddedDebugImage.DebugId);
452+
return embeddedDebugImage;
453+
}
454+
#endif
502455

503-
return debugImage;
456+
// Finally, admit defeat
457+
options.LogDebug("Skipping debug image for module '{0}' because assembly wasn't found: '{1}'",
458+
moduleName, assemblyName);
459+
return null;
504460
}
505461
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using Sentry.Extensibility;
2+
3+
namespace Sentry.Internal.Extensions;
4+
5+
internal static class PEReaderExtensions
6+
{
7+
public static PEDebugImageData? TryGetPEDebugImageData(this PEReader peReader)
8+
{
9+
try
10+
{
11+
return peReader.GetPEDebugImageData();
12+
}
13+
catch
14+
{
15+
return null;
16+
}
17+
}
18+
19+
private static PEDebugImageData GetPEDebugImageData(this PEReader peReader)
20+
{
21+
var headers = peReader.PEHeaders;
22+
var codeId = headers.PEHeader is { } peHeader
23+
? $"{headers.CoffHeader.TimeDateStamp:X8}{peHeader.SizeOfImage:x}"
24+
: null;
25+
26+
string? debugId = null;
27+
string? debugFile = null;
28+
string? debugChecksum = null;
29+
30+
var debugDirectoryEntries = peReader.ReadDebugDirectory();
31+
32+
foreach (var entry in debugDirectoryEntries)
33+
{
34+
switch (entry.Type)
35+
{
36+
case DebugDirectoryEntryType.PdbChecksum:
37+
{
38+
var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry);
39+
var checksumHex = checksum.Checksum.AsSpan().ToHexString();
40+
debugChecksum = $"{checksum.AlgorithmName}:{checksumHex}";
41+
break;
42+
}
43+
44+
case DebugDirectoryEntryType.CodeView:
45+
{
46+
var codeView = peReader.ReadCodeViewDebugDirectoryData(entry);
47+
debugFile = codeView.Path;
48+
49+
// Specification:
50+
// https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md#codeview-debug-directory-entry-type-2
51+
//
52+
// See also:
53+
// https://learn.microsoft.com/dotnet/csharp/language-reference/compiler-options/code-generation#debugtype
54+
//
55+
// Note: Matching PDB ID is stored in the #Pdb stream of the .pdb file.
56+
57+
if (entry.IsPortableCodeView)
58+
{
59+
// Portable PDB Format
60+
// Version Major=any, Minor=0x504d
61+
debugId = $"{codeView.Guid}-{entry.Stamp:x8}";
62+
}
63+
else
64+
{
65+
// Full PDB Format (Windows only)
66+
// Version Major=0, Minor=0
67+
debugId = $"{codeView.Guid}-{codeView.Age}";
68+
}
69+
70+
break;
71+
}
72+
}
73+
74+
if (debugId != null && debugChecksum != null)
75+
{
76+
// No need to keep looking, once we have both.
77+
break;
78+
}
79+
}
80+
81+
return new PEDebugImageData
82+
{
83+
CodeId = codeId,
84+
DebugId = debugId,
85+
DebugChecksum = debugChecksum,
86+
DebugFile = debugFile
87+
};
88+
}
89+
}
90+
91+
/// <summary>
92+
/// The subset of information about a DebugImage that we can obtain from a PE file.
93+
/// This rest of the information is obtained from the Module.
94+
/// </summary>
95+
internal sealed class PEDebugImageData
96+
{
97+
public string Type => "pe_dotnet";
98+
public string? ImageAddress { get; set; }
99+
public long? ImageSize { get; set; }
100+
public string? DebugId { get; set; }
101+
public string? DebugChecksum { get; set; }
102+
public string? DebugFile { get; set; }
103+
public string? CodeId { get; set; }
104+
}
105+
106+
internal static class PEDebugImageDataExtensions
107+
{
108+
internal static DebugImage? ToDebugImage(this PEDebugImageData? imageData, string? codeFile, Guid? moduleVersionId)
109+
{
110+
return imageData?.DebugId == null
111+
? null
112+
: new DebugImage
113+
{
114+
Type = imageData.Type,
115+
CodeId = imageData.CodeId,
116+
CodeFile = codeFile,
117+
DebugId = imageData.DebugId,
118+
DebugChecksum = imageData.DebugChecksum,
119+
DebugFile = imageData.DebugFile,
120+
ModuleVersionId = moduleVersionId
121+
};
122+
}
123+
}

0 commit comments

Comments
 (0)