How the .NET Debugger Works

Breakpoints

The concept of sequence points here is a major one for breakpoints and stepping and now we'll build on this to get the application breaking from user created breakpoints instead of needing code to be inserted into the application being debugged.

Setting a Breakpoint

In order to create a breakpoint we need to know which function we wish to set it in, and the offset into the function on which to set the breakpoint. Last time we started to use sequence points to determine where a byte offset referred to in the original source code, and so this time we need to do the same but in reverse.

There isn't going to be much source code for this instalment since we've moved on from having a working debugger and are now on to explaining individual concepts.

When the use creates a breakpoint we have three pieces of information that we need to use to create the actual breakpoint. Typically the user would have selected a point in a source line and enabled a breakpoint and so we have the source file name, the line and the column of the breakpoint. The ISymUnmanagedReader interface has a function called GetMethodFromDocumentPosition that takes exactly this information and returns an ISymUnmanagedMethod object. The only difficulty is that we need to supply a ISymUnmanagedDocument and not a filename and so we need to use the cache of documents that is created when we originally load them in the LoadModule callback. From here the procedure is the same as in the last part of this series where we were retrieving the sequence points and looking up the source code position. This time we need to examine the startLines[] and startColumns[] parameters and find the values that are on, or just before the values that we are trying to look up. Once we have the number that is the index in these two arrays we can use it to find the offset in the offsets[] array.

Now that we have this number we need to actually create a breakpoint object for this offset. To do this we need to get to the function object, which involves retrieving the method token with ISymUnmanagedMethod::GetToken and then calling ICorDebugModule::GetFunctionFromToken .

This returns a ICorDebugFunction object which has the method CreateBreakpoint , but this function isn't the one that we need. What we need to do is call ICorDebugFunction::GetILCode , which returns a ICorDebugCode object. This interface also has a CreateBreakpoint method, but unlike the one on the function object this one takes an offset parameter on which to set the breakpoint.

Calling this function returns an ICorDebugFunctionBreakpoint object which is now an active breakpoint on the system.

Hitting a Breakpoint

Hitting a breakpoint is just the same as when we broke as a result of calling Debugger.Break , only this time the notification we get is ICorDebugManagedHandler::Breakpoint . At this point the same code as we had when we handled that break should be executed and so we can see where we are in the source code.

Clearing a Breakpoint

Removing a breakpoint is a case of calling ICorDebugBreakpoint::Activate(FALSE) . There is no way to actually delete a breakpoint and so we can just deactivate it.

Conditional Breakpoints

There is no support in the framework for conditional breakpoints, but it is easy enough to implement. At the moment we don't have access to things such as variable values so the only condition that we can implement is the one where we can have the breakpoint trigger after x number of times it has been hit. This is easy to implement by having a counter value that is associated with the breakpoint (a map with the value of the ICorDebugBreakpoint interface pointer will do) that is incremented each time it is hit and if the hit count isn't as high as the trigger value then Continue(FALSE) is called in the Breakpoint handler to prevent it from actually stopping.

Displaying the breakpoint in the source

One of the interesting things that we can do with breakpoints under the CLR is to have the breakpoint set on a section of the line and not just the whole line. This is because we set breakpoints on sequence points and so we know which section of code we can highlight in the IDE to show exactly where the breakpoint is. This will be more useful once we get onto stepping through the code.

Conclusion

Once you understand sequence points under .NET it makes breakpoints very easy to use. The actual breakpoint classes provide very little functionality beyond stopping code from executing, but there is no reason why they can't be expanded beyond this. We are still far from having a working debugger because we can't step through code or view the contents of variables. Most of the functionality in a debugger can only be used when the debuggee is broken and so we are now in a position to start looking at the actual diagnostic functionality that users expect. We'll be covering this in a future article - if you're interested, then drop me a line.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“My definition of an expert in any field is a person who knows enough about what's really going on to be scared.” - P. J. Plauger