[转载]Modifying IL at runtime

简介: 原文出处:http://blog.monstuff.com/If you remember the Omniscient Debugger, it was a Java debugger that instrumented the bytecode at runtime to trace calls and monitor variables.

原文出处:http://blog.monstuff.com/

If you remember the Omniscient Debugger, it was a Java debugger that instrumented the bytecode at runtime to trace calls and monitor variables. It did so by using a custom ClassLoader.
Unfortunately the .NET classes that seemed somewhat equivalent to the Java ClassLoader are sealed, so they can't be extended. So, for a while I thought runtime instrumentation of the code wasn't possible in .NET...

A couple weeks later, I stumbled onto the NProf (open-source .NET profiler) project and wondered how they did their magic. It turns out they use the CLR Profiling APIs which are COM based and allow you to hook up into various events and get information on the runtime. It is while digging some more into these that I first found a mention of the intriguing ICorProfilerInfo::SetILFunctionBody method.

Although I still think that it is not very well documented (no MSDN reference and very few hits in Google), I have since found bits and pieces of information about this method and wrote a little program that demos its potential.

In this article, we'll go through the steps to build this simple runtime IL transformation program, to give you a better feel of what Get/SetILFunctionBody allows you to do.

Update: Follow-up articles are available (step II, step II+, step III).

Background information on the Profiling APIs
First you should have a little background on the Profiling APIs of the framework.

The SDK comes with a Tool Developers Guide. It's a directory with various documents, including the precious Profiling.doc file. Since I don't have Word on all my machines, I converted it to pdf and copied it over: CLR Profiling (Tool Developers Guide).

Two "Under the Hood" articles on MSDNMag, about the Profiling APIs:
The .NET Profiling API and the DNProfiler Tool and NET CLR Profiling Services: Track Your Managed Components to Boost Application Performance.


DNProfiler
I used the DNProfiler tool by Matt Pietrek as the foundation for the experiment. You can grab it on the MSDNMag page mentioned above.

You should be able to build DNProfiler with VS.net and run it easily. Try it on a couple simple .NET programs and look at the generated DNProfiler.out file, that contains the output of all ProfilerPrintf calls. You'll see the flood of events that the most simple program can generate.

It turns out that the main event that we'll need is JITCompilationStarted, so you can empty most of the other event methods (leave Initialize as it is, though).

Also, you don't need to receive notification from the CLR for all the events, so you can modify the profiling_on.bat batch to have "set DN_PROFILER_MASK=0x20", where 0x20 means COR_PRF_MONITOR_JIT_COMPILATION. This will tell the CLR to call all the JIT related hook functions in our profiler.


GetILFunctionBody
When the foundation is laid and we have a running profiler with a JITCompilationStarted method that gets called, we can start looking at the live IL as it gets JITed.

The ICorProfilerInfo::GetILFunctionBody allows you to do that.

Here is the code I used:

