Printer Friendly Version      Send     
Click to Rate and Give Feedback
Related Articles
We introduce you to the benefits of building composite applications with the Composite Application Guidance for WPF from Microsoft patterns & practices.

By Glenn Block (September 2008)
ADO.NET Data Services provide Web-accessible endpoints that allow you to filter, sort, shape, and page data without having to build that functionality yourself.

By Shawn Wildermuth (September 2008)
See how routed events and routed commands in Windows Presentation Foundation form the basis for communication between the parts of your UI.

By Brian Noyes (September 2008)
Technology changes at a lightening-fast pace. This month Howard Dierking considers how the rapid changes affect developer priorities and magazine focus.

By Howard Dierking (September 2008)
More ...
Articles by this Author
Enterprise Library is a collection of application functionality blocks that you can re-use in your application for common functionality you'd otherwise have to write again and again. Here Jay Hilyard explains how to use them.

By Jay Hilyard (September 2006)
Developers using .NET often make memory leak tracking a low priority because the common language runtime takes care of garbage collection. What few developers realize, however, is that their objects' lifespans, along with their size and what other objects have been instantiated, all affect how they are cleaned up. Depending on the particular circumstances, these combinations can negatively affect performance, especially over the lifetime of an application. This article presents a way for developers to see memory usage and understand garbage collection using the .NET Profiler API. Along the way, a sample application to demonstrate these principles is built.

By Jay Hilyard (January 2003)
More ...
Popular Articles
Here the author uses Document Information Panels in the Microsoft 2007 Office system to manipulate metadata from Office docs for better discovery and management.

By Ashish Ghoda (April 2008)
We build a Silverlight 2.0 application using the InkPresenter to let users annotate a pre-defined collection of images, perform handwriting recognition, and save the annotations and recognized text into a server-side database.

By Julia Lerman (August 2008)
Systems that handle failure without losing data are elusive. Learn how to achieve systems that are both scalable and robust.

By Udi Dahan (July 2008)
Howard Dierking talks to the inventor of C++, Bjarne Stroustrup, about language zealots, the evolution of programming, and what’s in the future of programming.

By Howard Dierking (April 2008)
More ...
Read the Blog
SQL Server 2008 supports a new data type, HierarchyID, that helps solve some of the problems in modeling and querying hier­archical information. In the September 2008 issue of MSDN Magazine, Kent Tegels introduces you to the ...
Read more!
Many people using SharePoint technologies don't realize that there is auditing support built directly into the Windows SharePoint Services (WSS) 3.0 platform. In the September 2008 issue of MSDN Magazine, Ted Pattison walks you through a ...
Read more!
The September 2008 issue of MSDN Magazine is now available online. Here's what's in the issue: Hierarchy ID: Model ...
Read more!
Silverlight 2 features a rich and robust control model that is the basis for the controls included in the platform and for third-party control packages. You can also use this control model to build controls of your own. In the August 2008 issue of MSDN Magazine, Jeff Prosise describes how to ...
Read more!
In the August 2008 issue of MSDN Magazine, Matt Milner covers several topics regarding development with Windows Workflow Foundation, some that are intended to address specific reader questions, such as how to safely share a persistence database ...
Read more!
LINQ is a powerful tool enabling quick filtering data based on a standard query language. It can tear through a structured set of data using a simple and straightforward syntax. In the August 2008 issue of MSDN Magazine, Jared Parsons demonstrates a ...
Read more!
More ...
New information has been added to this article since publication.
Refer to the Editor's Update below.

CLR Profiler
No Code Can Hide from the Profiling API in the .NET Framework 2.0
Jay Hilyard

This article is based on a prerelease version of the .NET Framework 2.0, formerly code-named "Whidbey." All information contained herein is subject to change.
This article discusses:
  • Looking into the .NET runtime
  • More detailed function tracing
  • Better thread and call stack information
  • The phase-out of in-process debugging
