Code Sprint 2012: Debugger

Blog post by anevilyak on Sat, 2012-11-10 14:44


After fixing a crash bug uncovered during the Begeistert weekend by Ingo and Stephan, my first goal was to implement support for typecasting. In a nutshell, what this allows one to do is tell the debugger to treat a variable (or even a subfield of a variable) as a different type than what the debug information reports it as being. A canonical example would be the case of base classes vs derived classes in C++. Let's say we have a function that takes a pointer or reference to a particular class as one of its arguments. As such, the debug information generated by gcc will only tell us about the base class at that point in time. However, if, on a particular invocation you know that the passed-in class is in fact a particular subclass, it would be useful to be able to see the additional fields that the subclass contains. Typecasting allows one to do precisely that. Another example from the world of BeOS/Haiku programming are thread functions, where the only allowed argument is of type void *. Since this type has absolutely no additional information associated with it, the debugger's display of such a variable is, shall we say, not so helpful:

Thankfully, typecasting can come to the rescue in this case as well. To illustrate the steps:

Step 1: Right click the variable and choose the "Cast as..." item.

Step 2: Input the new type that the variable should be shown as.

Step 3: Profit! In all seriousness, one now has a much more useful view of the variable in question, as can be seen here. This feature is currently in a quite basic form though, and doesn't yet understand some forms of types such as arrays, but I will be working on that further as time permits.


A quite frequent problem one comes across in debugging is data corruption of various forms. Unfortunately for us though, more often than not the point at which such an issue is actually detected is much later than the point at which the data was actually overwritten, and the thread/function in which the crash is seen is simply a hapless victim. This is where watchpoints come in. Almost any modern CPUs has a set of debug registers. These are used to implement both breakpoints and watchpoints, and have varying limitations. In the case of x86, there are 4 debug registers total. One of these is reserved for the kernel, and one is used to implement breakpoints. This leaves us two with which to create watchpoints. While the capabilities of these vary across CPUs, in x86's case in particular each watchpoint is able to watch up to 4 bytes of data for writes. Let us illustrate this feature with a very simple program:

Let's suppose we want to know when the variable x gets modified. To do so, we set a watchpoint on it as follows:

Step 1: right click the variable and choose the "Watch..." item.

Step 2: Configure the watchpoint. The default settings displayed are inferred from the variable one has picked, but can then be modified afterwards before actually setting the watchpoint.

Step 3: Let the program run until the watchpoint is triggered. Note that since the CPU will finish executing the instruction that performs the modification prior to signalling the debug exception, the break may actually occur at the line after the modification has actually taken place, though not necessarily depending on the complexity of the statement which the modification is part of. As with typecasting, this feature currently has some caveats. As stated, each hardware watchpoint is only able to watch 4 bytes at a time. Thus, if one wanted to watch, for instance, a 64-bit variable, it would take two registers to do so. Our watchpoint implementation is not yet smart enough to do this automatically for you. Furthermore, it currently isn't aware of the architectural limitations of the current platform, so it doesn't yet restrict what one can choose in the watchpoint configuration dialog. In the future these will be fixed, and eventually it may also be possible to add software emulation of watchpoints to lift the 2-watchpoint restriction. The latter will require support from the kernel's virtual memory subsystem though, and as such is a bit more involved/time consuming to properly implement.


I had also begun working on support for saving crash reports. The intention here is to make it easy for users to submit useful information about a crash without having to actually know the ins and outs of the debugger in question. Once this feature is implemented, both the debugger itself and the system crash dialog that is presented on program crashes will gain the ability to let one simply pick to save a crash report with all the pertinent information. This can then be attached to a Trac ticket for our use. Unfortunately, I ran out of time so this feature is still a work in progress and not yet present in the tree. I intend to complete it in the coming days/weeks as time permits though.