Debugging API Capabilities
This topic provides an overview of the functionality that the common language runtime (CLR) debugging services provide. It includes the following subsections:
Attaching to or Launching a Program
Controlling Execution
Examining Program State
Modifying Program State
Using Edit and Continue
Evaluating Functions
Injecting Code Dynamically
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:
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; 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.
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 that contains completely managed or unmanaged stack frames. 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 allotted 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 as they exist 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.
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.
If within a catch block, 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 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 a function's local variables and arguments when the function is running, in a manner similar to inspection. The debugger can also update fields of arrays and objects, as well as static fields and global variables.
Using Edit and Continue
Edit and Continue is a feature that makes it possible to be in the middle of a debugging session, edit source code, recompile the modified source, and continue the debugging session without having to rerun the executable from the beginning. From a functional perspective, Edit and Continue provides the ability to modify the code that is running in the debugger while preserving the rest of the run-time state of the executable that is being debugged.
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 abort such an operation because it may be dangerous (for example, it may trigger a deadlock with existing code). If the evaluation is aborted 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 normally 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 allowed 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.
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 allowed.)
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 the wrapper function with arguments that are declared to be ByRef so that side effects are immediate and permanent.