Key facts

  • The operating system (OS) allows you to assign a “debugger” process to a process.
  • Let’s call the debugging process, the debugger and the process being debuged the debugee.
  • The debugger has read/write access to the debugee’s entire virtual address space.
  • When a debugee throws an exception, the OS gives the debugger a chance to respond first, then it passes it to the debugee.
  • “Debug info” is basically information that maps from source file (lines in the code) to locations in the binary (and vice versa).
  • Debug info can either be placed directly in the binary, or it can be in a seperate .pdb file.
  • Debug info can be generated by the compiler when you are compiling the binary.

How it works

When you put a break point using your IDE, your IDE tells your debugger the source location (i.e. the line). Your debugger uses the debug info (either embedded in the debugee or in a .pdb file) to map this source location to a binary. It changes the first binary instruction in that location to a special instruction that will tell the CPU to throw a “break point” exception. When the CPU throws the break point exception, the exception is forwarded to the debugger, the debugger pauses execution of the program (i.e. blocks it). Since the debugger has read/write access to the debugee’s virtual address space, it can see all the variables, it reports these to the IDE. When you press continue in your IDE, your IDE tells the debugger to continue. Your debugger then changes that binary instruction from the break point instruction back to whatever it was. It then unpauses the debugee.

What about when you “step down”?. Well, your debugger uses the debug info to find the binary location of the next line of code. It puts the break point binary instruction there. Then lets the debugee run. You already know what happens when the debugee reaches the break point binary instruction.

The other things like “step in” and “step out” work in a similar way.

Not too bad eh?