-
Notifications
You must be signed in to change notification settings - Fork 45
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
Synchronizing dumper with monomod and fixing ILLabel dumping #118
Open
kohanis
wants to merge
5
commits into
BepInEx:master
Choose a base branch
from
kohanis:fix/dumper
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c976a2e
CecilEmitter.Dump: Synchronizing dumper with monomod changes
kohanis c7c196d
CecilEmitter.Dump: Tidying up
kohanis e89e215
CecilEmitter.Dump: Adding argument check
kohanis c48023c
CecilEmitter.Dump: Fixing dumping ILLabels
kohanis fb1248b
CecilEmitter.Dump: Adding test for ILLabel dumping
kohanis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
using HarmonyLib.Tools; | ||
using Mono.Cecil; | ||
using Mono.Cecil.Cil; | ||
using Mono.Collections.Generic; | ||
using MonoMod.Cil; | ||
using MonoMod.Utils; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
@@ -19,17 +21,40 @@ namespace HarmonyLib.Internal.Util; | |
/// <summary> | ||
/// Basic safe DLL emitter for dynamically generated <see cref="MethodDefinition"/>s. | ||
/// </summary> | ||
/// <remarks>Based on https://github.com/MonoMod/MonoMod.Common/blob/master/Utils/DMDGenerators/DMDCecilGenerator.cs</remarks> | ||
/// <remarks>Based on https://github.com/MonoMod/MonoMod/blob/reorganize/src/MonoMod.Utils/DMDGenerators/DMDCecilGenerator.cs</remarks> | ||
internal static class CecilEmitter | ||
{ | ||
private static readonly ConstructorInfo UnverifiableCodeAttributeConstructor = | ||
typeof(UnverifiableCodeAttribute).GetConstructor(Type.EmptyTypes); | ||
|
||
public static void Dump(MethodDefinition md, IEnumerable<string> dumpPaths, MethodBase original = null) | ||
{ | ||
using var module = DumpImpl(md, original); | ||
|
||
foreach (var settingsDumpPath in dumpPaths) | ||
{ | ||
var fullPath = Path.GetFullPath(settingsDumpPath); | ||
try | ||
{ | ||
Directory.CreateDirectory(fullPath); | ||
using var stream = File.OpenWrite(Path.Combine(fullPath, $"{module.Name}.dll")); | ||
module.Write(stream); | ||
} | ||
catch (Exception e) | ||
{ | ||
Logger.Log(Logger.LogChannel.Error, () => $"Failed to dump {md.GetID(simple: true)} to {fullPath}: {e}"); | ||
} | ||
} | ||
} | ||
|
||
internal static ModuleDefinition DumpImpl(MethodDefinition md, MethodBase original = null) | ||
{ | ||
if(md.Body is null) | ||
throw new ArgumentException($"Body of MethodDefinition '{md.Name}' to dump is null"); | ||
|
||
var name = $"HarmonyDump.{SanitizeTypeName(md.GetID(withType: false, simple: true))}.{Guid.NewGuid().GetHashCode():X8}"; | ||
var originalName = (original?.Name ?? md.Name).Replace('.', '_'); | ||
using var module = ModuleDefinition.CreateModule(name, | ||
var module = ModuleDefinition.CreateModule(name, | ||
new ModuleParameters | ||
{ | ||
Kind = ModuleKind.Dll, ReflectionImporterProvider = MMReflectionImporter.ProviderNoDefault | ||
|
@@ -47,51 +72,75 @@ public static void Dump(MethodDefinition md, IEnumerable<string> dumpPaths, Meth | |
|
||
module.Types.Add(td); | ||
|
||
MethodDefinition clone = null; | ||
|
||
var isVolatile = new TypeReference("System.Runtime.CompilerServices", "IsVolatile", module, | ||
module.TypeSystem.CoreLibrary); | ||
|
||
Relinker relinker = (mtp, _) => mtp == md ? clone : module.ImportReference(mtp); | ||
|
||
clone = | ||
var clone = | ||
new MethodDefinition(original?.Name ?? "_" + md.Name.Replace(".", "_"), md.Attributes, module.TypeSystem.Void) | ||
{ | ||
MethodReturnType = md.MethodReturnType, | ||
Attributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, | ||
ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed, | ||
DeclaringType = td, | ||
HasThis = false | ||
HasThis = false, | ||
NoInlining = true | ||
}; | ||
td.Methods.Add(clone); | ||
|
||
#pragma warning disable IDE0039 // Use local function | ||
Relinker relinker = (mtp, _) => | ||
{ | ||
if (mtp == md) | ||
return clone!; | ||
if (mtp is MethodReference mr) | ||
{ | ||
if (mr.FullName == md.FullName | ||
&& mr.DeclaringType.FullName == md.DeclaringType.FullName | ||
&& mr.DeclaringType.Scope.Name == md.DeclaringType.Scope.Name) | ||
return clone!; | ||
} | ||
return module.ImportReference(mtp); | ||
}; | ||
#pragma warning restore IDE0039 // Use local function | ||
|
||
foreach (var param in md.Parameters) | ||
clone.Parameters.Add(param.Clone().Relink(relinker, clone)); | ||
|
||
clone.ReturnType = md.ReturnType.Relink(relinker, clone); | ||
var body = clone.Body = md.Body.Clone(clone); | ||
|
||
foreach (var variable in clone.Body.Variables) | ||
foreach (var variable in body.Variables) | ||
variable.VariableType = variable.VariableType.Relink(relinker, clone); | ||
|
||
foreach (var handler in clone.Body.ExceptionHandlers.Where(handler => handler.CatchType != null)) | ||
foreach (var handler in body.ExceptionHandlers.Where(handler => handler.CatchType != null)) | ||
handler.CatchType = handler.CatchType.Relink(relinker, clone); | ||
|
||
/* see below | ||
var isVolatile = new TypeReference("System.Runtime.CompilerServices", "IsVolatile", module, | ||
module.TypeSystem.CoreLibrary); | ||
*/ | ||
foreach (var instr in body.Instructions) | ||
{ | ||
var operand = instr.Operand; | ||
operand = operand switch | ||
{ | ||
ParameterDefinition param => clone.Parameters[param.Index], | ||
ILLabel label => label.Target, | ||
ILLabel label => LabelFix(label, body.Instructions, md.Body.Instructions, originalName), | ||
IMetadataTokenProvider mtp => mtp.Relink(relinker, clone), | ||
_ => operand | ||
}; | ||
|
||
if (instr.Previous?.OpCode == OpCodes.Volatile && | ||
operand is FieldReference fref && | ||
(fref.FieldType as RequiredModifierType)?.ModifierType != isVolatile) | ||
fref.FieldType = new RequiredModifierType(isVolatile, fref.FieldType); | ||
// System.Reflection doesn't contain any volatility info. | ||
// System.Reflection.Emit presumably does something similar to this. | ||
// Mono.Cecil thus isn't aware of the volatility as part of the imported field reference. | ||
// The modifier is still necessary though. | ||
// This is done here instead of the copier as Harmony and other users can't track modreqs | ||
|
||
// This isn't actually a valid transformation though. A ldfld or stfld can have the volatile | ||
// prefix, without having modreq(IsVolatile) on the field. Adding the modreq() causes the runtime | ||
// to not be able to find the field. | ||
/*if (instr.Previous?.OpCode == OpCodes.Volatile && | ||
operand is FieldReference fref && | ||
(fref.FieldType as RequiredModifierType)?.ModifierType != tr_IsVolatile) { | ||
fref.FieldType = new RequiredModifierType(tr_IsVolatile, fref.FieldType); | ||
}*/ | ||
Comment on lines
+139
to
+143
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, if something isn't important, delete it. |
||
|
||
instr.Operand = operand; | ||
} | ||
|
@@ -105,20 +154,7 @@ operand is FieldReference fref && | |
new ParameterDefinition("<>_this", ParameterAttributes.None, type.Relink(relinker, clone))); | ||
} | ||
|
||
foreach (var settingsDumpPath in dumpPaths) | ||
{ | ||
var fullPath = Path.GetFullPath(settingsDumpPath); | ||
try | ||
{ | ||
Directory.CreateDirectory(fullPath); | ||
using var stream = File.OpenWrite(Path.Combine(fullPath, $"{module.Name}.dll")); | ||
module.Write(stream); | ||
} | ||
catch (Exception e) | ||
{ | ||
Logger.Log(Logger.LogChannel.Error, () => $"Failed to dump {md.GetID(simple: true)} to {fullPath}: {e}"); | ||
} | ||
} | ||
return module; | ||
} | ||
|
||
private static string SanitizeTypeName(string typeName) | ||
|
@@ -129,4 +165,14 @@ private static string SanitizeTypeName(string typeName) | |
.Replace("<", "{") | ||
.Replace(">", "}"); | ||
} | ||
|
||
private static Instruction LabelFix(ILLabel label, Collection<Instruction> clone, Collection<Instruction> original, string originalName) | ||
{ | ||
Debug.Assert(clone.Count == original.Count); | ||
var target = label.Target; | ||
var idx = original.IndexOf(target); | ||
if (idx == -1) | ||
throw new NullReferenceException($"ILLabel from dump copy of '{originalName}' cannot be relinked. It points to '{target}', which was not found in the original body"); | ||
return clone[idx]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using HarmonyLib.Internal.Util; | ||
using HarmonyLibTests; | ||
using MonoMod.Cil; | ||
using MonoMod.Utils; | ||
using NUnit.Framework; | ||
using System; | ||
using System.Linq; | ||
|
||
|
||
namespace HarmonyTests.Extras; | ||
|
||
[TestFixture] | ||
public class Dump : TestLogger | ||
{ | ||
[Test] | ||
public void Test_DumpIlLabel() | ||
{ | ||
using var dmd = new DynamicMethodDefinition("DumpIlLabelTestMethod", typeof(void), Type.EmptyTypes); | ||
var il = new ILContext(dmd.Definition); | ||
var cur = new ILCursor(il); | ||
|
||
var label = cur.DefineLabel(); | ||
cur.EmitBr(label); | ||
cur.EmitRet(); | ||
label.Target = cur.Prev; | ||
|
||
var method = cur.Method; | ||
|
||
using var dumpedModule = CecilEmitter.DumpImpl(method); | ||
var dumpedMethod = dumpedModule.Types.SelectMany(i => i.Methods).FirstOrDefault(); | ||
Assert.NotNull(dumpedMethod); | ||
|
||
var instructions = dumpedMethod!.Body.Instructions; | ||
Assert.True(instructions[0].Operand == instructions[1]); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Justification?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated from reorg: https://github.com/MonoMod/MonoMod/blob/26a0a4d22fcd233dd22db4fd034cb3ac41420fd4/src/MonoMod.Utils/DMDGenerators/DMDCecilGenerator.cs#L72-L83
Which is also the same for the double Public, and everything else but the label stuff. Label stuff specifically addressed the issue I mentioned above.