HRESULT CProfilerCallback::JITCompilationStarted(UINT functionId,
      BOOL fIsSafeToBlock)
{
  wchar_t wszClass[512];
  wchar_t wszMethod[512];

 

  // Uncomment the next line to set a breakpoint
  // __asm int 3

  HRESULT hr = S_OK;

  ClassID classId = 0;
  ModuleID moduleId = 0;
  mdToken tkMethod = 0;
  LPCBYTE pMethodHeader = NULL;
  ULONG iMethodSize = 0;


  //
  // Get the name of the method that is going to get JITed
  //
  if (GetMethodNameFromFunctionId(functionId, wszClass, wszMethod))
  {
   ProfilerPrintf("JITCompilationStarted: %ls::%ls\n",wszClass,wszMethod);
  } else {
   ProfilerPrintf("JITCompilationStarted\n");
  }


  //
  // Get the IL
  //
  hr = m_pICorProfilerInfo->GetFunctionInfo(functionId, &classId, &moduleId, &tkMethod );
  if (FAILED(hr))
   { goto exit; }

  hr = m_pICorProfilerInfo->GetILFunctionBody(moduleId, tkMethod, &pMethodHeader, &iMethodSize);
  if (FAILED(hr))
   { goto exit; }


  //
  // Look at the IL and print it out
  //
  IMAGE_COR_ILMETHOD* pMethod = (IMAGE_COR_ILMETHOD*)pMethodHeader;
  COR_ILMETHOD_FAT* fatImage = (COR_ILMETHOD_FAT*)&pMethod->Fat;

  if(!fatImage->IsFat()) {
   COR_ILMETHOD_TINY* tinyImage = (COR_ILMETHOD_TINY*)&pMethod->Tiny;
   //Handle Tiny method
  } else {
   //Handle Fat method
   ProfilerPrintf("Flags: %X\n", fatImage->Flags);
   ProfilerPrintf("Size: %X\n", fatImage->Size);
   ProfilerPrintf("MaxStack: %X\n", fatImage->MaxStack);
   ProfilerPrintf ("CodeSize: %X\n", fatImage->CodeSize);
   ProfilerPrintf("LocalVarSigTok: %X\n", fatImage->LocalVarSigTok);

   byte* codeBytes = fatImage->GetCode();
   ULONG codeSize = fatImage->CodeSize;

   for(ULONG i = 0; i < codeSize; i++) {
    if(codeBytes[i] > 0x0F) {
     ProfilerPrintf("codeBytes[%u] = 0x%X;\n", i, codeBytes[i]);
    } else {
     ProfilerPrintf("codeBytes[%u] = 0x0%X;\n", i, codeBytes[i]);
    }
   }
  }

exit:
  return hr;
}

 

This code is based on the original DNProfiler method and has code pieces from this entry and this entry from Jimski's blog.

You'll need to #include "corhlpr.h" to get access to the type definitions like COR_ILMETHOD_FAT.

The sample Hello.cs file (compiled with "csc Hello.cs"):

using System;

 

public class Hello
{
  public static void Main(string[] prms)
  {
   Console.WriteLine("hello world!");
   Console.WriteLine("test!");
  }
}

 


This brings the following DNProfiler.out file:

Initialize
JITCompilationStarted: Hello::Main
Flags: 13
Size: 3
MaxStack: 1
CodeSize: 15
LocalVarSigTok: 0
codeBytes[0] = 0x72;
codeBytes[1] = 0x01;
codeBytes[2] = 0x00;
codeBytes[3] = 0x00;
codeBytes[4] = 0x70;
codeBytes[5] = 0x28;
codeBytes[6] = 0x02;
codeBytes[7] = 0x00;
codeBytes[8] = 0x00;
codeBytes[9] = 0x0A;
codeBytes[10] = 0x72;
codeBytes[11] = 0x1B;
codeBytes[12] = 0x00;
codeBytes[13] = 0x00;
codeBytes[14] = 0x70;
codeBytes[15] = 0x28;
codeBytes[16] = 0x02;
codeBytes[17] = 0x00;
codeBytes[18] = 0x00;
codeBytes[19] = 0x0A;
codeBytes[20] = 0x2A;
Shutdown

 


If you run "ildasm /bytes Hello.exe", you'll see the matching bytes in the dis-assembled version of the Main method. ILdasm will give you more insight on what the bytes actually mean and how they are grouped.
The comparison of the output and the ILdasm dis-assembly suggests that switching codeBytes[1] and codeBytes[11] could lead to printing the strings in the reverse order. That's what we'll try and do :-)


SetILFunctionBody
Here is the code I used to switch the two string prints in Hello.exe:

