Source: http://blog.monstuff.com/
If you remember the omniscient debugger, it was a Java debugger that instrumented the bytecode at runtime to trace CILS and monitor variables. It did so by using a custom classloader.
Unfortunately. 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 varous events and get information on the runtime. it is while digging some more into these that I first found a mention of the intriguingIcorprofilerinfo: setilfunctionbodyMethod.
Although I still think that it is not very well known ented (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 whatGET/setilfunctionbodyAllows you to do.
Update:Follow-up articles are available (Step II, Step II +, step III ).
Background information on the profiling APIs
First you shoshould have a little background on the profiling APIs of the framework.
The SDK comes withTool developers Guide. It's a directory with varous documents, including the preciousProfiling.docFile. 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 shoshould be able to build dnprofiler with vs.net and run it easily. Try it on a couple simple. net programs and look at the generatedDnprofiler. OutFile, that contains the output of allProfilerprintfCILS. 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 isJitcompilationstarted, So you can empty most of the other event methods (leaveInitializeAs it is, though ).
Also, you don't need to receive notification from the CLR for all the events, so you can modifyProfiling_on.batBatch 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 withJitcompilationstartedMethod that gets called, we can start looking at the live Il as it gets jited.
TheIcorprofilerinfo: getilfunctionbodyAllows 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 = 0;
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 \ 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# 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 ofMainMethod. 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] cocould 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 = 0;
Moduleid = 0;
Mdtoken tkmethod = 0;
Lpcbyte pmethodheader = NULL;
Ulong imethodsize = 0;
If (getmethodnamefromfunctionid (functionid, wszclass, wszmethod ))
{
Profilerprintf ("jitcompilationstarted: % 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 outGetilfunctionbody, 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 inSetilfunctionbody.