Jun*_*des 85 c# methods swap cil assemblies
我想要做的是改变C#方法在调用时的执行方式,这样我就可以编写如下内容:
[Distributed]
public DTask<bool> Solve(int n, DEvent<bool> callback)
{
for (int m = 2; m < n - 1; m += 1)
if (m % n == 0)
return false;
return true;
}
Run Code Online (Sandbox Code Playgroud)
在运行时,我需要能够分析具有Distributed属性(我已经可以做)的方法,然后在函数体执行之前和函数返回之后插入代码.更重要的是,我需要能够在不修改调用Solve的代码的情况下或在函数的开头修改代码(在编译时;在运行时这样做是目标).
目前我尝试了这段代码(假设t是Solve存储的类型,m是Solve的MethodInfo):
private void WrapMethod(Type t, MethodInfo m)
{
// Generate ILasm for delegate.
byte[] il = typeof(Dpm).GetMethod("ReplacedSolve").GetMethodBody().GetILAsByteArray();
// Pin the bytes in the garbage collection.
GCHandle h = GCHandle.Alloc((object)il, GCHandleType.Pinned);
IntPtr addr = h.AddrOfPinnedObject();
int size = il.Length;
// Swap the method.
MethodRental.SwapMethodBody(t, m.MetadataToken, addr, size, MethodRental.JitImmediate);
}
public DTask<bool> ReplacedSolve(int n, DEvent<bool> callback)
{
Console.WriteLine("This was executed instead!");
return true;
}
Run Code Online (Sandbox Code Playgroud)
但是,MethodRental.SwapMethodBody仅适用于动态模块; 不是那些已经编译并存储在程序集中的.
所以我正在寻找一种方法来有效地对已经存储在已加载和执行的程序集中的方法执行SwapMethodBody .
注意,如果我必须将方法完全复制到动态模块中,这不是问题,但在这种情况下,我需要找到一种方法来复制IL并更新所有对Solve()的调用,以便它们会指向新副本.
Log*_*man 162
对于.NET 4及更高版本
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace InjectionTest
{
class Program
{
static void Main(string[] args)
{
Target targetInstance = new Target();
targetInstance.test();
Injection.install(1);
Injection.install(2);
Injection.install(3);
Injection.install(4);
targetInstance.test();
Console.Read();
}
}
public class Target
{
public void test()
{
targetMethod1();
Console.WriteLine(targetMethod2());
targetMethod3("Test");
targetMethod4();
}
private void targetMethod1()
{
Console.WriteLine("Target.targetMethod1()");
}
private string targetMethod2()
{
Console.WriteLine("Target.targetMethod2()");
return "Not injected 2";
}
public void targetMethod3(string text)
{
Console.WriteLine("Target.targetMethod3("+text+")");
}
private void targetMethod4()
{
Console.WriteLine("Target.targetMethod4()");
}
}
public class Injection
{
public static void install(int funcNum)
{
MethodInfo methodToReplace = typeof(Target).GetMethod("targetMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
MethodInfo methodToInject = typeof(Injection).GetMethod("injectionMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
unsafe
{
if (IntPtr.Size == 4)
{
int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;
int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2;
#if DEBUG
Console.WriteLine("\nVersion x86 Debug\n");
byte* injInst = (byte*)*inj;
byte* tarInst = (byte*)*tar;
int* injSrc = (int*)(injInst + 1);
int* tarSrc = (int*)(tarInst + 1);
*tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
Console.WriteLine("\nVersion x86 Release\n");
*tar = *inj;
#endif
}
else
{
long* inj = (long*)methodToInject.MethodHandle.Value.ToPointer()+1;
long* tar = (long*)methodToReplace.MethodHandle.Value.ToPointer()+1;
#if DEBUG
Console.WriteLine("\nVersion x64 Debug\n");
byte* injInst = (byte*)*inj;
byte* tarInst = (byte*)*tar;
int* injSrc = (int*)(injInst + 1);
int* tarSrc = (int*)(tarInst + 1);
*tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
Console.WriteLine("\nVersion x64 Release\n");
*tar = *inj;
#endif
}
}
}
private void injectionMethod1()
{
Console.WriteLine("Injection.injectionMethod1");
}
private string injectionMethod2()
{
Console.WriteLine("Injection.injectionMethod2");
return "Injected 2";
}
private void injectionMethod3(string text)
{
Console.WriteLine("Injection.injectionMethod3 " + text);
}
private void injectionMethod4()
{
System.Diagnostics.Process.Start("calc");
}
}
}
Run Code Online (Sandbox Code Playgroud)
And*_*ike 131
Harmony是一个开源库,旨在在运行时替换,修饰或修改任何类型的现有C#方法.它的主要焦点是用Mono编写的游戏和插件,但该技术可以用于任何.NET版本.它还负责对同一方法进行多次更改(它们累积而不是覆盖).
它为每个原始方法创建DynamicMethod类型的方法,并向其发出代码,在开始和结束时调用自定义方法.它还允许您编写过滤器来处理原始IL代码,从而允许对原始方法进行更详细的操作.
为了完成该过程,它将一个简单的汇编程序跳转写入原始方法的trampoline,该方法指向通过编译动态方法生成的汇编程序.这适用于Windows,MacOS和Mono支持的任何Linux上的32/64Bit.
Oli*_*ier 25
您可以在运行时修改方法的内容.但是你不应该这样做,强烈建议保留它以用于测试目的.
看看:
http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time
基本上,你可以:
弄乱这些字节.
如果您只是希望预先添加或附加一些代码,那么只需预先发布/追加您想要的操作码(但要注意保持堆栈干净)
以下是"解编"现有IL的一些提示:
修改后,您可以通过InjectionHelper.UpdateILCodes(MethodInfo方法,byte [] ilCodes)重新注入IL字节数组 - 请参阅上面提到的链接
这是"不安全"的部分......它运作良好,但这包括黑客内部CLR机制......
Tet*_*r28 12
如果方法是非虚拟的,非泛型的,不是泛型的,不是内联的,并且在x86平台上,你可以替换它:
MethodInfo methodToReplace = ...
RuntimeHelpers.PrepareMetod(methodToReplace.MethodHandle);
var getDynamicHandle = Delegate.CreateDelegate(Metadata<Func<DynamicMethod, RuntimeMethodHandle>>.Type, Metadata<DynamicMethod>.Type.GetMethod("GetMethodDescriptor", BindingFlags.Instance | BindingFlags.NonPublic)) as Func<DynamicMethod, RuntimeMethodHandle>;
var newMethod = new DynamicMethod(...);
var body = newMethod.GetILGenerator();
body.Emit(...) // do what you want.
body.Emit(OpCodes.jmp, methodToReplace);
body.Emit(OpCodes.ret);
var handle = getDynamicHandle(newMethod);
RuntimeHelpers.PrepareMethod(handle);
*((int*)new IntPtr(((int*)methodToReplace.MethodHandle.Value.ToPointer() + 2)).ToPointer()) = handle.GetFunctionPointer().ToInt32();
//all call on methodToReplace redirect to newMethod and methodToReplace is called in newMethod and you can continue to debug it, enjoy.
Run Code Online (Sandbox Code Playgroud)
Tak*_*est 10
根据这个问题和另一个问题的答案,我想出了这个整理过的版本:
// Note: This method replaces methodToReplace with methodToInject
// Note: methodToInject will still remain pointing to the same location
public static unsafe MethodReplacementState Replace(this MethodInfo methodToReplace, MethodInfo methodToInject)
{
//#if DEBUG
RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
//#endif
MethodReplacementState state;
IntPtr tar = methodToReplace.MethodHandle.Value;
if (!methodToReplace.IsVirtual)
tar += 8;
else
{
var index = (int)(((*(long*)tar) >> 32) & 0xFF);
var classStart = *(IntPtr*)(methodToReplace.DeclaringType.TypeHandle.Value + (IntPtr.Size == 4 ? 40 : 64));
tar = classStart + IntPtr.Size * index;
}
var inj = methodToInject.MethodHandle.Value + 8;
#if DEBUG
tar = *(IntPtr*)tar + 1;
inj = *(IntPtr*)inj + 1;
state.Location = tar;
state.OriginalValue = new IntPtr(*(int*)tar);
*(int*)tar = *(int*)inj + (int)(long)inj - (int)(long)tar;
return state;
#else
state.Location = tar;
state.OriginalValue = *(IntPtr*)tar;
* (IntPtr*)tar = *(IntPtr*)inj;
return state;
#endif
}
}
public struct MethodReplacementState : IDisposable
{
internal IntPtr Location;
internal IntPtr OriginalValue;
public void Dispose()
{
this.Restore();
}
public unsafe void Restore()
{
#if DEBUG
*(int*)Location = (int)OriginalValue;
#else
*(IntPtr*)Location = OriginalValue;
#endif
}
}
Run Code Online (Sandbox Code Playgroud)
有两个框架可让您在运行时动态更改任何方法(它们使用user152949提到的ICLRProfiling接口):
还有一些框架可以模拟.NET的内部结构,这些框架可能更脆弱,并且可能无法更改内联代码,但是另一方面,它们是完全独立的,不需要您使用自定义启动器。
Logman的解决方案,但具有交换方法体的接口.另外,一个更简单的例子.
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace DynamicMojo
{
class Program
{
static void Main(string[] args)
{
Animal kitty = new HouseCat();
Animal lion = new Lion();
var meow = typeof(HouseCat).GetMethod("Meow", BindingFlags.Instance | BindingFlags.NonPublic);
var roar = typeof(Lion).GetMethod("Roar", BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine("<==(Normal Run)==>");
kitty.MakeNoise(); //HouseCat: Meow.
lion.MakeNoise(); //Lion: Roar!
Console.WriteLine("<==(Dynamic Mojo!)==>");
DynamicMojo.SwapMethodBodies(meow, roar);
kitty.MakeNoise(); //HouseCat: Roar!
lion.MakeNoise(); //Lion: Meow.
Console.WriteLine("<==(Normality Restored)==>");
DynamicMojo.SwapMethodBodies(meow, roar);
kitty.MakeNoise(); //HouseCat: Meow.
lion.MakeNoise(); //Lion: Roar!
Console.Read();
}
}
public abstract class Animal
{
public void MakeNoise() => Console.WriteLine($"{this.GetType().Name}: {GetSound()}");
protected abstract string GetSound();
}
public sealed class HouseCat : Animal
{
protected override string GetSound() => Meow();
private string Meow() => "Meow.";
}
public sealed class Lion : Animal
{
protected override string GetSound() => Roar();
private string Roar() => "Roar!";
}
public static class DynamicMojo
{
/// <summary>
/// Swaps the function pointers for a and b, effectively swapping the method bodies.
/// </summary>
/// <exception cref="ArgumentException">
/// a and b must have same signature
/// </exception>
/// <param name="a">Method to swap</param>
/// <param name="b">Method to swap</param>
public static void SwapMethodBodies(MethodInfo a, MethodInfo b)
{
if (!HasSameSignature(a, b))
{
throw new ArgumentException("a and b must have have same signature");
}
RuntimeHelpers.PrepareMethod(a.MethodHandle);
RuntimeHelpers.PrepareMethod(b.MethodHandle);
unsafe
{
if (IntPtr.Size == 4)
{
int* inj = (int*)b.MethodHandle.Value.ToPointer() + 2;
int* tar = (int*)a.MethodHandle.Value.ToPointer() + 2;
byte* injInst = (byte*)*inj;
byte* tarInst = (byte*)*tar;
int* injSrc = (int*)(injInst + 1);
int* tarSrc = (int*)(tarInst + 1);
int tmp = *tarSrc;
*tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
*injSrc = (((int)tarInst + 5) + tmp) - ((int)injInst + 5);
}
else
{
throw new NotImplementedException($"{nameof(SwapMethodBodies)} doesn't yet handle IntPtr size of {IntPtr.Size}");
}
}
}
private static bool HasSameSignature(MethodInfo a, MethodInfo b)
{
bool sameParams = !a.GetParameters().Any(x => !b.GetParameters().Any(y => x == y));
bool sameReturnType = a.ReturnType == b.ReturnType;
return sameParams && sameReturnType;
}
}
}
Run Code Online (Sandbox Code Playgroud)
根据TakeMeAsAGuest 的回答,这里有一个类似的扩展,不需要使用不安全的块。
这是Extensions
课程:
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MethodRedirect
{
static class Extensions
{
public static void RedirectTo(this MethodInfo origin, MethodInfo target)
{
IntPtr ori = GetMethodAddress(origin);
IntPtr tar = GetMethodAddress(target);
Marshal.Copy(new IntPtr[] { Marshal.ReadIntPtr(tar) }, 0, ori, 1);
}
private static IntPtr GetMethodAddress(MethodInfo mi)
{
const ushort SLOT_NUMBER_MASK = 0xffff; // 2 bytes mask
const int MT_OFFSET_32BIT = 0x28; // 40 bytes offset
const int MT_OFFSET_64BIT = 0x40; // 64 bytes offset
IntPtr address;
// JIT compilation of the method
RuntimeHelpers.PrepareMethod(mi.MethodHandle);
IntPtr md = mi.MethodHandle.Value; // MethodDescriptor address
IntPtr mt = mi.DeclaringType.TypeHandle.Value; // MethodTable address
if (mi.IsVirtual)
{
// The fixed-size portion of the MethodTable structure depends on the process type
int offset = IntPtr.Size == 4 ? MT_OFFSET_32BIT : MT_OFFSET_64BIT;
// First method slot = MethodTable address + fixed-size offset
// This is the address of the first method of any type (i.e. ToString)
IntPtr ms = Marshal.ReadIntPtr(mt + offset);
// Get the slot number of the virtual method entry from the MethodDesc data structure
long shift = Marshal.ReadInt64(md) >> 32;
int slot = (int)(shift & SLOT_NUMBER_MASK);
// Get the virtual method address relative to the first method slot
address = ms + (slot * IntPtr.Size);
}
else
{
// Bypass default MethodDescriptor padding (8 bytes)
// Reach the CodeOrIL field which contains the address of the JIT-compiled code
address = md + 8;
}
return address;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个简单的用法示例:
using System;
using System.Reflection;
namespace MethodRedirect
{
class Scenario
{
static void Main(string[] args)
{
Assembly assembly = Assembly.GetAssembly(typeof(Scenario));
Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario");
MethodInfo Scenario_InternalInstanceMethod = Scenario_Type.GetMethod("InternalInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic);
MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic);
Scenario_InternalInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod);
// Using dynamic type to prevent method string caching
dynamic scenario = (Scenario)Activator.CreateInstance(Scenario_Type);
bool result = scenario.InternalInstanceMethod() == "PrivateInstanceMethod";
Console.WriteLine("\nRedirection {0}", result ? "SUCCESS" : "FAILED");
Console.ReadKey();
}
internal string InternalInstanceMethod()
{
return "InternalInstanceMethod";
}
private string PrivateInstanceMethod()
{
return "PrivateInstanceMethod";
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是从我在 Github 上提供的一个更详细的项目(MethodRedirect)中提炼出来的。
备注:该代码是使用 .NET Framework 4 实现的,尚未在较新版本的 .NET 上进行测试。
我知道这不是您问题的确切答案,但通常的方法是使用工厂/代理方法。
首先我们声明一个基类型。
public class SimpleClass
{
public virtual DTask<bool> Solve(int n, DEvent<bool> callback)
{
for (int m = 2; m < n - 1; m += 1)
if (m % n == 0)
return false;
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们可以声明一个派生类型(称为代理)。
public class DistributedClass
{
public override DTask<bool> Solve(int n, DEvent<bool> callback)
{
CodeToExecuteBefore();
return base.Slove(n, callback);
}
}
// At runtime
MyClass myInstance;
if (distributed)
myInstance = new DistributedClass();
else
myInstance = new SimpleClass();
Run Code Online (Sandbox Code Playgroud)
派生类型也可以在运行时生成。
public static class Distributeds
{
private static readonly ConcurrentDictionary<Type, Type> pDistributedTypes = new ConcurrentDictionary<Type, Type>();
public Type MakeDistributedType(Type type)
{
Type result;
if (!pDistributedTypes.TryGetValue(type, out result))
{
if (there is at least one method that have [Distributed] attribute)
{
result = create a new dynamic type that inherits the specified type;
}
else
{
result = type;
}
pDistributedTypes[type] = result;
}
return result;
}
public T MakeDistributedInstance<T>()
where T : class
{
Type type = MakeDistributedType(typeof(T));
if (type != null)
{
// Instead of activator you can also register a constructor delegate generated at runtime if performances are important.
return Activator.CreateInstance(type);
}
return null;
}
}
// In your code...
MyClass myclass = Distributeds.MakeDistributedInstance<MyClass>();
myclass.Solve(...);
Run Code Online (Sandbox Code Playgroud)
唯一的性能损失是在派生对象的构造过程中,第一次非常慢,因为它将使用大量反射和反射发射。在其他情况下,它是并发表查找和构造函数的成本。如前所述,您可以使用优化构造
ConcurrentDictionary<Type, Func<object>>.
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
48173 次 |
最近记录: |