This article uses the following technologies:
The .NET Framework
Since the inception of .NET, the common language runtime (CLR) profiling API has been the mechanism to use to inspect what the runtime is doing under the covers. Many profilers simply report how much time is spent in a given routine, file, or class, but the profiling API is much more complete in the amount and types of data it makes available. Information about the application domains, assemblies, and classes that are loaded and used in a process, just-in-time (JIT) compiler notifications, memory usage tracking, tracing of events, exception tracking, managed to unmanaged code transitions, and the state of the runtime are all categories of information available to a profiler written for the .NET Framework 1.0 and 1.1 using the profiling API.
You will find a nicely enhanced profiling API in the .NET Framework 2.0 Beta 1. In this article, I will discuss why the changes were made and will describe how developers can take advantage of them.
If you have not worked with the profiling API before, let me explain its capabilities before I start discussing the new enhancements. First and foremost, using the profiling API requires an unmanaged COM server DLL that implements the ICorProfilerCallback interface. In addition, you need to set up two environment variables (COR_PROFILER and COR_ENABLE_PROFILING) to let the CLR know that it should load your DLL as a profiler. COR_PROFILER is set to the class ID (CLSID) of your COM server and COR_ENABLE_PROFILING is set to 1.
Once the CLR has created an instance of your profiler, the first notification you will receive on the ICorProfilerCallback interface is the Initialize notification. This gives you the opportunity to tell the CLR what types of notifications you would like to receive by calling ICorProfilerInfo::SetEventMask with a combination of flags that are defined by the COR_PRF_MONITOR enumeration. ICorProfilerInfo allows you to ask the CLR about the currently running program and act accordingly. The flags in COR_PRF_MONITOR are enabled at the very beginning of the program, though they can be turned on and off by calling SetEventMask again later in the program execution with any new flags you want.
Another thing you can do in the original profiling API is set up callbacks using the ICorProfilerInfo::SetEnterLeaveFunctionHooks call. This allows you to provide callback functions to the CLR that will be called every time a function is entered or exited, or when a tailcall occurs (these do not return). When the program terminates, you receive a shutdown notification from the CLR, at which point you have a chance to perform final cleanup of any resources in use. The original profiling API allowed many different ways of exploring your program as it ran, but as with all things, there was room for improvement.
For more background on the profiling API, see my overview of it in the January 2003 issue of MSDN®Magazine, available at Inspect and Optimize Your Program's Memory Usage with the .NET Profiler API, as well as Aleksandr Mikunov's article on MSIL code rewriting using the profiling API, available in the September 2003 issue of MSDN Magazine at Rewrite MSIL Code on the Fly with the .NET Framework Profiling API .

