Skip to content

Commit

Permalink
Added native code support
Browse files Browse the repository at this point in the history
  • Loading branch information
Aragas committed Sep 21, 2023
1 parent d963396 commit 6eaadd7
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<PropertyGroup>
<PackageId>BUTR.CrashReport.Bannerlord.Source</PackageId>
<Title>BUTR.CrashReport.Bannerlord.Source</Title>
<Description>.</Description>
<Description>Source code for creating the crash report model and render it as HTML</Description>
<DevelopmentDependency>true</DevelopmentDependency>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>icon.png</PackageIcon>
Expand Down Expand Up @@ -42,7 +42,7 @@
<ItemGroup>
<PackageReference Include="NuGetizer" Version="1.1.1" Pack="false" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Ben.Demystifier" Version="0.4.1" Pack="true" />
</ItemGroup>
Expand All @@ -51,7 +51,7 @@
<PackageReference Include="Harmony.Extensions" Version="3.2.0.77" Pack="true" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="Lib.Harmony" Version="2.2.2" Pack="true" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Bannerlord.ReferenceAssemblies.Core" Version="1.0.0.*-*" Pack="false" PrivateAssets="all" />
<PackageReference Include="Bannerlord.ModuleManager.Source" Version="5.0.209" Pack="true" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
Expand Down
3 changes: 3 additions & 0 deletions src/BUTR.CrashReport.Bannerlord.Source/CrashReportCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ private static ICollection<EnhancedStacktraceFrameModel> GetEnhancedStacktrace(C
Method = method.Method.DeclaringType is not null ? $"{method.Method.DeclaringType.FullName}.{method.Method.Name}" : method.Method.Name,
MethodFullName = method.Method.FullDescription(),
MethodParameters = method.Method.GetParameters().Select(x => x.ParameterType.FullName).ToArray(),
NativeInstructions = method.NativeInstructions,
CilInstructions = method.CilInstructions,
});
}
Expand All @@ -189,10 +190,12 @@ private static ICollection<EnhancedStacktraceFrameModel> GetEnhancedStacktrace(C
Method = entry.Method.DeclaringType is not null ? $"{entry.Method.DeclaringType.FullName}.{entry.Method.Name}" : entry.Method.Name,
MethodFullName = entry.Method.FullDescription(),
MethodParameters = entry.Method.GetParameters().Select(x => x.ParameterType.FullName).ToArray(),
NativeInstructions = entry.NativeInstructions,
CilInstructions = entry.CilInstructions,
},
PatchMethods = methodsBuilder.ToArray(),
ILOffset = entry.ILOffset,
NativeOffset = entry.NativeOffset,
MethodFromStackframeIssue = entry.MethodFromStackframeIssue,
});
}
Expand Down
23 changes: 13 additions & 10 deletions src/BUTR.CrashReport.Bannerlord.Source/CrashReportHtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ private static string GetRecursiveExceptionHtml(ExceptionModel ex)

private static string GetEnhancedStacktraceHtml(CrashReportModel crashReport)
{
var random = new Random();
var sb = new StringBuilder();
var sbCil = new StringBuilder();
sb.AppendLine("<ul>");
Expand All @@ -368,26 +369,34 @@ private static string GetEnhancedStacktraceHtml(CrashReportModel crashReport)
sb.Append("<li>")
.Append($"Frame: {stacktrace.Name}</br>")
.Append($"Approximate IL Offset: {(stacktrace.ILOffset is null ? "UNKNOWN" : $"{stacktrace.ILOffset:X4}")}</br>")
.Append($"Native Offset: {(stacktrace.NativeOffset is null ? "UNKNOWN" : $"{stacktrace.NativeOffset:X4}")}")
.Append("<ul>");

foreach (var method in stacktrace.PatchMethods)
{
var id = random.Next();
sb.Append("<li>")
.Append($"Module: {method.Module}</br>")
.Append($"Method: {method.MethodFullName}</br>")
.AppendLine("CIL:").Append("<pre>").AppendJoin(NL, method.CilInstructions).Append("</pre>")
.AppendLine($"<div><a href='javascript:;' class='headers' onclick='showHideById(this, \"{id}\")'>+ CIL:</a><div id='{id}' class='headers-container'><pre>")
.AppendJoin(NL, method.CilInstructions).Append("</pre></div></div>")
.Append("</li>");
}

var id2 = random.Next();
var id3 = random.Next();
sb.Append("<li>")
.Append($"Module: {stacktrace.OriginalMethod.Module}</br>")
.Append($"Method: {stacktrace.OriginalMethod.MethodFullName}</br>")
.Append($"Method From Stackframe Issue: {stacktrace.MethodFromStackframeIssue}</br>")
.AppendLine("CIL:").Append("<pre>").AppendJoin(NL, stacktrace.OriginalMethod.CilInstructions).Append("</pre></br>")
.Append("</li>");
.AppendLine($"<div><a href='javascript:;' class='headers' onclick='showHideById(this, \"{id2}\")'>+ CIL:</a><div id='{id2}' class='headers-container'><pre>")
.AppendJoin(NL, stacktrace.OriginalMethod.CilInstructions).Append("</pre></div></div>")
.AppendLine($"<div><a href='javascript:;' class='headers' onclick='showHideById(this, \"{id3}\")'>+ Native:</a><div id='{id3}' class='headers-container'><pre>")
.AppendJoin(NL, stacktrace.OriginalMethod.NativeInstructions).Append("</pre></div></div>")
.Append("</br></li>");

sb.Append("</ul>");
sb.AppendLine("</li>");
sb.Append("</li>");
sbCil.Clear();
}
sb.AppendLine("</ul>");
Expand Down Expand Up @@ -443,7 +452,6 @@ private static string GetModuleListHtml(CrashReportModel crashReport)
var additionalAssembliesBuilder = new StringBuilder();
var dependenciesBuilder = new StringBuilder();


