/
DetourHelper.cs
279 lines (234 loc) · 11.2 KB
/
DetourHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
using System;
using System.Reflection;
using System.Linq.Expressions;
using MonoMod.Utils;
using System.Collections.Generic;
using MonoMod.RuntimeDetour.Platforms;
using Mono.Cecil.Cil;
using System.Threading;
using Mono.Cecil;
namespace MonoMod.RuntimeDetour {
#if !MONOMOD_INTERNAL
public
#endif
static class DetourHelper {
private static readonly object _RuntimeLock = new object();
private static IDetourRuntimePlatform _Runtime;
public static IDetourRuntimePlatform Runtime {
get {
if (_Runtime != null)
return _Runtime;
lock (_RuntimeLock) {
if (_Runtime != null)
return _Runtime;
if (Type.GetType("Mono.Runtime") != null) {
_Runtime = new DetourRuntimeMonoPlatform();
} else if (typeof(object).Assembly.GetName().Name == "System.Private.CoreLib") {
_Runtime = new DetourRuntimeNETCorePlatform();
} else {
_Runtime = new DetourRuntimeNETPlatform();
}
return _Runtime;
}
}
set => _Runtime = value;
}
private static readonly object _NativeLock = new object();
private static IDetourNativePlatform _Native;
public static IDetourNativePlatform Native {
get {
if (_Native != null)
return _Native;
lock (_NativeLock) {
if (_Native != null)
return _Native;
if (PlatformHelper.Is(Platform.ARM)) {
_Native = new DetourNativeARMPlatform();
} else {
_Native = new DetourNativeX86Platform();
}
if (PlatformHelper.Is(Platform.Windows)) {
return _Native = new DetourNativeWindowsPlatform(_Native);
}
if (Type.GetType("Mono.Runtime") != null) {
try {
// It's prefixed with lib on every platform.
return _Native = new DetourNativeMonoPlatform(_Native, $"libmonosgen-2.0.{PlatformHelper.LibrarySuffix}");
} catch {
// Fall back to another native platform wrapper.
}
} else {
// .NET Core currently doesn't contain any meaningful built-in wrappers.
}
// MonoPosixHelper is available outside of Unix and even outside of Mono.
try {
_Native = new DetourNativeMonoPosixPlatform(_Native);
} catch {
// Good job, your copy of Mono doesn't ship with MonoPosixHelper.
// https://www.youtube.com/watch?v=l60MnDJklnM
}
// Might as well try libc...
try {
_Native = new DetourNativeLibcPlatform(_Native);
} catch {
// Oh well.
}
return _Native;
}
}
set => _Native = value;
}
#region Interface extension methods
public static void MakeWritable(this IDetourNativePlatform plat, NativeDetourData detour) => plat.MakeWritable(detour.Method, detour.Size);
public static void MakeExecutable(this IDetourNativePlatform plat, NativeDetourData detour) => plat.MakeExecutable(detour.Method, detour.Size);
public static void FlushICache(this IDetourNativePlatform plat, NativeDetourData detour) => plat.FlushICache(detour.Method, detour.Size);
#endregion
#region Native helpers
/// <summary>
/// Write the given value at the address to + offs, afterwards advancing offs by sizeof(byte).
/// </summary>
public static unsafe void Write(this IntPtr to, ref int offs, byte value) {
*((byte*) ((long) to + offs)) = value;
offs += 1;
}
/// <summary>
/// Write the given value at the address to + offs, afterwards advancing offs by sizeof(ushort).
/// </summary>
public static unsafe void Write(this IntPtr to, ref int offs, ushort value) {
*((ushort*) ((long) to + offs)) = value;
offs += 2;
}
/// <summary>
/// Write the given value at the address to + offs, afterwards advancing offs by sizeof(ushort).
/// </summary>
public static unsafe void Write(this IntPtr to, ref int offs, uint value) {
*((uint*) ((long) to + offs)) = value;
offs += 4;
}
/// <summary>
/// Write the given value at the address to + offs, afterwards advancing offs by sizeof(ulong).
/// </summary>
public static unsafe void Write(this IntPtr to, ref int offs, ulong value) {
*((ulong*) ((long) to + offs)) = value;
offs += 8;
}
#endregion
#region Method-related helpers
public static IntPtr GetNativeStart(this MethodBase method)
=> Runtime.GetNativeStart(method);
public static IntPtr GetNativeStart(this Delegate method)
=> method.Method.GetNativeStart();
public static IntPtr GetNativeStart(this Expression method)
=> ((MethodCallExpression) method).Method.GetNativeStart();
public static MethodInfo CreateILCopy(this MethodBase method)
=> Runtime.CreateCopy(method);
public static bool TryCreateILCopy(this MethodBase method, out MethodInfo dm)
=> Runtime.TryCreateCopy(method, out dm);
public static T Pin<T>(this T method) where T : MethodBase {
Runtime.Pin(method);
return method;
}
public static T Unpin<T>(this T method) where T : MethodBase {
Runtime.Unpin(method);
return method;
}
#endregion
#region DynamicMethod generation helpers
/// <summary>
/// Generate a DynamicMethod to easily call the given native function from another DynamicMethod.
/// </summary>
/// <param name="target">The pointer to the native function to call.</param>
/// <param name="signature">A MethodBase with the target function's signature.</param>
/// <returns>The detoured DynamicMethod.</returns>
public static MethodInfo GenerateNativeProxy(IntPtr target, MethodBase signature) {
Type returnType = (signature as MethodInfo)?.ReturnType ?? typeof(void);
ParameterInfo[] args = signature.GetParameters();
Type[] argTypes = new Type[args.Length];
for (int i = 0; i < args.Length; i++)
argTypes[i] = args[i].ParameterType;
MethodInfo dm;
using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(
$"Native<{((long) target).ToString("X16")}>",
returnType, argTypes
))
dm = dmd.StubCriticalDetour().Generate().Pin();
// Detour the new DynamicMethod into the target.
NativeDetourData detour = Native.Create(dm.GetNativeStart(), target);
Native.MakeWritable(detour);
Native.Apply(detour);
Native.MakeExecutable(detour);
Native.FlushICache(detour);
Native.Free(detour);
return dm;
}
// Used in EmitDetourApply.
private static NativeDetourData ToNativeDetourData(IntPtr method, IntPtr target, uint size, byte type, IntPtr extra)
=> new NativeDetourData {
Method = method,
Target = target,
Size = size,
Type = type,
Extra = extra
};
private static readonly FieldInfo _f_Native = typeof(DetourHelper).GetField("_Native", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo _m_ToNativeDetourData = typeof(DetourHelper).GetMethod("ToNativeDetourData", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo _m_Copy = typeof(IDetourNativePlatform).GetMethod("Copy");
private static readonly MethodInfo _m_Apply = typeof(IDetourNativePlatform).GetMethod("Apply");
private static readonly ConstructorInfo _ctor_Exception = typeof(Exception).GetConstructor(new Type[] { typeof(string) });
/// <summary>
/// Fill the DynamicMethodDefinition with a throw.
/// </summary>
public static DynamicMethodDefinition StubCriticalDetour(this DynamicMethodDefinition dm) {
ILProcessor il = dm.GetILProcessor();
ModuleDefinition ilModule = il.Body.Method.Module;
for (int i = 0; i < 32; i++) {
// Prevent mono from inlining the DynamicMethod.
il.Emit(OpCodes.Nop);
}
il.Emit(OpCodes.Ldstr, $"{dm.Definition.Name} should've been detoured!");
il.Emit(OpCodes.Newobj, ilModule.ImportReference(_ctor_Exception));
il.Emit(OpCodes.Throw);
return dm;
}
/// <summary>
/// Emit a call to DetourManager.Native.Copy using the given parameters.
/// </summary>
public static void EmitDetourCopy(this ILProcessor il, IntPtr src, IntPtr dst, byte type) {
ModuleDefinition ilModule = il.Body.Method.Module;
// Load NativePlatform instance.
il.Emit(OpCodes.Ldsfld, ilModule.ImportReference(_f_Native));
// Fill stack with src, dst, type
il.Emit(OpCodes.Ldc_I8, (long) src);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Ldc_I8, (long) dst);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Ldc_I4, (int) type);
il.Emit(OpCodes.Conv_U1);
// Copy.
il.Emit(OpCodes.Callvirt, ilModule.ImportReference(_m_Copy));
}
/// <summary>
/// Emit a call to DetourManager.Native.Apply using a copy of the given data.
/// </summary>
public static void EmitDetourApply(this ILProcessor il, NativeDetourData data) {
ModuleDefinition ilModule = il.Body.Method.Module;
// Load NativePlatform instance.
il.Emit(OpCodes.Ldsfld, ilModule.ImportReference(_f_Native));
// Fill stack with data values.
il.Emit(OpCodes.Ldc_I8, (long) data.Method);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Ldc_I8, (long) data.Target);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Ldc_I4, (int) data.Size);
il.Emit(OpCodes.Ldc_I4, (int) data.Type);
il.Emit(OpCodes.Conv_U1);
il.Emit(OpCodes.Ldc_I8, (long) data.Extra);
il.Emit(OpCodes.Conv_I);
// Put values in stack into NativeDetourData.
il.Emit(OpCodes.Call, ilModule.ImportReference(_m_ToNativeDetourData));
// Apply.
il.Emit(OpCodes.Callvirt, ilModule.ImportReference(_m_Apply));
}
#endregion
}
}