CLR Debugging vs. CLR Profiling

The CLR offers both debugging and profiling services.  While there is some overlap, there are some significant differences between profiling and debugging in the CLR and they’re intended to solve very different problems.

 

What about the similarities?

There’s certainly some overlap between these two services. Both:

1) can inspect CLR state. including callstacks, local variables, and object layout.

2) can receive various notifications of different CLR activity.  The profiler’s notifications are more low level than the debugger’s.

3) have similar version semantics at the interface level. A debugger or profiler can target any current verion of the runtime, or provide adaptive behavior for previous version of the runtime (we call this backward- compat). They can’t target future versions (we call this forward-compat).  Eg, a v2.0 debugger/profiler can target both the v1.0 + v2.0 runtimes, but not the v3.0 runtime. That aside, the profiler has many more breaking changes than the debugger, so a backwards-compatible profiler is less practical.

 

What about the differences?

The debugger and profiler were designed with very different goals in mind:

 

Design Differences between CLR Debugging and CLR Profiling

Debugging

Profiling

Intended to determine correctness issues

Intended to determine diagnostic/performance issues

Can be extremely intrusive

Tries to be as non-invasive as possible. Although the profiler exposes some invasive features (like rewriting IL, function enter/leave hooks), those are usually intended for instrumenting and not to radically alter program execution.

Primary goal is complete control of target. This includes inspection, execution-control (such as set-next-statement), and development (such as Edit-And-Continue).

Primary goal is inspection of target. It allows instrumentation (such as rewriting IL or injecting function enter/leave hooks), but that’s basically inspection-focused.

Large API and thick object model complete with abstractions.

Small API with few/no abstractions.

Very interactive experience: debugger operations are controlled by a user (or algorithm). In fact, editors and debuggers are generally integrated together (IDEs).

Not interactive: Data usually collected without any user interaction and then inspected afterwards

Few breaking changes in backwards compat scenario. We expect migrating a v1.1 debugger to v2.0 to be a small to medium sized task.

Many breaking changes in backwards compat scenario. For example, the profiler’s entire inspection API was deprecated after v1.1. We expect migrating a v1.1 profiler to v2.0 to be a large task almost on par with a rewrite.

 

Further CLR-specific differences flow forth from implementation decisions made under the influence of the design decisions above.  

 

CLR-specific Differences between CLR Debugging and CLR Profiling

Debugging

Profiling

Uses the ICorDebug API defined in CorDebug.idl.

Uses the ICorProfiler API defined in CorProf.idl. (V1.0/1.1 also used the inspection portion of ICorDebug. but in v2.0, there is a new inspection API for profiling that is completely disjoint from ICorDebug)

Can attach and detach to target.

Can not attach / detach. Target must be launched with profiling enabled. (Perhaps future versions will allow this)

Target is a separate process. (The fact that the debugging services have a helper-thread that runs in the debuggee’s process is “just” an implementation detail).

Target is the same process. The profiling dll is loaded into the same process as the target and functions as an extension of the CLR (mscorwks.dll).

Debugger could be managed or unmanaged. For eg, you could use the MDbg wrappers to write a debugger in VB.Net.

Profiler must be unmanaged. The profiler is profiling all managed code in the process, if it were managed, it would end up profiling itself. Profilers are written in unmanaged C/C++.

High level event notifications that abstract away the CLR internals as much as possible. This lets debuggers have a flexible versioning model.

Low level event notifications exposing the raw implementation of the CLR. Eg, Profilers can be notified of when functions are jitted, when the GC moves references, and when the runtime is suspended. This is why profilers have a stricter versioning model.

Instrument via Breakpoints.

Instrument via function enter / leave hooks and rewriting IL (via ICorProfilerInfo:: SetILFunctionBody) before function is jitted. Although rewritten IL could do anything, it’s intended for inserting instrumentation (see ICorProfilerInfo::SetILInstrumentedCodeMap for details).

Change code via Edit-And-Continue. Changes can be before or after the function is jitted, so there may be multiple jitted instantiations of an edited function.

Change code via rewriting IL before the function is jitted.

 

Debugging and profiler are also different enough from the CLR perspective that we usually have different people working on each. Profiling is now owned by our performance gurus. I work almost exclusively on the debugging services, and I have never actually written a profiler. (Special thanks to Jonathan Keljo, who works on both debugging and profiling, for helping me get some of the information above straight.)

 

It turns out there’s actually enough overlap in functionality that Russ Osterlund at SmidgeonSoft used CLR profiling services to supplement a native debugger with primitive managed debugging functionality. Check out PEBrowse Interactive at https://www.smidgeonsoft.com for more info.

 

Overall, I regret that we didn’t find a better way to managed the overlap in these APIs, especially since they each have useful features that the other could benefit from.