CLR Debugging Overview

The common language runtime (CLR) debugging API enables tools vendors to write debuggers to debug applications that run in the CLR environment. The code to be debugged can be any kind of code that the CLR supports.

The CLR debugging API is implemented primarily with unmanaged code. Therefore, the debugging API is presented as a set of Component Object Model (COM) interfaces. The API consists of the following:

  • A collection of COM objects and interfaces that are implemented by the CLR.

  • A collection of COM callback interfaces that must be implemented by the debugger.

This overview contains the following sections:

  • CLR Debugging Scenarios

  • API Categories

  • Attaching to or Launching a Program

  • Controlling Execution

  • Examining Program State

  • Modifying Program State

  • Using Edit and Continue

  • Evaluating Functions

  • Injecting Code Dynamically

  • Supported Environments

  • Related Topics

CLR Debugging Scenarios

The following sections describe how the common language runtime debugging API handles typical debugging scenarios. Note that the runtime supports some scenarios directly and interoperates with current methods to support others.

Out-of-Process Debugging

In out-of-process debugging, the debugger is in a separate process from the process that is being debugged (that is, it is outside the debuggee). This scenario reduces the interactions between the debugger and the debuggee. Therefore, it enables a more accurate picture of the process.

The CLR debugging API supports out-of-process debugging directly. The API handles all communication between the debugger and the managed parts of the debuggee to support managed code debugging.

Although the CLR debugging API is used out-of-process, some of the debugging logic (for example, thread synchronization) occurs in-process with the debuggee. In most cases, this is an implementation detail that should be transparent to the debugger. For more information about thread synchronization, see CLR Debugging Architecture. A disadvantage is that the debugging API cannot be used to inspect crash dumps when it is used out-of-process.

In-Process Debugging

In the .NET Framework version 1.0 and 1.1, the CLR debugging API supported limited in-process debugging, in which a profiler could use the inspection features of the debugging API. In the .NET Framework 2.0, in-process debugging was replaced with a set of functionality that is more consistent with the profiling API. For more information about these changes, see the stack snapshot and object inspection features in the Profiling Overview.

Remote Process Debugging

In remote process debugging, the debugger user interface is on a separate computer from the process that is being debugged. This scenario can be useful if the debugger interferes with the debuggee when they are running on the same computer. The interference may be caused by the following:

  • Limited resources.

  • Location dependencies.

  • Bugs that interfere with the operating system.

The CLR debugging API does not support remote process debugging directly. A debugger that is based on the CLR debugging API must still exist out-of-process from the debuggee. Therefore, this solution requires a proxy process on the computer that has the debuggee.

Unmanaged Code Debugging

Managed code and unmanaged code often coexist in the same process. Simultaneous debugging of both code types is a common need.

The CLR debugging API supports stepping through boundaries between managed and unmanaged code, but it does not directly support unmanaged code debugging. However, the CLR debugging API can coexist with an unmanaged code debugger by sharing the Win32 debugging facilities.

Furthermore, the CLR debugging API provides two options for debugging a process:

  • A soft-attach option in which only the managed parts of the process are debugged. A debugger that is soft-attached to a process can subsequently detach from the process.

  • A hard-attach option in which both the managed and unmanaged parts of a process are debugged, and all Win32 debug events are exposed through the debugging API.

Mixed Language Environments

In component software, different components can be built with different languages. A debugger must understand language differences so that it can display data in the correct format, evaluate expressions with the correct syntax, and so on.

The CLR debugging API does not provide any direct support for mixed language environments, because the CLR has no concept of source language. A debugger's existing source mapping facilities should enable it to map a given function to the language in which the function was implemented.

Multiple Processes and Distributed Programs

A component program can include cooperating components that are running in different processes or even on different computers throughout a network. A debugger should be able to trace execution logic between processes and computers to provide a logical view of what is going on.

The CLR debugging API does not provide any direct support for multiple process debugging. Again, a debugger that is using the API should provide such support directly, and existing methods to do this should continue to work.

Back to top

API Categories

The debugging API includes the following three groups of interfaces, all used typically by a CLR debugger and all implemented as unmanaged code:

  • Interfaces that support debugging of CLR applications.

  • Interfaces that provide access to symbolic debug information, which is typically stored in program database (PDB) files.

  • Interfaces that support querying of the processes and application domains on a computer.

The debugging API relies on two additional sets of interfaces:

  • Metadata API to handle inspection of static program information such as classes and method type information.

  • Symbol store API to support source-level debugging for managed code debuggers.

The debugging interfaces can also be organized into the functional categories shown in the following table.

API category

Description

Registration

Interfaces that the debugger calls to register with the CLR and to request notification when specific events occur.

Notification

Callback interfaces that the CLR uses to notify the debugger of various events and to return requested information. These interfaces must be implemented by the debugger.

Breakpoint

Interfaces that the debugger calls to retrieve information about breakpoints.

Execution