HRESULT CProfilerCallback::JITCompilationStarted(UINT functionId,
      BOOL fIsSafeToBlock)
{
  wchar_t wszClass[512];
  wchar_t wszMethod[512];

 

  //__asm int 3
  HRESULT hr = S_OK;

  ClassID classId = 0;
  ModuleID moduleId = 0;
  mdToken tkMethod = 0;
  LPCBYTE pMethodHeader = NULL;
  ULONG iMethodSize = 0;

  if ( GetMethodNameFromFunctionId( functionId, wszClass, wszMethod ) )
  {
   ProfilerPrintf("JITCompilationStarted: %ls::%ls\n",wszClass,wszMethod);
  } else {
   ProfilerPrintf( "JITCompilationStarted\n" );
   goto exit;
  }
  if (wcscmp(wszClass, L"Hello") != 0 || wcscmp(wszMethod, L"Main") != 0) {
   goto exit;
  }


  //
  // Get the existing IL
  //
  hr = m_pICorProfilerInfo->GetFunctionInfo(functionId, &classId, &moduleId, &tkMethod );
  if (FAILED(hr))
   { goto exit; }

  hr = m_pICorProfilerInfo->GetILFunctionBody(moduleId, tkMethod, &pMethodHeader, &iMethodSize);
  if (FAILED(hr))
   { goto exit; }


  //
  // Print the existing IL
  //
  IMAGE_COR_ILMETHOD* pMethod = (IMAGE_COR_ILMETHOD*)pMethodHeader;
  COR_ILMETHOD_FAT* fatImage = (COR_ILMETHOD_FAT*)&pMethod->Fat;

  if(!fatImage->IsFat()) {
   COR_ILMETHOD_TINY* tinyImage = (COR_ILMETHOD_TINY*)&pMethod->Tiny;
   //Handle Tiny method
  } else {
   //Handle Fat method
   ProfilerPrintf("Flags: %X\n", fatImage->Flags);
   ProfilerPrintf("Size: %X\n", fatImage->Size);
   ProfilerPrintf("MaxStack: %X\n", fatImage->MaxStack);
   ProfilerPrintf ("CodeSize: %X\n", fatImage->CodeSize);
   ProfilerPrintf("LocalVarSigTok: %X\n", fatImage->LocalVarSigTok);

   byte* codeBytes = fatImage->GetCode();
   ULONG codeSize = fatImage->CodeSize;

   for(ULONG i = 0; i < codeSize; i++) {
    if(codeBytes[i] > 0x0F) {
     ProfilerPrintf("codeBytes[%u] = 0x%X;\n", i, codeBytes[i]);
    } else {
     ProfilerPrintf("codeBytes[%u] = 0x0%X;\n", i, codeBytes[i]);
    }
   }
  }


  //
  // Get the IL Allocator
  //
  IMethodMalloc* pIMethodMalloc = NULL;
  IMAGE_COR_ILMETHOD* pNewMethod = NULL;
  hr = m_pICorProfilerInfo->GetILFunctionBodyAllocator(moduleId, &pIMethodMalloc);
  if (FAILED(hr))
   { goto exit; }


  //
  // Allocate IL space and copy the IL in it
  //
  pNewMethod = (IMAGE_COR_ILMETHOD*) pIMethodMalloc->Alloc(iMethodSize);
  if (pNewMethod == NULL)
   { goto exit; }

  memcpy((void*)pNewMethod, (void*)pMethod, iMethodSize);


  //
  // Print IL copy, modify it and print it again
  //
  COR_ILMETHOD_FAT* newFatImage = (COR_ILMETHOD_FAT*)&pNewMethod->Fat;
  if(!newFatImage->IsFat()) {
   COR_ILMETHOD_TINY* newTinyImage = (COR_ILMETHOD_TINY*)&pNewMethod->Tiny;
   //Handle Tiny method
  } else {
   //Handle Fat method
   ProfilerPrintf("New Flags: %X\n", newFatImage->Flags);
   ProfilerPrintf("New Size: %X\n", newFatImage->Size);
   ProfilerPrintf("New MaxStack: %X\n", newFatImage->MaxStack);
   ProfilerPrintf ("New CodeSize: %X\n", newFatImage->CodeSize);
   ProfilerPrintf("New LocalVarSigTok: %X\n", newFatImage->LocalVarSigTok);

   byte* codeBytes = newFatImage->GetCode();
   ULONG codeSize = newFatImage->CodeSize;

   for(ULONG i = 0; i < codeSize; i++) {
    if(codeBytes[i] > 0x0F) {
     ProfilerPrintf("codeBytes[%u] = 0x%X;\n", i, codeBytes[i]);
    } else {
     ProfilerPrintf("codeBytes[%u] = 0x0%X;\n", i, codeBytes[i]);
    }
   }


   //
   // Tweak the IL (switch the bytes)
   //
   BYTE temp;
   temp = codeBytes[1];
   codeBytes[1] = codeBytes[11];
   codeBytes[11] = temp;


   //
   // Print the modified IL
   //
   for(ULONG i = 0; i < codeSize; i++) {
    if(codeBytes[i] > 0x0F) {
     ProfilerPrintf("codeBytes[%u] = 0x%X;\n", i, codeBytes[i]);
    } else {
     ProfilerPrintf("codeBytes[%u] = 0x0%X;\n", i, codeBytes[i]);
    }
   }
  }


  hr = m_pICorProfilerInfo->SetILFunctionBody(moduleId, tkMethod, (LPCBYTE) pNewMethod);
  if (FAILED(hr))
   { goto exit; }


  pIMethodMalloc->Release();

exit:
  return hr;
}

 

