Skip to content

Commit

Permalink
Fix GetLdftnPointer, use it for virtual struct methods on .NET
Browse files Browse the repository at this point in the history
  • Loading branch information
0x0ade committed Apr 20, 2020
1 parent f5aa7c8 commit 58702d6
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 15 deletions.
18 changes: 9 additions & 9 deletions RuntimeDetour/Platforms/Runtime/DetourRuntimeILPlatform.cs
Expand Up @@ -116,16 +116,16 @@ private struct _SelftestStruct {

#endregion

protected virtual IntPtr GetFunctionPointer(RuntimeMethodHandle handle)
protected virtual IntPtr GetFunctionPointer(MethodBase method, RuntimeMethodHandle handle)
=> handle.GetFunctionPointer();

protected virtual void PrepareMethod(RuntimeMethodHandle handle)
protected virtual void PrepareMethod(MethodBase method, RuntimeMethodHandle handle)
=> RuntimeHelpers.PrepareMethod(handle);

protected virtual void PrepareMethod(RuntimeMethodHandle handle, RuntimeTypeHandle[] instantiation)
protected virtual void PrepareMethod(MethodBase method, RuntimeMethodHandle handle, RuntimeTypeHandle[] instantiation)
=> RuntimeHelpers.PrepareMethod(handle, instantiation);

protected virtual void DisableInlining(RuntimeMethodHandle handle) {
protected virtual void DisableInlining(MethodBase method, RuntimeMethodHandle handle) {
// no-op. Not supported on all platforms, but throwing an exception doesn't make sense.
}

Expand All @@ -135,8 +135,8 @@ protected virtual void PrepareMethod(RuntimeMethodHandle handle, RuntimeTypeHand
lock (PinnedMethods)
pinGot = PinnedMethods.TryGetValue(method, out pin);
if (pinGot)
return GetFunctionPointer(pin.Handle);
return GetFunctionPointer(GetMethodHandle(method));
return GetFunctionPointer(method, pin.Handle);
return GetFunctionPointer(method, GetMethodHandle(method));
}

public void Pin(MethodBase method) {
Expand All @@ -150,11 +150,11 @@ protected virtual void PrepareMethod(RuntimeMethodHandle handle, RuntimeTypeHand
pin.Count = 1;
RuntimeMethodHandle handle = pin.Handle = GetMethodHandle(method);
if (method.DeclaringType?.IsGenericType ?? false) {
PrepareMethod(handle, method.DeclaringType.GetGenericArguments().Select(type => type.TypeHandle).ToArray());
PrepareMethod(method, handle, method.DeclaringType.GetGenericArguments().Select(type => type.TypeHandle).ToArray());
} else {
PrepareMethod(handle);
PrepareMethod(method, handle);
}
DisableInlining(handle);
DisableInlining(method, handle);
PinnedMethods[method] = pin;
}
}
Expand Down
Expand Up @@ -30,7 +30,7 @@ class DetourRuntimeMonoPlatform : DetourRuntimeILPlatform {
return method.MethodHandle;
}

protected override unsafe void DisableInlining(RuntimeMethodHandle handle) {
protected override unsafe void DisableInlining(MethodBase method, RuntimeMethodHandle handle) {
// https://github.com/mono/mono/blob/34dee0ea4e969d6d5b37cb842fc3b9f73f2dc2ae/mono/metadata/class-internals.h#L64
ushort* iflags = (ushort*) ((long) handle.Value + 2);
*iflags |= (ushort) MethodImplOptions.NoInlining;
Expand Down
Expand Up @@ -12,7 +12,7 @@ namespace MonoMod.RuntimeDetour.Platforms {
#endif
class DetourRuntimeNETCorePlatform : DetourRuntimeNETPlatform {

protected override void DisableInlining(RuntimeMethodHandle handle) {
protected override void DisableInlining(MethodBase method, RuntimeMethodHandle handle) {
// https://github.com/dotnet/runtime/blob/89965be3ad2be404dc82bd9e688d5dd2a04bcb5f/src/coreclr/src/vm/method.hpp#L178
// mdcNotInline = 0x2000
// References to RuntimeMethodHandle (CORINFO_METHOD_HANDLE) pointing to MethodDesc
Expand Down
21 changes: 20 additions & 1 deletion RuntimeDetour/Platforms/Runtime/DetourRuntimeNETPlatform.cs
Expand Up @@ -60,8 +60,27 @@ class DetourRuntimeNETPlatform : DetourRuntimeILPlatform {
return method.MethodHandle;
}

protected override void DisableInlining(RuntimeMethodHandle handle) {
protected override void DisableInlining(MethodBase method, RuntimeMethodHandle handle) {
// This is not needed for .NET Framework - see DisableInliningTest.
}

protected override unsafe IntPtr GetFunctionPointer(MethodBase method, RuntimeMethodHandle handle) {
if (method.IsVirtual && (method.DeclaringType?.IsValueType ?? false)) {
/* .NET has got TWO MethodDescs and thus TWO ENTRY POINTS for virtual struct methods (f.e. override ToString).
* More info: https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/#unboxing-stub-creation
*
* Observations made so far:
* - GetFunctionPointer ALWAYS returns a pointer to the unboxing stub handle.
* - On x86, the "real" entry point is often found 8 bytes after the unboxing stub entry point.
* - Methods WILL be called INDIRECTLY using the pointer found in the "real" MethodDesc.
* - The "real" MethodDesc will be updated, which isn't an issue except that we can't patch the stub in time.
* - The "real" stub will stay untouched.
* - LDFTN RETURNS A POINTER TO THE "REAL" ENTRY POINT.
*/
return method.GetLdftnPointer();
}

return base.GetFunctionPointer(method, handle);
}
}
}
7 changes: 4 additions & 3 deletions Utils/Extensions.Unsafe.cs
Expand Up @@ -107,8 +107,9 @@ static partial class Extensions {
/// Get a native function pointer for a given method. This matches an IL-level ldftn.
/// </summary>
/// <remarks>
/// ldftn doesn't JIT-compile the method on mono, which thus keeps the class constructor untouched.
/// On the other hand, its result thus doesn't always match that of MethodHandle.GetFunctionPointer().
/// The result of ldftn doesn't always match that of MethodHandle.GetFunctionPointer().
/// For example, ldftn doesn't JIT-compile the method on mono, which thus keeps the class constructor untouched.
/// And on .NET, struct overrides (f.e. ToString) have got multiple entry points pointing towards the same code.
/// </remarks>
/// <param name="m">The method to get a native function pointer for.</param>
/// <returns>The native function pointer.</returns>
Expand All @@ -118,7 +119,7 @@ static partial class Extensions {

DynamicMethodDefinition dmd = new DynamicMethodDefinition(
$"GetLdftnPointer<{m.GetID(simple: true)}>",
typeof(int), Type.EmptyTypes
typeof(IntPtr), Type.EmptyTypes
);

ILProcessor il = dmd.GetILProcessor();
Expand Down

0 comments on commit 58702d6

Please sign in to comment.