Pex - Automated Whitebox Testing for .NET : Microsoft.ExtendedReflection.ClrMonitor

Overview


The ClrMonitor is an unmanaged COM component that implements the CLR profiling API. It uses this API mainly to rewrite MSIL instructions of selected methods, types or assemblies on-the-fly at JIT time. It can redirect calls and insert callbacks in the MSIL instruction sequence, enabling the observation of the execution of the program at a fine grained level:

  • (Static) calls can be redirected. The redirection target may call the original call target.
  • Similar to the standard CLR profiling API, the ClrMonitor monitors when a method is entered or exited.
  • It also monitors the concrete parameter values that are passed to a monitored method.
  • The execution of every instruction can be monitored.
  • The control flow is monitored, i.e. the evaluation of all explicit conditional branches, and of all implicit checks that can cause exceptions.
  • Every monitored event causes a callback into the core Microsoft.ExtendedReflection assembly, which can dispatch the callbacks to a user-provided Execution Monitor.

Code instrumentation


The instruction sequence of each instrumented method is rewritten. It starts by inserted instructions which check whether monitoring is currently on. If not, the original instructions are executed. It if it turned on, the instrumented version of the instructions is execution, starting with a callback to EnterMethod(m, c), and eventually finished by a callback to LeaveMethod(m), where m identifies the current method, and c is the number of branch identifiers that the instrumented code will refer to within the method body.

EnterMethod returns a Boolean value that indicates whether the monitor is interested in the concrete arguments to the method. If it returns true, a sequence of calls to Argument<T>(i, v), ArgumentPtr(i, v, t), ArgumentTypedReference(i, v) and ArgumentByRef<T>(i, a) is inserted, one for each argument, where i is the index of the argument, v is a value, t a type, and a a managed pointer.

Each exception filter starts with a callback to StartFilter(r) where r is the reference to the exception object that is currently being filtered. Each exception handler starts with a callback to StartExceptionHandler(r).

Each instruction that can throw a NullReferenceException, InvalidCastException, IndexOutOfRangeException, ArrayTypeMismatchException, DivideByZeroException or OverflowException is immediately followed by a callback that indicates that the instruction did not throw the exception. Also, all such instructions are surrounded by an exception handler, that catches the corresponding exception type, performs a callback to a method that indicates that the exception was thrown, and rethrows the exception.

See the file checks.def in the Microsoft.ExtendedReflection.ClrMonitor project for detailed information about which instructions may throw which exceptions.

In general, each instruction causes a callback named after the instruction itself. The callback is issued before the execution of the actual instruction.

In order to capture all relevant information to track the execution path taken by the program, some instructions get a special treatment.

Substitutions


Call instruction in instrumented MSIL code can be redirected. Read more here.

Setup


In order for code to be monitored, certain environment variables must be set before the process starts:

COR_ENABLE_PROFILING=1
COR_PROFILER={C1DAAFC4-10C6-4838-9F89-435E2120F5F1}

CLRMONITOR_FLAGS={flags that decide the extend of the instrumentation}


  • which types should be protected?
CLRMONITOR_PROTECT_TYPES={comma-separated list of type names; case matters; generic types must end in '`n' annotations; nested types are separated by '+'; <Module> stands for all module-level members}

  • which type and assemblies should be instrumented?
CLRMONITOR_INSTRUMENT_TYPES={comma-separated list of type names; case matters; generic types must end in '`n' annotations; nested types are separated by '+'; <Module> stands for all module-level members}
 
CLRMONITOR_INSTRUMENT_ASSEMBLIES={comma-separated list of display names of assemblies; case does not matter; use '*' for all assemblies}
 
CLRMONITOR_INSTRUMENT_TYPES_EXCLUSIONS={list of types which should not be instrumented, despite the above}
 
CLRMONITOR_INSTRUMENT_ASSEMBLIES_EXCLUSIONS={list of assemblies which should not be instrumented, despite the above}
 
CLRMONITOR_DISABLE_MSCORLIB_SUPPRESSIONS=1, disable mscorlib exclusions. Use with *Great* care.

  • which types and assemblies should be instrumented so that values can be injected at any place in the execution
CLRMONITOR_INJECT_TYPES={comma-separated list of type names; case matters; generic types must end in '`n' annotations; nested types are separated by '+'; <Module> stands for all module-level members}
 
CLRMONITOR_INJECT_ASSEMBLIES={comma-separated list of display names of assemblies; case does not matter; use '*' for all assemblies}
 
CLRMONITOR_INJECT_TYPES_EXCLUSIONS={list of types which should not be instrumented, despite the above}
 
CLRMONITOR_INJECT_ASSEMBLIES_EXCLUSIONS={list of assemblies which should not be instrumented, despite the above}

  • which method calls should be substituted (redirected)?
CLRMONITOR_SUBSTITUTIONS={comma-separated list of file names, pointing to assemblies containing substitutions}

  • which instructions should be monitored by inserted callbacks?
See here for a detailed description of possible CLRMONITOR_FLAGS.

The class Monitoring.Controller in the Microsoft.ExtendedReflection project provides support to check if code in the current process can be monitored, and to setup monitoring for a new process.

Activation


There are two ways to direct the instrumented code's callbacks to a user-written managed class:

  • In a running process, the Monitoring.Controller class provides Start and Stop methods to direct the callbacks to a particular Execution Monitor and to stop the redirection.
  • When the following environment variables are set, a user-provided Execution Monitor is loaded automatically into any monitored process. Note that in addition to setting up the environment variables, the Microsoft.ExtendedReflection assembly must either be in the monitored process' application directory, or in the GAC.

CLRMONITOR_USER_ASSEMBLY={file name of assembly containing [Execution Monitor]}
 
CLRMONITOR_USER_TYPE={type in assembly implementing [Execution Monitor]}

Logging (Only available in Debug-builds)


If compiled in Debug configuration, setting the environment variable

CLRMONITOR_LOGFILE={filename}
CLRMONITOR_VERBOSE=1

causes detailed information about the code instrumentation to be stored in the indicated file.


Debugging (Only available in Debug-builds)


If set, this environment variable will cause the ClrMonitor to break into the debugger when launched.

CLRMONITOR_BREAK=1


Loading your own profiler


When the environment variables are generated through the managed API, Extended Reflection sets the following environment variable with the profiler CLSID.

CLRMONITOR_CLSID={...}

In Pex, one can use the /AllowLoadingProfiler to authorize this kind of profiler 'chaining':
  • enable your own profiler by setting the COR_PROFILER environment variables,
    • do not turn on COR_PROFILER_ENABLED! Pex will enable it when launching the monitored process.
  • use the CLSID from CLRMONITOR_CLSID to create the Extended Reflection profiler and delegates the profiling APIs
  • in the pex command line, use the /AllowLoadingProfiler to enable this mode. In that case, Pex will not modify the value of COR_PROFILER.
(c) Microsoft Corporation. All rights reserved. pex Wiki Documentation 0.93.50813.0