So What's New?
The profiling team at Microsoft listened to developers and added a number of cool capabilities in 2.0. New things you can do with the profiling API include tracking the name and application domain of a managed thread, obtaining function arguments and return values without using in-process debugging, inspecting these arguments for values, and getting a stack trace quickly.
Much of this functionality is exposed through two new interfaces: ICorProfilerCallback2 and ICorProfilerInfo2. The ICorProfilerCallback2 interface inherits from the original ICorProfilerCallback interface and adds a new method for tracking thread name changes as well as eight new methods for garbage collection notifications. This is discussed in the section on thread information a bit later in this article. The ICorProfilerInfo2 interface inherits from ICorProfilerInfo and adds 21 new methods for controlling interactions with the runtime.
[ Editor's Update - 1/9/2006: The tables in the figures have been updated to reflect changes made for the release of the .NET Framework 2.0.]
However, all of these flags have also been added to the COR_PRF_IMMUTABLE enumeration value, which means that they can be set only with the ICorProfilerCallback::Initialize method. Any attempt to reset them at a later point by calling ICorProilerInfo::SetEventMask again will fail. COR_PRF_IMMUTABLE consists of the following COR_PRF_ MONITOR flags ORed together, as shown in Figure 2.
COR_PRF_ENABLE_FUNCTION_ARGS turns on two specific capabilities. The first is the ability to get function arguments in callbacks to FunctionEnter2 and FunctionTailcall2 (both new for the .NET Framework 2.0). The second is the ability to use the ICorProfilerInfo2::GetFunctionInfo2 function to get exact Class IDs for generic functions. If this flag is not used and you register for the FunctionEnter2 callback, the COR_PRF_FUNCTION_ARGUMENT_INFO* and COR_PRF_FRAME_INFO parameters to these functions will always be NULL. COR_PRF_ ENABLE_FUNCTION_RETVAL turns on the ability to track return values through the FunctionLeave2 callback via the COR_PRF_FUNCTION_ARGUMENT_RANGE* parameter. This parameter will always be NULL if this flag is not set in ICorProfilerInfo::SetEventMask.
Let's quickly touch on what is no longer available from the original profiling API. Then, we will explore how to use the powerful new functionality of Visual Studio®.

What's Gone?
The main feature being phased out with this release of the profiling API is the concept of in-process debugging. This was the original method used by profiler writers to examine values of function calls and return values as well as program state at run time. This was accomplished through four ICorProfilerInfo API methods (shown in Figure 3), and an interface called ICorDebug. ICorDebug is no longer used in conjunction with the profiling API.
One of the downfalls of in-process debugging was its inability to correctly and reliably deliver method parameter and return values during FunctionEnter, FunctionLeave, and FunctionTailcall callbacks. This was due to the relatively complex nature of the interactions between ICorDebug and the JIT compiler in-process. Happily for us, the Microsoft profiling API team has replaced this with a better-performing version of the same functionality originally envisioned for ICorDebug.

Obtaining Thread Information
The flag previously needed to get threading information in a profiler was COR_PRF_MONITOR_THREADS, a value set during the call to SetEventMask. In the new version of the Profiling API, you can use the same flag, but you will get more information if you have implemented ICorProfilerCallback2. ThreadNameChanged is a new method on ICorProfilerCallback2:
HRESULT ICorProfilerCallback2::ThreadNameChanged(
[in] ThreadID threadId, // ID of thread whose name was changed 
[in] ULONG cchName,  // number of characters in new name
[in] WCHAR name[]) // the new name of the thread
ThreadNameChanged is called when the friendly name is set on a .NET thread by assigning System.Threading.Thread.Name to a new value. Initially, .NET threads are not named, but if you are doing any sort of serious multithreading, I suggest naming them; this can prove an invaluable aid when debugging thread issues. It is much easier to manage threads named "GUI" and "Worker" than "0x98745837" and "0x22345873".
Another newly exposed piece of thread information is the AppDomain in which a thread is currently executing. This is retrieved with a call to ICorProfilerInfo2::GetThreadAppDomain:
HRESULT ICorProfilerInfo2::GetThreadAppDomain (
[in] ThreadID threadId, // ID of the thread whose AppDomain you require
[out] AppDomainID *pAppDomainId) // AppDomainID for the requested thread 
GetThreadAppDomain will allow you to get the ID for the current AppDomain associated with a given runtime thread. This may be able to help you solve bugs where threads are attempting to access AppDomain-specific resources when the thread is no longer in that AppDomain.

Stack 'Em Up
[Editor's Update - 1/9/2006: In order to use stack snapshots, ICorProfilerInfo2::SetEventMask must include the new COR_PRF_ENABLE_STATIC_SNAPSHOT flag.] To start the snapshot of the current thread's stack, call ICorProfilerInfo2::DoStackSnapshot, like so:
HRESULT ICorProfilerInfo2::DoStackSnapshot (
[in] ThreadID thread,   // thread for which snapshot should be taken
[in] StackSnapshotCallback *callback, // callback for each frame
[in] ULONG32 infoFlags, // COR_PRF_SNAPSHOT_INFO flags to control
                        // the degree of information for each snapshot
clientData) // caller-specified data passed to each callback 
[ Editor's Update - 1/9/2006: Many of the ICorProfilerInfo2 methods described in this article, including DoStackSnapshot, were updated for the final release of the .NET Framework 2.0. Please see the documentation at ICorProfilerInfo2 for updated signatures and information.]
DoStackSnapshot will perform a stack snapshot of the current thread. During the snapshot, the callback function whose signature is defined by StackSnapshotCallback is called once per frame of the managed stack and once at the beginning of an unmanaged chain. A profiler can do unmanaged stack walking from this point if it is tracking unmanaged code in addition to managed code, or it can ignore the unmanaged stack markers and just trace the managed frames of the stack. The definition of the StackSnapshotCallback function looks like this:
HRESULT StackSnapshotCallback(
[in] FunctionID funcId, // function ID for reported frame; 0 for unmanaged
[in] UINT_PTR ip, // instruction pointer for next instruction in frame
[in] COR_PRF_FRAME_INFO frameInfo, // opaque value used to get frame info
[in] ULONG32 contextSize, // size of buffer in context parameter
[in] BYTE [] context, // register context of the frame; can be NULL
[in] void* clientData),// data passed via DoStackSnapshot call
StackSnapshotCallback allows the recipient to examine the frame of the stack that the snapshot is currently on. This includes getting info available via the COR_PRF_FRAME_INFO value, seeing the register information, and reading any supporting data passed in from the original caller of DoStackSnapshot in the clientData field.
The opaque value COR_PRF_FRAME_INFO is defined as shown in the following line of code:
typedef UINT_PTR COR_PRF_FRAME_INFO
COR_PRF_FRAME_INFO is used to represent a stack frame and act as a token for methods on ICorProfilerInfo2. This allows ICorProfilerInfo2 to retrieve parameter and return value information.
The COR_PRF_SNAPSHOT_INFO enumeration contains the values to pass as the infoFlags parameter value in DoStackSnapshot. These values are listed in Figure 4.
Using DoStackSnapshot every time you need a stack is fine as long as you don't need stacks too often. It also has the added bonus of making it relatively easy to interleave unmanaged stack frames; if you need full stacks of both managed and unmanaged code ranges, you must use DoStackSnapshot because it provides the unmanaged frame starting points. If you are only interested in managed stack walking, then you can use the enhanced tracing capabilities of the new FunctionEnter2, FunctionLeave2, and FunctionTailcall2 tracing functions. These three functions allow you to monitor the progression of your code and build a "shadow stack." If you often need stack traces, building a shadow stack is less expensive and, as an added benefit, it provides you with generic type parameter information that the DoStackSnapshot method will never give to a profiler.

Enhanced Tracing
Function tracing is a core part of any profiler, as it allows you to see the entry and exit for each function in the application. In the 1.0 and 1.1 versions of the Profiling API, this was accomplished by setting callback functions on the ICorProfilerInfo interface through the SetEnterLeaveFunctionHooks method. The method allowed a profiler to sign up for three different types of function-tracing callbacks: entering a function (FunctionEnter), leaving a function (FunctionLeave), and performing a tailcall (FunctionTailcall). A tailcall occurs when the last action of a method is a call to another method.
What's interesting here is that the stack never records the call to the first method, only the second. These callbacks would execute for every function when the type of call or return occurred in the CLR, so you could get a real picture of where your application was doing its work. Each callback was passed a function ID, so a profiler could identify which function was being entered or exited; this function ID can be resolved by using the metadata API that is also provided for unmanaged access.
This was adequate for profilers who simply cared whether a function was entered or exited. However, to find out which parameters the function was called with or what the return value was on exit, the profiler was supposed to use the in-process debugging interface. As I mentioned earlier in this article, in-process debugging was not optimal for this purpose as there was no way for a profiler to detect these values.
Now we come to .NET Framework 2.0, where the profiling API team has liberated us from the use of ICorDebug for in-process debugging. They did this by adding a new method to the ICorProfilerInfo2 interface called SetEnterLeaveFunctionHooks2 as shown in the following:
HRESULT   
ICorProfilerInfo2::SetEnterLeaveFunctionHooks2 (
// ptr to FunctionEnter2 callback
[in] FunctionEnter2    *pFuncEnter, 
// ptr to FunctionLeave2 callback
[in] FunctionLeave2    *pFuncLeave, 
// ptr to FunctionTailcall2 callback
[in] FunctionTailcall2 *pFuncTailcall) 
To make the callback occur, the COR_PRF_MONITOR flag COR_PRF_MONITOR_ENTERLEAVE must be set using ICorProfilerInfo2::SetEventMask. To get the new functionality of being able to inspect the arguments of a function, the profiler must set the new flag, COR_PRF_ENABLE_FUNCTION_ARGS. And to get the return values, COR_PRF_ENABLE_FUNCTION_ RETVAL must be set as well.
If you have done work with the profiling API in the past, you will have noticed that the calling convention for the original FunctionEnter, FunctionLeave, and FunctionTailcall are _declspec(naked). This means that the prologue and epilogue for the function are not set up for the profiler by the compiler. The profiler needed to do a bit of inline assembly language to set up the prologue at the beginning of the function and the epilogue at the end of the function. The new callbacks for FunctionEnter2, FunctionLeave2, and FunctionTailcall2 use the _stdcall calling convention where none of this is necessary.
[ Editor's Update - 3/25/2006: This is incorrect. For the .NET Framework 2.0, the new callbacks for FunctionEnter2, FunctionLeave2, and FunctionTailcall2 should still in general be implemented as __declspec(naked).]
To register for the new function tracing callbacks, the profiling API provides the callback implementations for FunctionEnter2, FunctionLeave2, and FunctionTailcall2, as shown here:
typedef void FunctionEnter2 (
[in] FunctionID funcId, // FunctionID for the function being entered
[in] COR_PRF_FRAME_INFO func, // opaque value only valid during callback
[in] COR_PRF_FUNCTION_ARGUMENT_INFO *argumentInfo) // function args info
COR_PRF_ENABLE_FUNCTION_ARGS is not set in the event mask and will always be NULL.
FunctionEnter2 is called at the entering of almost every method (except tailcalls) during the execution of the program. This occurs after SetEnterLeaveFunctionHooks2 is called with the following callback pointer set:
typedef void FunctionLeave2 (
[in] FunctionID funcId, // FunctionID for function being left
[in] COR_PRF_FRAME_INFO func,  // opaque value only valid during callback
[in] COR_PRF_FUNCTION_ARGUMENT_RANGE *retvalRange) // info  about return
                                                   // values from function
COR_PRF_ENABLE_FUNCTION_RETVAL is not set in the event mask and will always be NULL.
FunctionLeave2 is called for the exiting of every method during the execution of the app. Again, this occurs after SetEnterLeaveFunctionHooks2 is called with this callback pointer set:
typedef void FunctionTailcall2 (
[in] FunctionID funcId, // FunctionID for the function
[in] COR_PRF_FRAME_INFO func) // opaque value only valid during callback

Describing and Decoding Function Arguments
Now that you have callbacks for the function at the point it is being entered and exited, you can examine arguments and return values. The Profiling API exposes these values as regions in memory. You can use a function's metadata token to get the types of the parameters you are dealing with, which will allow you to decode them. The .NET Framework keeps track of items by tagging them with values from the CorElementType enumeration. Figure 5 lists all of these values (entries in red are new enumeration values added in the .NET Framework 2.0). CorElementTypes with a value above 0x22 (ELEMENT_TYPE_ MAX) are special modifiers used to describe types in different circumstances. In the arguments and return values, you will see the basic .NET types, represented by the CorElementType enumeration. These are shown in Figure 6.
The arguments for a FunctionEnter2 call or a FunctionTailcall2 call are passed in the COR_PRF_FUNCTION_ARGUMENT_ INFO* argumentInfo parameter. COR_PRF_FUNCTION_ ARGUMENT_INFO is a struct that's defined like this:
typedef struct _COR_PRF_FUNCTION_ARGUMENT_INFO {
    ULONG numRanges; // number of argument ranges
    ULONG totalArgumentSize; // total size of arguments
    COR_PRF_FUNCTION_ARGUMENT_RANGE ranges[ 1 ]; // array of ranges
} 
COR_PRF_FUNCTION_ARGUMENT_INFO;
The ranges field is defined as a set of structures defined as COR_PRF_FUNCTION_ARGUMENT_RANGE and shown here:
typedef struct _COR_PRF_FUNCTION_ARGUMENT_RANGE {
    UINT_PTR startAddress; // starting memory address of first argument
    ULONG length; // length of this range of memory
}
COR_PRF_FUNCTION_ARGUMENT_RANGE;
If a profiler wants to reference any of this information later, it needs to copy it, since these structures will not be valid once you leave the FunctionEnter2 callback. COR_PRF_FUNCTION_ARGUMENT_INFO is essentially a map to the function argument values (for Value Types) or Object IDs (for Reference Types). If the arguments are contiguous in memory in left-to-right order, then one COR_PRF_FUNCTION_ARGUMENT_RANGE structure will be used to represent them. This range is pulled apart by looking at the function signature which can be retrieved from the metadata using the unmanaged metadata API, and then using that knowledge to walk along the memory range and get each argument's value or Object ID.
For a FunctionLeave2 call, only the return values are provided in a range specified by a COR_PRF_FUNCTION_ARGUMENT_RANGE structure. For items such as ref and out parameters, you need to save the memory address for the argument in the call to FunctionEnter2 and then check it in the Leave callback.
Now that you have the values and/or object IDs for the arguments and return values, what do you do with them? It's easy to print out a simple integer value, but what if the value is an object ID? Help is needed to decode what type of object you are dealing with in order to access the values.
A whole new set of functions was added to the ICorProfilerInfo2 interface to help you retrieve the information for these parameters and more. This set includes the following new functions added to the ICorProfilerInfo2 interface:
  • GetFunctionInfo2
  • GetStringLayout
  • GetClassLayout
  • GetClassIDInfo2
  • GetClassFromTokenAndTypeArgs
  • GetFunctionFromTokenAndTypeArgs
  • GetArrayObjectInfo
  • GetBoxClassLayout
Let's start by figuring out some things about the function you are being called back about. In the previous versions of the profiling API, there was a function called ICorProfilerInfo::GetFunctionInfo. GetFunctionInfo let you use the Function ID passed in to the callback to determine a function's class and module; it even got the function's metadata token. In the grand tradition of API versioning, Microsoft now gives us the fabulously exciting method name ICorProfilerInfo2::GetFunctionInfo2.
What new and wondrous things can now be accessed? Well let's take a look at the definition for GetFunctionInfo2:
HRESULT GetFunctionInfo2 (
  [in] COR_PRF_FRAME_INFO frameInfo, // frame information for callback
  [out] ClassID *pClassId,   // class ID for function; can be NULL
  [out] ModuleID *pModuleId, // module ID for function; can be NULL
  [out] mdToken *pToken,     // metadata token for function; can be NULL
  [in] ULONG32 cTypeArgs,    // number of elements in the type arg array
  [out] ULONG32 *pcTypeArgs, // number of elements needed to hold type
                             // arguments to the function; can be NULL
  [out] ClassID typeArgs[])  // caller-allocated buffer to accept the
                             // the function's type arguments; can be NULL
If you are trying to examine different parts of the function signature, GetFunctionInfo2 allows you to get the class the function is part of, the module it belongs to, and the metadata token. (The latter can be used to find all sorts of information with the metadata API, but that's a topic for another time.)
You now have the capability to look at the type parameters on a generic function. All of the arguments with TypeArgs in the name help to describe the classes of the type arguments for this instantiation of this function. For example, consider a generic function defined like this:
public class Printer {
    public void Print <T>(T item) {
       // do some printing generically
    }
}
You can find out the type of T when this function was instantiated at run time for this callback. That's not all, though. Due to the way the CLR stores different types of items that can be arguments (strings, arrays, boxed items, and so on), a profiler needs a few helper methods to pluck the appropriate value from th