Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add Portable PDB support to Raygun4Net and Raygun4NetCore #528

Merged
merged 16 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
39 changes: 26 additions & 13 deletions Mindscape.Raygun4Net.Core/Builders/RaygunErrorMessageBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,31 +105,44 @@ public static RaygunErrorStackTraceLineMessage[] BuildStackTrace(StackTrace stac
return lines.ToArray();
}

foreach (StackFrame frame in frames)
foreach (var frame in frames)
{
MethodBase method = frame.GetMethod();
var method = frame.GetMethod();

if (method != null)
{
int lineNumber = frame.GetFileLineNumber();
string methodName = null;
string file = null;
string className = null;
var lineNumber = 0;
var ilOffset = StackFrame.OFFSET_UNKNOWN;
var methodToken = StackFrame.OFFSET_UNKNOWN;

if (lineNumber == 0)
try
{
lineNumber = frame.GetILOffset();
file = frame.GetFileName();
lineNumber = frame.GetFileLineNumber();
methodName = GenerateMethodName(method);
className = method.ReflectedType != null ? method.ReflectedType.FullName : "(unknown)";
ilOffset = frame.GetILOffset();

// This might fail in medium trust environments or for array methods,
// so don't crash the entire send process - just move on with what we have
methodToken = method.MetadataToken;
}
catch (Exception ex)
{
Debug.WriteLine("Exception retrieving stack frame details: {0}", ex);
}

var methodName = GenerateMethodName(method);

string file = frame.GetFileName();

string className = method.ReflectedType != null ? method.ReflectedType.FullName : "(unknown)";

var line = new RaygunErrorStackTraceLineMessage
{
FileName = file,
LineNumber = lineNumber,
MethodName = methodName,
ClassName = className
ClassName = className,
ILOffset = ilOffset,
MethodToken = methodToken
};

lines.Add(line);
Expand All @@ -139,4 +152,4 @@ public static RaygunErrorStackTraceLineMessage[] BuildStackTrace(StackTrace stac
return lines.ToArray();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ public class RaygunErrorStackTraceLineMessage

public string Raw { get; set; }

public int ILOffset { get; set; }

public int MethodToken { get; set; }

public override string ToString()
{
// This exists because Reflection in Xamarin can't seem to obtain the Getter methods unless the getter is used somewhere in the code.
// The getter of all properties is required to serialize the Raygun messages to JSON.
return string.Format("[RaygunErrorStackTraceLineMessage: LineNumber={0}, ClassName={1}, FileName={2}, MethodName={3}, Raw={4}]", LineNumber, ClassName, FileName, MethodName, Raw);
return string.Format("[RaygunErrorStackTraceLineMessage: LineNumber={0}, ClassName={1}, FileName={2}, MethodName={3}, Raw={4}, ILOffset={5}, MethodToken={6}]", LineNumber, ClassName, FileName, MethodName, Raw, ILOffset, MethodToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Text;
using Mindscape.Raygun4Net.Diagnostics;

namespace Mindscape.Raygun4Net
{
public class RaygunErrorMessageBuilder
{
private static readonly ConcurrentDictionary<string, PdbDebugInformation> DebugInformationCache = new();
public static Func<string, PEReader> AssemblyReaderProvider { get; set; } = PortableExecutableReaderExtensions.GetFileSystemPEReader;
Copy link
Contributor Author

@xenolightning xenolightning May 19, 2024

Choose a reason for hiding this comment

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

Intention here is that this is replaced with an android version for MAUI.

This needs to be bundled inside the raygun4maui release though, as our core library doesn't have platform targets


protected static string FormatTypeName(Type type, bool fullName)
{
string name = fullName ? type.FullName : type.Name;
Expand All @@ -24,6 +30,7 @@ protected static string FormatTypeName(Type type, bool fullName)
{
stringBuilder.Append(FormatTypeName(t, false)).Append(",");
}

stringBuilder.Remove(stringBuilder.Length - 1, 1);
stringBuilder.Append(">");

Expand Down Expand Up @@ -51,31 +58,50 @@ public static RaygunErrorStackTraceLineMessage[] BuildStackTrace(StackTrace stac
return lines.ToArray();
}

foreach (StackFrame frame in frames)
foreach (var frame in frames)
{
MethodBase method = frame.GetMethod();
var method = frame.GetMethod();

if (method != null)
{
int lineNumber = frame.GetFileLineNumber();

if (lineNumber == 0)
string methodName = null;
string file = null;
string className = null;
var lineNumber = 0;
var ilOffset = StackFrame.OFFSET_UNKNOWN;
var methodToken = StackFrame.OFFSET_UNKNOWN;
PdbDebugInformation debugInfo = null;

try
{
lineNumber = frame.GetILOffset();
file = frame.GetFileName();
lineNumber = frame.GetFileLineNumber();
methodName = GenerateMethodName(method);
className = method.ReflectedType != null ? method.ReflectedType.FullName : "(unknown)";
ilOffset = frame.GetILOffset();
debugInfo = TryGetDebugInformation(method.Module.Name);

// This might fail in medium trust environments or for array methods,
// so don't crash the entire send process - just move on with what we have
methodToken = method.MetadataToken;
}
catch (Exception ex)
{
Debug.WriteLine("Exception retrieving stack frame details: {0}", ex);
}

var methodName = GenerateMethodName(method);

string file = frame.GetFileName();

string className = method.DeclaringType != null ? method.DeclaringType.FullName : "(unknown)";

var line = new RaygunErrorStackTraceLineMessage
{
FileName = file,
LineNumber = lineNumber,
MethodName = methodName,
ClassName = className
ClassName = className,
ILOffset = ilOffset,
MethodToken = methodToken,
PdbChecksum = debugInfo?.Checksum,
PdbSignature = debugInfo?.Signature,
PdbFile = debugInfo?.File,
PdbTimestamp = debugInfo?.Timestamp
};

lines.Add(line);
Expand Down Expand Up @@ -173,9 +199,7 @@ public static RaygunErrorMessage Build(Exception exception)
message.Data = data;
}

AggregateException ae = exception as AggregateException;

if (ae != null && ae.InnerExceptions != null)
if (exception is AggregateException ae)
{
message.InnerErrors = new RaygunErrorMessage[ae.InnerExceptions.Count];
int index = 0;
Expand All @@ -193,5 +217,31 @@ public static RaygunErrorMessage Build(Exception exception)

return message;
}

private static PdbDebugInformation TryGetDebugInformation(string moduleName)
{
if (DebugInformationCache.TryGetValue(moduleName, out var cachedInfo))
{
return cachedInfo;
}

try
{
// Attempt to read out the Debug Info from the PE
var peReader = AssemblyReaderProvider(moduleName);

if (peReader.TryGetDebugInformation(out var debugInfo))
{
DebugInformationCache.TryAdd(moduleName, debugInfo);
return debugInfo;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Could not load debug information: {ex}");
}

return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as the other implementation.

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;

namespace Mindscape.Raygun4Net.Diagnostics;

internal sealed class PdbDebugInformation
{
/// <summary>
/// The signature of the PE and PDB linking them together - usually a GUID
/// </summary>
public string Signature { get; set; }

/// <summary>
/// Checksum of the PE & PDB. Format: {algorithm}:{hash:X}
/// </summary>
public string Checksum { get; set; }

/// <summary>
/// The full location of the PDB at build time
/// </summary>
public string File { get; set; }

/// <summary>
/// The generated Timestamp of the code at build time stored as hex
/// </summary>
public string Timestamp { get; set; }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#nullable enable

using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;

namespace Mindscape.Raygun4Net.Diagnostics;

internal static class PortableExecutableReaderExtensions
{
public static PEReader? GetFileSystemPEReader(string moduleName)
{
try
{
// Read into memory to avoid any premature stream closures
var bytes = ImmutableArray.Create(File.ReadAllBytes(moduleName));
return new PEReader(bytes);
}
catch (Exception ex)
{
Debug.WriteLine($"Could not open module [{moduleName}] from disk: {ex}");
return null;
}
}

public static bool TryGetDebugInformation(this PEReader peReader, out PdbDebugInformation? debugInformation)
{
try
{
debugInformation = GetDebugInformation(peReader);
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"Error reading PE Debug Data: {ex}");
}

debugInformation = null;
return false;
}

private static PdbDebugInformation GetDebugInformation(this PEReader peReader)
{
var debugInfo = new PdbDebugInformation
{
Timestamp = $"{peReader.PEHeaders.CoffHeader.TimeDateStamp:X8}"
};

foreach (var entry in peReader.ReadDebugDirectory())
{
if (entry.Type == DebugDirectoryEntryType.CodeView)
{
// Read the CodeView data
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);

debugInfo.File = codeViewData.Path;
debugInfo.Signature = codeViewData.Guid.ToString();
}

if (entry.Type == DebugDirectoryEntryType.PdbChecksum)
{
var checksumEntry = peReader.ReadPdbChecksumDebugDirectoryData(entry);
var checksumHex = BitConverter.ToString(checksumEntry.Checksum.ToArray()).Replace("-", "").ToUpperInvariant();
debugInfo.Checksum = $"{checksumEntry.AlgorithmName}:{checksumHex}";
}
}

return debugInfo;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,17 @@ public class RaygunErrorStackTraceLineMessage
public string FileName { get; set; }

public string MethodName { get; set; }

public int ILOffset { get; set; }

public int MethodToken { get; set; }

public string PdbSignature { get; set; }

public string PdbChecksum { get; set; }

public string PdbFile { get; set; }

public string PdbTimestamp { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@
<None Include="..\LICENSE" Pack="true" PackagePath=""/>
<None Include="..\128x128-transparent.png" Pack="true" PackagePath="\"/>
</ItemGroup>


<ItemGroup>
<PackageReference Include="System.Reflection.Metadata" Version="6.0.1" />
xenolightning marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>
</Project>