鲲鹏 发表于 2024-12-30 21:32:28

.NET笔记:挂钩JIT获取编译的方法句柄、本机代码地址和部分方法名称

本帖最后由 鲲鹏 于 2024-12-30 21:32 编辑

流程如下。

1. 挂钩JIT,获取`methodInfo`结构体中的`ftn`成员,该成员有关方法句柄原始数据。获取`entryAddress`参数,该参数有关本机代码地址。
2. 将方法句柄原始数据转为`RuntimeMethodHandle`。需要注意的是,仅符合条件的部分方法可以完成转换。如泛型等会引发异常。
3. 根据`RuntimeMethodHandle`得到`MethodBase`。此时已经可以通过`Name`属性获取方法名称。
4. (可选)使用`dnlib`获取更为详细的方法名称。

不妨使用C++/CLI和C#混合编程。首先使用C++/CLI编写挂钩JIT。我们选取`compileMethod`位置进行挂钩,定位该位置可通过符号文件或`getJit`虚表等多种方式实现,这里不再赘述。目标方法为非托管。
#pragma managed(push, off)
int __stdcall Hook_compileMethod(void *self, void *compHnd, void *methodInfo, unsigned int flags,
                                 unsigned char **entryAddress, unsigned long *nativeSizeOfCode)
{
    thread_local int recursionFlag = 0;

    int retval = Orig_compileMethod(self, compHnd, methodInfo, flags, entryAddress, nativeSizeOfCode);

    if (recursionFlag)
      return retval;

    if (retval == 0)
    {
      // 获取`methodInfo`结构体中的`ftn`成员,该成员有关方法句柄原始数据。
      void *methodInfo_ftn = *(void **)methodInfo;

      // 获取`entryAddress`参数,该参数有关本机代码地址。
      printf("%p\n", *entryAddress);

      recursionFlag++;
      FuncInternal(methodInfo_ftn);
      recursionFlag--;
    }
    return retval;
}
#pragma managed(pop)

其中,`FuncInternal`完成方法名称输出工作。该方法为托管。为了演示C++/CLI和C#混合编程的便捷性,我们调用了C#编写的`Func`方法。
#pragma managed(push, on)
void FuncInternal(void *ptr)
{
    IntPtr methodHandleValue = IntPtr(ptr);
    ClassLibrary1::Class1::Func(methodHandleValue);
}
#pragma managed(pop)

C#代码如下。`GetRuntimeMethodHandle`将方法句柄原始数据转为`RuntimeMethodHandle`。`Func`输出方法名称。
namespace ClassLibrary1
{
    public class Class1
    {
      public static RuntimeMethodHandle GetRuntimeMethodHandle(IntPtr methodHandleValue)
      {
            var asm = typeof(RuntimeMethodHandle).Assembly;
            var method = asm.CreateInstance("System.RuntimeMethodInfoStub",
                                          false,
                                          BindingFlags.Instance | BindingFlags.Public,
                                          null,
                                          new object[] { methodHandleValue, null },
                                          null,
                                          null);
            var handle = (RuntimeMethodHandle)asm.CreateInstance("System.RuntimeMethodHandle",
                                                               false,
                                                               BindingFlags.Instance | BindingFlags.NonPublic,
                                                               null,
                                                               new object[] { method },
                                                               null,
                                                               null);
            return handle;
      }

      public static void Func(IntPtr methodHandleValue)
      {
            try
            {
                // 将方法句柄原始数据转为`RuntimeMethodHandle`。
                var handle = GetRuntimeMethodHandle(methodHandleValue);
                var mb = MethodBase.GetMethodFromHandle(handle);
                // 通过`Name`属性获取方法名称。
                Console.WriteLine(mb.Name);

                // (可选)使用`dnlib`获取更为详细的方法名称。
                ModuleDefMD moduleDefMD = ModuleDefMD.Load(mb.Module);
                MethodDef methodDef = (MethodDef)moduleDefMD.ResolveToken(mb.MetadataToken);
                Console.WriteLine(methodDef.FullName);
            }
            catch
            {
                // 需要注意的是,仅符合条件的部分方法可以完成转换。如泛型等会引发异常。
            }
      }
    }
}



不破不立 发表于 2024-12-31 08:40:41

感谢分享思路

zixuan203344 发表于 2024-12-31 16:25:47

每个字都认识,连起来一起读就闷逼了https://cdn.jsdelivr.net/gh/master-of-forums/master-of-forums/public/images/patch.gif

xiaomils 发表于 2025-1-1 09:54:43

楼主厉害,向你学习

飞天梦 发表于 2025-1-1 15:05:45

楼主厉害

飞天梦 发表于 2025-1-1 15:06:07

楼主厉害

如烟往事 发表于 2025-1-2 17:09:34


感谢发布原创作品,PYG有你更精彩!

杨林 发表于 2025-1-4 00:01:32

学习了,感谢提供分享!

那伤你得给 发表于 2025-1-7 08:36:12

        PYG有你更精彩!
页: [1]
查看完整版本: .NET笔记:挂钩JIT获取编译的方法句柄、本机代码地址和部分方法名称