void AppendDependencies(ModuleModel module)
{
var deps = new Dictionary<string, string>();
Expand Down Expand Up @@ -609,9 +617,7 @@ void AppendAssembly(AssemblyModel assembly)

sb0.AppendLine("<ul>");
foreach (var assembly in crashReport.Assemblies)
{
AppendAssembly(assembly);
}
sb0.AppendLine("</ul>");

return sb0.ToString();
Expand All @@ -628,9 +634,6 @@ void AppendPatches(string name, IEnumerable<HarmonyPatchModel> patches)
patchBuilder.Clear();
foreach (var patch in patches)
{
//if (string.Equals(patch.owner, ExceptionHandlerSubSystem.Instance?.Harmony.Id, StringComparison.InvariantCultureIgnoreCase))
// continue;

patchBuilder.Append("<li>")
.Append("Owner: ").Append(patch.Owner).Append("; ")
.Append("Namespace: ").Append(patch.Namespace).Append("; ")
Expand Down
19 changes: 17 additions & 2 deletions src/BUTR.CrashReport/BUTR.CrashReport.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@
<PropertyGroup>
<PackageId>BUTR.CrashReport</PackageId>
<Title>BUTR.CrashReport</Title>
<Description>.</Description>
<DevelopmentDependency>true</DevelopmentDependency>
<Description>Contains the models for creating the crash report</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIconUrl>https://raw.githubusercontent.com/BUTR/BUTR.CrashReport/master/assets/Icon128x128.png</PackageIconUrl>
<PackageTags>butr crash report bannerlord</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Iced" Version="1.20.0" Alias="iced" Private="false" PrivateAssets="all" />
<PackageReference Include="AsmResolver.DotNet.Dynamic" Version="5.4.0" PrivateAssets="all" />
</ItemGroup>

Expand All @@ -43,6 +43,21 @@
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="Required" Version="1.0.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>

<Target Name="AddPackageAliases" BeforeTargets="ResolveReferences" Outputs="%(PackageReference.Identity)">
<PropertyGroup>
<AliasPackageReference>@(PackageReference->'%(Identity)')</AliasPackageReference>
<AliasName>@(PackageReference->'%(Alias)')</AliasName>
<Private>@(PackageReference->'%(Private)')</Private>
</PropertyGroup>

<ItemGroup>
<ReferencePath Condition="'%(FileName)'=='$(AliasPackageReference)'">
<Aliases>$(AliasName)</Aliases>
<Private>$(Private)</Private>
</ReferencePath>
</ItemGroup>
</Target>

<Target Name="ExcludeAssembliesFromILRepack" BeforeTargets="ILRepackPrepare">
<PropertyGroup>
Expand Down
77 changes: 70 additions & 7 deletions src/BUTR.CrashReport/CrashReportInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using AsmResolver.DotNet;
extern alias iced;

using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Dynamic;

Expand All @@ -10,13 +12,19 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

using iced::Iced.Intel;
using Decoder = iced::Iced.Intel.Decoder;

namespace BUTR.CrashReport;

public record MethodEntry
{
public required MethodBase Method { get; set; }
public required IModuleInfo? ModuleInfo { get; set; }
public required string[] NativeInstructions { get; set; }
public required string[] CilInstructions { get; set; }
}

Expand All @@ -26,7 +34,9 @@ public record StacktraceEntry
public required bool MethodFromStackframeIssue { get; set; }
public required IModuleInfo? ModuleInfo { get; set; }
public required int? ILOffset { get; set; }
public required int? NativeOffset { get; set; }
public required string StackFrameDescription { get; set; }
public required string[] NativeInstructions { get; set; }
public required string[] CilInstructions { get; set; }
public required MethodEntry[] Methods { get; set; }
}
Expand All @@ -44,6 +54,10 @@ public class CrashReportInfo
private static readonly GetIdentifiableDelegate? GetIdentifiable =
AccessTools2.GetDelegate<GetIdentifiableDelegate>("MonoMod.Core.Platforms.PlatformTriple:GetIdentifiable");

private delegate IntPtr GetNativeMethodBodyDelegate(object instance, MethodBase method);
private static readonly GetNativeMethodBodyDelegate? GetNativeMethodBody =
AccessTools2.GetDelegate<GetNativeMethodBodyDelegate>("MonoMod.Core.Platforms.PlatformTriple:GetNativeMethodBody");

public readonly byte Version = 12;
public Guid Id { get; } = Guid.NewGuid();
public Exception Exception { get; }
Expand Down Expand Up @@ -183,7 +197,8 @@ private static IEnumerable<StacktraceEntry> GetAllInvolvedModules(Exception ex,
{
Method = methodBase,
ModuleInfo = extendedModuleInfo,
CilInstructions = GetInstructionLines(methodBase),
NativeInstructions = Array.Empty<string>(),
CilInstructions = GetILInstructionLines(methodBase),
});
}

