Debugger Tips, Tricks and Tools #3

Use tracepoints to log execution flow and even modify it!

You may or may not know about tracepoints one of the cool new features of Visual Studio 2005. Basically a tracepoint is just like a breakpoint, except it doesn't break execution of your program. What use is it then? Well, rather than break, they allow you to print a message when they are "hit". This functionality is fairly obvious in its usefullness, I think (hope).

For you newbies out there, to create a tracepoint, you:

  1. Create any type of breakpoint.
  2. Right click on the breakpoint glyph in the channel to the left of your editor and choose "When Hit..."

A dialog will appear that allows you to choose what message you'd like to print when the tracepoint is hit. If you clear the "Continue execution" checkbox at the bottom of the dialog, the tracepoint will turn into a breakpoint. 

This is all great, but did you know you could use a tracepoint to modify the flow of your program's execution while under a live debugging session? I've used this feature a couple times for real-life bug investigation. Say you have the following code:

int j = 1000;

for (int i = 0; i < 1000; i++)

{

j--;

printf("j = %d\n", j);

}

Let's say you determine for whatever reason, that the j-- should NOT occur inside this loop (making the loop useless, but go with me on this) and you don't really want to stop debugging to rebuild and start the process again. First, switch to view disassembly for this block of code to see something like this (the order here is somewhat important as will be explained below):

int j = 1000;

004113DC mov dword ptr [j],3E8h

for (int i = 0; i < 1000; i++)

004113E3 mov dword ptr [i],0

004113EA jmp wmain+55h (4113F5h)

004113EC mov eax,dword ptr [i]

004113EF add eax,1

004113F2 mov dword ptr [i],eax

004113F5 cmp dword ptr [i],3E8h

004113FC jge wmain+84h (411424h)

{

j--;

004113FE mov eax,dword ptr [j]

00411401 sub eax,1

00411404 mov dword ptr [j],eax

printf("j = %d\n", j);

00411407 mov esi,esp

00411409 mov eax,dword ptr [j]

0041140C push eax

0041140D push offset string "j = %d\n" (41563Ch)

00411412 call dword ptr [__imp__printf (4182C4h)]

00411418 add esp,8

0041141B cmp esi,esp

0041141D call @ILT+315(__RTC_CheckEsp) (411140h)

}

Set a breakpoint on address 0x004113FE. Right click on the bp glyph and choose "When Hit...". Rather than print a message we want to change the current IP to be somewhere other than this line. To do this we will supply a message that actually evaluates a special expression and has the side-effect of changing the current instruction pointer. The message looks like the following:

Skipping j-- line! {eip=0x00411407}

When this message is "printed" it basically changes the value of the eip register, thus making the next instruction the beginning of the printf line. Setting other properties of the tracepoint, such as hit count and condition allows you to get even fancier.

There are some big caveats here though. First, the more the tracepoint is hit the slower your process will run. Second, in VS2005, tracepoints are NOT hit when you step to or over them and can even cause the step operation to turn into a continue operation (YUCK!). This behavior has been addressed in VS2008. But probably most importantly is that using this technique can wreak havoc on a debugging session if you're not aware of it's use. In addition to the obvious problems of specifying invalid or problematic places to jump, if you change and rebuild your application, your addresses will also very likely change! Obviously, if you have a tracepoint that sets the current instruction to the same place an particular piece of code USED to be, your application will likely get into a very, very bad state. It is for this reason that I asked you to switch to disassembly mode BEFORE setting your breakpoint. When a bp is set in disassembly, an address breakpoint is created. Address bp's are always automatically disabled when a debugging session is restarted. However, there's no reason why you couldn't have set a source bp BEFORE switching to disassembly and made it a tracepoint that had the same functionality. In this case though, the tracepoint will NOT be automatically disabled.

Nevertheless, when the need arises, this can be a powerful little trick!