If you run Hello.exe with this profiler, you'll get "Test!" then "Hello World!", which confirms that the IL was modified. Success !!
You'll notice that it is completely hardcoded for the current Hello.exe example, so you shouldn't try it on other assemblies.

If you try to tweak the IL that you got out of GetILFunctionBody, you'll get an access violation because it is read-only. This is why we first make a copy of it, then tweak it and finally set it back in with SetILFunctionBody.


目录
相关文章
|
8月前
|
Java 开发工具 计算机视觉
Caused by: java.lang.UnsatisfiedLinkError: Can‘t load AMD 64-bit .dll on a IA 32-bit platform【已解决】
Caused by: java.lang.UnsatisfiedLinkError: Can‘t load AMD 64-bit .dll on a IA 32-bit platform【已解决】
91 0
sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.221-b11. Target VM
今天在查看 JVM 堆内存具体使用情况的时候,执行 jmap 命令发现如下的报错信息,报错说的是虚拟机版本不匹配,感觉很奇怪因为我刚在另外一台机器上执行了这个命令,换了一台机器就执行报错,初步判断是机器环境的问题,最后对比了两个机器的 JDK 环境,发现报错的这台机器上有两个不同版本的 JDK 所以推测跟多个版本有关系.
MAC编译OpenJDK8:error: ‘&&‘ within ‘||‘ [-Werror,-Wlogical-op-parentheses]
MAC编译OpenJDK8:error: ‘&&‘ within ‘||‘ [-Werror,-Wlogical-op-parentheses]
105 0
编译OpenJDK12:Target CPU mismatch. We are building for x86_64 but CL is for ""; expected "x64"
编译OpenJDK12:Target CPU mismatch. We are building for x86_64 but CL is for ""; expected "x64"
124 0
编译OpenJDK8:Target CPU mismatch. We are building for x86_64 but CL is for ""; expected "x64"
编译OpenJDK8:Target CPU mismatch. We are building for x86_64 but CL is for ""; expected "x64"
115 0
|
缓存 Java
f you already have a JDK installed, define a JAVA HOME variable in Computer > System Properties > Sy
f you already have a JDK installed, define a JAVA HOME variable in Computer > System Properties > Sy
1140 0
f you already have a JDK installed, define a JAVA HOME variable in Computer > System Properties > Sy
|
Windows
如何通过ildas“.NET技术”m/ilasm修改assembly的IL代码
  这段时间为跟踪一个Bug而焦头烂额,最后发现是Framework的问题,这让人多少有些绝望。所以到微软论坛提了个帖子,希望能得到些帮助。虽然论坛智能到能够判断楼主是否是MSDN订阅用户,以便尽快解决(传说MSDN订阅用户的问题能在两天内得到回复的,当时还很得意公司为我们购买的MSDN订阅账号),但得到的回复是“Could you file a bug report for this issue through Connect?”,绝望之后的又一次寒心啊。
1026 0
|
开发工具
The EF Core tools version '2.1.1-rtm-30846' is older than that of the runtime '2.1.3-rtm-32065'. ...
The EF Core tools version '2.1.1-rtm-30846' is older than that of the runtime '2.
2569 0
|
存储 JSON 数据格式