[转载]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.


目录
相关文章
java.lang.Error: Unresolved compilation problem: The type List is not generic; it cannot be parame
java.lang.Error: Unresolved compilation problem: The type List is not generic; it cannot be parame
sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.221-b11. Target VM
今天在查看 JVM 堆内存具体使用情况的时候,执行 jmap 命令发现如下的报错信息,报错说的是虚拟机版本不匹配,感觉很奇怪因为我刚在另外一台机器上执行了这个命令,换了一台机器就执行报错,初步判断是机器环境的问题,最后对比了两个机器的 JDK 环境,发现报错的这台机器上有两个不同版本的 JDK 所以推测跟多个版本有关系.
|
Android开发 Kotlin
kotlin协程库报错“Program type already present”解决
最近在学习kotlin,学习到协程库这一块了,针对Android的话就是coroutines-android库。本来学习就不容易了,再加上kotlin现在还处于快速变化期,那个酸爽简直了,废话不多说,进入正题。
编译OpenJDK11:configure: error: Target CPU mismatch. We are building for x86_64 but CL is for “版“; exp
编译OpenJDK11:configure: error: Target CPU mismatch. We are building for x86_64 but CL is for “版“; exp
157 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"
109 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"
103 0
解决g++编译C++报错unresolved external... (undefined reference ...)
解决g++编译C++报错unresolved external... (undefined reference ...)
294 0
解决g++编译C++报错unresolved external... (undefined reference ...)
|
Java 应用服务中间件 编译器
Java: Unresolved compilation problem的解决方法
Java: Unresolved compilation problem的解决方法
Java: Unresolved compilation problem的解决方法
How is dependent libraries defined in metadata loaded in the runtime
How is dependent libraries defined in metadata loaded in the runtime
143 0
How is dependent libraries defined in metadata loaded in the runtime
SAP ABAP DDICSAP ABAP DDIC table runtime object table runtime object
SAP ABAP DDICSAP ABAP DDIC table runtime object table runtime object
108 0
SAP ABAP DDICSAP ABAP DDIC table runtime object table runtime object