Expand All @@ -193,7 +208,8 @@ private static IEnumerable<StacktraceEntry> GetAllInvolvedModules(Exception ex,
{
Method = methodBase,
ModuleInfo = extendedModuleInfo,
CilInstructions = GetInstructionLines(methodBase),
NativeInstructions = Array.Empty<string>(),
CilInstructions = GetILInstructionLines(methodBase),
});
}

Expand All @@ -203,7 +219,8 @@ private static IEnumerable<StacktraceEntry> GetAllInvolvedModules(Exception ex,
{
Method = methodBase,
ModuleInfo = extendedModuleInfo,
CilInstructions = GetInstructionLines(methodBase),
NativeInstructions = Array.Empty<string>(),
CilInstructions = GetILInstructionLines(methodBase),
});
}

Expand All @@ -213,28 +230,74 @@ private static IEnumerable<StacktraceEntry> GetAllInvolvedModules(Exception ex,
{
Method = methodBase,
ModuleInfo = extendedModuleInfo,
CilInstructions = GetInstructionLines(methodBase),
NativeInstructions = Array.Empty<string>(),
CilInstructions = GetILInstructionLines(methodBase),
});
}

var moduleInfo = GetModuleInfoIfMod(identifiableMethod, crashReportHelper);

var ilOffset = frame.GetILOffset();
var nativeILOffset = frame.GetNativeOffset();

yield return new()
{
Method = identifiableMethod!,
MethodFromStackframeIssue = methodFromStackframeIssue,
ModuleInfo = moduleInfo,
ILOffset = ilOffset != StackFrame.OFFSET_UNKNOWN ? ilOffset : null,
NativeOffset = nativeILOffset != StackFrame.OFFSET_UNKNOWN ? nativeILOffset : null,
StackFrameDescription = frame.ToString(),
CilInstructions = GetInstructionLines(identifiableMethod),
NativeInstructions = GetInstructionLines(identifiableMethod, nativeILOffset),
CilInstructions = GetILInstructionLines(identifiableMethod),
Methods = methods.ToArray(),
};
}
}

private static string[] GetInstructionLines(MethodBase? method)
private static string[] GetInstructionLines(MethodBase? method, int nativeILOffset)
{
static IEnumerable<string> GetLines(MethodBase method, int nativeILOffset)
{
var nativeCodePtr = GetNativeMethodBody!(CurrentPlatformTriple!(), method);

var length = (uint) nativeILOffset + 16;
var bytecode = new byte[length];

Marshal.Copy(nativeCodePtr, bytecode, 0, bytecode.Length);

var codeReader = new ByteArrayCodeReader(bytecode);
var decoder = Decoder.Create(IntPtr.Size == 4 ? 32 : 64, codeReader);

var output = new StringOutput();
var sb = new StringBuilder();

var formatter = new NasmFormatter
{
Options =
{
DigitSeparator = "`",
FirstOperandCharIndex = 10
}
};

while (decoder.IP < length)
{
var instr = decoder.Decode();
formatter.Format(instr, output); // Don't use instr.ToString(), it allocates more, uses masm syntax and default options
sb.Append(instr.IP.ToString("X4")).Append(" ").Append(output.ToStringAndReset());
yield return sb.ToString();
sb.Clear();
}
}

if (method is null) return Array.Empty<string>();
if (nativeILOffset == StackFrame.OFFSET_UNKNOWN) return Array.Empty<string>();
if (CurrentPlatformTriple is null || GetNativeMethodBody is null) return Array.Empty<string>();

return GetLines(method, nativeILOffset).ToArray();
}
private static string[] GetILInstructionLines(MethodBase? method)
{
static string[] ToLines(CilInstructionCollection? instructions) => instructions?.Select(x => x.ToString()).ToArray() ?? Array.Empty<string>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public record EnhancedStacktraceFrameMethod
public required string MethodFullName { get; set; }
public required string Method { get; set; }
public required ICollection<string> MethodParameters { get; set; }
public required string[] NativeInstructions { get; set; }
public required string[] CilInstructions { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public record EnhancedStacktraceFrameModel
public required string Name { get; set; }
public required string FrameDescription { get; set; }
public required int? ILOffset { get; set; }
public required int? NativeOffset { get; set; }
public required EnhancedStacktraceFrameMethod OriginalMethod { get; set; }
public required ICollection<EnhancedStacktraceFrameMethod> PatchMethods { get; set; }
public required bool MethodFromStackframeIssue { get; set; }
Expand Down

0 comments on commit 6eaadd7

Please sign in to comment.