2.8 The debugger
When every other checking tool fails to detect the problem, then it is debugger's turn. A debugger allows working through the code line-by-line to find out what it is going wrong, where and why. It allows working interactively, controlling the execution of the program, stopping it at various times, inspecting variables, changing code flow whilst running.
In order to make use of a debugger, a program must be compiled with debugging information inserted. This information is provided by debugging symbols included by the compiler in the binaries. Debugging symbols describe where functions and variables are stored in memory. An executable with debugging symbols can run as a normal program, it is just slightly slower.
An important feature of debuggers is the possibility to set breakpoints. Breakpoints stop program execution on demand: the program runs normally until it is about to execute the piece of code at the same address of the breakpoint. At that point it drops back into the debugger to look at variables, or continue stepping through the code. Breakpoints are fundamental in interactive debugging, and accordingly have many options associated with them. They can be set up on a specific line number, at the beginning of a function, at a specific address, or conditionally (i.e. as soon as a condition is verified).
After stopping the program as a consequence of a breakpoint, a debugger can resume its execution. There are several ways in which this can be done. The debugger can execute just the next program line stepping over any function calls in the line. This way any call will be executed in one go, as if it were a single instruction. Alternatively, the debugger can step into a function call, executing also its code line by line. Obviously, the debugger can also go on running your program without performing any actions.
Another important feature that all decent debuggers must offer is the possibility to set watchpoints. Watchpoints are particular type of breakpoints which stop the code whenever a variable changes, even if the line doesn't reference the variable explicitly by name. Instead, a watchpoint looks at the memory address of the variable and alerts the programmer when something is written to it.
In large programs, adding breakpoints for every iteration of a loop is prohibitive. It is not necessary to step through each one in turn: a technique known as binary split can greatly simplify the debugging process. The technique consists in placing a breakpoint at the last line of the first half of the code, and running the code. If the problem does not manifest itself, then the fault is likely to be within the second half. From here, the procedure is repeated with the region where the problem is supposed to be, reducing the area under test at each iteration. At the end, the method either leaves you with just one line, or a sufficiently small routine that can be stepped through. A binary split [13] can limit the search area of a 1000 line program to just 10 steps!
Algorithm implementation errors are reasonably easy to track down with a debugger. Stepping through, looking for an invalid state or bad data, is enough: the last statement to execute either is itself wrong or at least points you at the problem.