Interfaces that the debugger calls to control the execution of the debuggee and to access call stacks.

Information

Interfaces that the debugger calls to obtain information about the debuggee.

Enumeration

Interfaces that the debugger calls to enumerate objects.

Modification

Interfaces that the debugger calls to modify the code that is being debugged.

The following sections describe the functionality that the common language runtime (CLR) debugging services provide.

Back to top

Attaching to or Launching a Program

The CLR lets you attach your debugger to a running program or start a process. The CLR debugging services support just-in-time (JIT) debugging by letting you attach your debugger to a program that throws an unhandled exception. However, a program that is not running in debuggable mode may have less debugging information available. A program can always run itself in debuggable mode to avoid this problem. For more information about debuggable mode, see the following:

Back to top

Controlling Execution

The CLR debugging services provide several ways to control the execution of a program. These include breakpoints, single stepping, exception notification, function evaluation, and other events that relate to the startup and shutdown of a program.

The CLR debugging API provides execution control only for managed code. If you want to perform execution control in unmanaged code, you must implement that functionality separately in your debugger.

Breakpoints

You can create breakpoints by specifying the code and the Microsoft intermediate language (MSIL) or native offset of the location where the break should occur. The debugger will then be notified when the breakpoint is encountered. The debugging API does not directly support conditional breakpoints. However, a debugger can implement these by evaluating an expression in response to a breakpoint and deciding whether to inform the user of the stop.

Stepping

The CLR debugging services provide a wide variety of stepping functionality. A program can step through code one instruction at a time (single-stepping) or a range of instructions at a time (range-stepping). It can skip over, step into, or step out of a function. The CLR debugging services can also notify the debugger if an exception occurs that interrupts the stepping operation.

Although the debugging services do not directly support stepping through unmanaged code, they will provide callbacks when a stepping operation reaches unmanaged code, to hand off control to the debugger. They also provide functionality that lets the debugger determine when managed code is about to be entered from unmanaged code.

The CLR does not directly provide source-level stepping. A debugger can provide this functionality by using range-stepping together with its own source mapping information. You can use the symbol store interfaces to obtain source-level information. For more information about these interfaces, see Diagnostics Symbol Store (Unmanaged API Reference).

Exceptions

The CLR debugging services enable a debugger to be informed of both first-chance and second-chance exceptions in managed code. The thrown object is available for inspection at each point.

The CLR does not handle native exceptions in unmanaged code unless they propagate up to managed code. However, you can still use the Win32 debugging services that are shared with the CLR debugging services to handle unmanaged exceptions.

Program Events

The CLR debugging services notify a debugger when many program events occur. These events include process creation and exit, thread creation and exit, application domain creation and exit, assembly loading and unloading, module loading and unloading, and class loading and unloading. To guarantee good performance, you may disable class loading and unloading events for a module. By default, events for class loading and unloading are disabled.

Thread Control

The CLR debugging services provide interfaces for suspending and resuming individual (managed) threads.

Back to top

Examining Program State

The CLR debugging services provide a detailed way to inspect the parts of a process that are running managed code when the process is in a stopped state. A process can be inspected to obtain a list of physical threads.

A thread can be examined to inspect its call stack. A thread's call stack is decomposed at two levels: at the chain level and at the stack frame level. The call stack is first decomposed into chains. A chain is a contiguous logical call stack segment. It contains either managed or unmanaged stack frames, but not both. In addition, all managed call frames in a single chain share the same CLR context. A chain can be either managed or unmanaged.

Each managed chain can be additionally decomposed into single stack frames. Each stack frame represents one method invocation. You can query a stack frame to obtain the code it is executing or to obtain its arguments, local variables, and native registers.

An unmanaged chain does not contain stack frames. Instead, it provides the range of stack addresses that are allocated to unmanaged code. It is up to an unmanaged code debugger to decode the unmanaged part of the stack and provide a stack trace.

Note

CLR debugging services do not support the concept of local variables that occur in source code. It is up to the debugger to map local variables to their allocations.

The CLR debugging services also provide access to global, class static, and thread local variables.

Back to top

Modifying Program State

The CLR debugging services let a debugger change the physical location of the instruction pointer during execution, although this may be a dangerous operation. The instruction pointer may be changed successfully when the following conditions are true:

  • The current instruction pointer and the target instruction pointer are both at sequence points. Sequence points approximately represent statement boundaries.

  • The target instruction pointer is not located in an exception filter, a catch block, or a finally block.

  • The current instruction pointer is in a catch block, and the target instruction pointer is not located outside the catch block.

  • The target instruction pointer is in the same frame as the current instruction pointer.

When the physical location of the instruction pointer changes, variables at the current instruction pointer location will be mapped to the variables at the target instruction pointer location. Garbage collection references at the target instruction pointer location will be initialized correctly.

After the instruction pointer is changed, the CLR debugging services mark any cached stack information as invalid and refresh the information the next time that it is needed. Debuggers that cache pointers to stack information such as frames and chains should refresh this information after changing the instruction pointer.

