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!