your programing

C # 메서드의 내용을 동적으로 바꾸시겠습니까?

lovepro 2020. 10. 8. 08:27
반응형

C # 메서드의 내용을 동적으로 바꾸시겠습니까?


내가 원하는 것은 호출 될 때 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;
}

런타임에 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;
}

그러나 MethodRental.SwapMethodBody는 동적 모듈에서만 작동합니다. 어셈블리에 이미 컴파일 및 저장된 항목이 아닙니다.

그래서 이미로드되고 실행중인 어셈블리에 저장된 메서드에서 SwapMethodBody를 효과적으로 수행하는 방법을 찾고 있습니다.

메서드를 동적 모듈에 완전히 복사해야하는 경우 문제가되지 않지만이 경우 IL을 통해 복사하는 방법을 찾고 Solve ()에 대한 모든 호출을 업데이트해야합니다. 새 사본을 가리킬 것입니다.


.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");
        }
    }

}

Harmony 는 런타임 중에 모든 종류의 기존 C # 메서드를 대체, 장식 또는 수정하도록 설계된 오픈 소스 라이브러리입니다. 주요 초점은 Mono로 작성된 게임과 플러그인 이지만이 기술은 모든 .NET 버전에서 사용할 수 있습니다. 또한 동일한 메서드에 대한 여러 변경 사항을 처리합니다 (덮어 쓰는 대신 누적 됨).

모든 원본 메서드에 대해 DynamicMethod 유형의 메서드를 만들고 시작 및 끝에서 사용자 지정 메서드를 호출하는 코드를 내 보냅니다. 또한 원래 방법을보다 자세하게 조작 할 수 있도록 원래 IL 코드를 처리하는 필터를 작성할 수 있습니다.

프로세스를 완료하기 위해 동적 메서드를 컴파일하여 생성 된 어셈블러를 가리키는 원래 메서드의 트램폴린으로 간단한 어셈블러 점프를 작성합니다. Windows, macOS 및 Mono가 지원하는 모든 Linux의 32 / 64Bit에서 작동합니다.


런타임에 메소드의 내용을 수정할 수 있습니다. 하지만 그렇게해서는 안되며 테스트 목적으로 보관하는 것이 좋습니다.

다음을 살펴보십시오.

http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time

기본적으로 다음을 수행 할 수 있습니다.

  1. MethodInfo.GetMethodBody (). GetILAsByteArray ()를 통해 IL 메서드 콘텐츠 가져 오기
  2. 이 바이트와 엉망입니다.

    일부 코드를 앞에 추가하거나 추가하려면 원하는 opcode를 미리 추가 / 추가하십시오 (하지만 스택을 깨끗하게 유지하는 데주의하십시오).

    다음은 기존 IL을 "컴파일 해제"하는 몇 가지 팁입니다.

    • 반환되는 바이트는 일련의 IL 명령어와 그 뒤에 해당 인수입니다 (예를 들어 '.call'에는 하나의 인수가 있습니다. 호출 된 메서드 토큰이고 '.pop'에는 없음)
    • 반환 된 배열에서 찾은 IL 코드와 바이트 간의 대응은 OpCodes.YourOpCode.Value (어셈블리에 저장된 실제 opcode 바이트 값)를 사용하여 찾을 수 있습니다.
    • IL 코드 뒤에 추가 된 인수는 호출 된 opcode에 따라 크기가 다를 수 있습니다 (1 바이트에서 여러 바이트까지).
    • 이러한 인수가 적절한 방법을 통해 참조하는 토큰을 찾을 수 있습니다. 예를 들어, IL에 ".call 354354"(헥사에서 28 00 05 68 32로 코딩 됨, 28h = 40은 '.call'opcode 및 56832h = 354354로 코딩 됨)가 포함 된 경우 MethodBase.GetMethodFromHandle (354354)을 사용하여 해당 호출 메서드를 찾을 수 있습니다. )
  3. 수정되면 IL 바이트 배열은 InjectionHelper.UpdateILCodes (MethodInfo method, byte [] ilCodes)를 통해 다시 주입 할 수 있습니다.

    이것은 "안전하지 않은"부분입니다 ... 잘 작동하지만 내부 CLR 메커니즘을 해킹하는 것입니다 ...


메소드가 가상이 아니고 일반이 아니고 일반 유형이 아니고 인라인되지 않고 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.

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;
        }
    }
}

런타임에 모든 메서드를 동적으로 변경할 수있는 몇 가지 프레임 워크가 있습니다 (사용자 152949에서 언급 한 ICLRProfiling 인터페이스 사용).

또한 .NET의 내부를 조롱하는 몇 가지 프레임 워크가 있습니다. 이러한 프레임 워크는 더 취약 할 가능성이 높고 인라인 코드를 변경할 수 없지만 다른 한편으로는 완전히 독립적이며 사용자가 a를 사용할 필요가 없습니다. 커스텀 런처.

  • Harmony : MIT 라이센스. 실제로 몇 가지 게임 모드에서 성공적으로 사용 된 것으로 보이며 .NET과 Mono를 모두 지원합니다.
  • Deviare In Process Instrumentation Engine : GPLv3 및 Commercial. .NET 지원은 현재 실험적인 것으로 표시되어 있지만 반면에 상업적으로 지원된다는 이점이 있습니다.

ICLRPRofiling 인터페이스 를 사용하여 런타임에 메서드를 바꿀 수 있습니다 .

  1. AttachProfiler호출 하여 프로세스에 연결합니다.
  2. SetILFunctionBody호출 하여 메서드 코드를 바꿉니다 .

자세한 내용은이 블로그 를 참조하십시오.


귀하의 질문에 대한 정확한 대답은 아니지만 일반적인 방법은 공장 / 프록시 접근 방식을 사용하는 것입니다.

먼저 기본 유형을 선언합니다.

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;
    }
}

그런 다음 파생 유형을 선언 할 수 있습니다 (프록시라고 함).

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();

파생 된 유형은 런타임에 생성 될 수도 있습니다.

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(...);

유일한 성능 손실은 파생 된 오브젝트를 구성하는 동안입니다. 처음에는 많은 반사와 반사 방출을 사용하기 때문에 상당히 느립니다. 다른 모든 경우에는 동시 테이블 조회 및 생성자의 비용입니다. 말했듯이 다음을 사용하여 시공을 최적화 할 수 있습니다.

ConcurrentDictionary<Type, Func<object>>.

이 질문과 다른 질문에 대한 답변을 바탕으로 ive는 다음과 같은 깔끔한 버전을 만들었습니다.

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
        }
    }

참고 URL : https://stackoverflow.com/questions/7299097/dynamically-replace-the-contents-of-ac-sharp-method

반응형