The debugger can also modify the data of a program when the program is stopped. The debugger can change the local variables and arguments of a function when the function is running, in a manner similar to inspection. The debugger can also update fields of arrays and objects, and also static fields and global variables.

Back to top

Using Edit and Continue

During a debugging session, you can use the Edit and Continue feature to do the following:

  • Edit source code.

  • Recompile the modified source.

  • Preserve the rest of the run-time state of the executable that is being debugged.

  • Continue the debugging session without having to rerun the executable from the start.

Back to top

Evaluating Functions

To evaluate user expressions and dynamic properties of objects, a debugger needs a way to run the code of the process that is being debugged. The CLR debugging services enable the debugger to make a function or method call and have it run inside the debuggee's process.

The CLR lets the debugger stop such an operation because it may be dangerous (for example, it may trigger a deadlock with existing code). If the evaluation is stopped successfully, the thread is treated as if the evaluation never occurred, except for any side effects on local variables from the partial evaluation. If the function calls into unmanaged code or blocks in some manner, it may be impossible to end the evaluation.

When the function evaluation is complete, the CLR uses a callback to notify the debugger whether the evaluation completed correctly or the function threw an exception. You can use ICorDebugValue and ICorDebugValue2 methods to inspect the results of an evaluation.

The thread on which the function evaluation is to occur must be stopped in managed code, at a point that is safe for garbage collection. (Function evaluation is also permitted for unhandled exceptions.) In unoptimized code, these safe points are very common; most breakpoint or MSIL-level step operations will complete at one. However, these points can be rare in optimized code. Sometimes an entire function may not have any safe points. The frequency of points that are safe for garbage collection will vary from function to function. Even in unoptimized code, it is possible not to stop at one. In either optimized or unoptimized code, the ICorDebugController::Stop method rarely lands at a safe point.

The CLR debugging services will set up a new chain on the thread to start a function evaluation and call the requested function. As soon as the evaluation is started, all aspects of the debugging API are available: execution control, inspection, function evaluation, and so on. Nested evaluations are supported, and breakpoints are handled as usual.

Back to top

Injecting Code Dynamically

Some debuggers let a user enter arbitrary statements in the Immediate window and execute the statements. The CLR debugging services support this scenario. Within reason, there are no restrictions on what code you can inject dynamically. (For example, non-local goto statements are not permitted.)

Dynamic code injection is implemented by using a combination of Edit and Continue operations and function evaluation. The code to be injected is wrapped in a function and injected by using Edit and Continue. The injected function is then evaluated. If you want, you can supply ByRef arguments to the wrapper function so that side effects are immediate and permanent.

Back to top

Supported Environments

CLR debugging facilities are available on all processors and operating systems that the CLR supports, with the following exceptions:

  • Edit and Continue and mixed-mode debugging are not supported on 64-bit operating systems. The SetIP methods (ICorDebugILFrame::SetIP and ICorDebugNativeFrame::SetIP) have additional restrictions on 64-bit operating systems. The remaining functionality is equivalent on all processors (although there will be processor-specific data representations such as pointer sizes, register contexts, and so on).

  • Edit and Continue and mixed-mode debugging are not supported on Win9x-based operating systems. The remaining functionality should be equivalent on all operating systems. However, there are some specific exceptions, which are noted in the documentation of individual functions.

Back to top

Title

Description

CLR Debugging Architecture

Describes how the different components of the CLR debugging API interact with the CLR and the debugger.

Debugging API Changes in the .NET Framework 2.0

Describes changes and improvements to debugging in the .NET Framework version 2.0.

Pre-Conditions for CorDebug.idl Interfaces

Describes how some of the CLR debugging interfaces require the process that is being debugged to be in a specific state.

Debugging a Runtime Process

Provides a step-by-step description of how a run-time process is debugged.

Controlling a Program During Debugging

Describes how a debugger uses the CLR debugging API to set breakpoints, to step through managed and unmanaged code, and to handle exceptions.

Examining a Program During Debugging

Describes how a debugger uses the CLR debugging API to access managed stack frames and evaluate expressions.

Injecting Code Dynamically with the Debugging API

Describes dynamic code injection, in which the CLR hijacks an active thread to execute code that was not present in the original portable executable (PE) file.

Publishing Processes in the Debugging API

Summarizes CLR process publishing interfaces, which enumerate and provide information about the processes and application domains on a computer.

Security Considerations in the Debugging API

Discusses security considerations for using the CLR debugging API.

Debugging Coclasses

Describes the unmanaged coclasses that the debugging API uses.

Debugging Interfaces

Describes the unmanaged interfaces that handle the debugging of a program that is executing in the common language runtime.

Debugging Global Static Functions

Describes the unmanaged global static functions that the debugging API uses.

Debugging Enumerations

Describes the unmanaged enumerations that the debugging API uses.

Debugging Structures

Describes the unmanaged structures that the debugging API uses.

Back to top