I posted this mainly because it’s an important reference for something I hope to cover here in future, something that involves analysing code for possible vulnerabilities. Although there are GUI alternatives out there, it’s more than likely I’ll be using the GNU debug utility, as it’s the most commonly referred to for UNIX-based installations.
Compiling the Program
Taking a program I wrote several months ago as an example, the straightforward way to compile is:
g++ gdb-example.cpp -o example
However, here we want to debug the program, and several other options should be added:
g++ example.cpp -g -Wall -Werror -o example
The ‘-Wall‘ option enables all warning flags, and ‘-Werror‘ marks the warnings as errors. In other words, the program might run perfectly, but anything that might conceivably cause a problem will be flagged. In this case, no warnings or error messages were displayed, but the debug symbols exist in the executable, which is called gdb-example.
Debugging with GDB
Now we can move on to debugging the executable with GNU Debug by typing:
This loads the executable and gives us GDB’s command line and the (gdb) prompt. From here it’s possible to run the program and view any messages after it’s executed:
Well, the program works, but the whole point of debug is to actually analyse the program. One thing we could do is set breakpoints at specific lines in the code:
(gdb) break 5
The output looks something like this when it reaches that point:
As we can see, the debug shows us the contents of the relevant buffers (by their variable names) at the moment the breakpoint was reached.
Just in case we didn’t know exactly where the program breaks, the ‘list‘ command prints the 10 lines of code around it:
How did that happen, if the breakpoint was set at line 5? Well, C++ programs rarely (in the real world) execute in the order they’re coded, and the int main() function always executes before any function preceding it. In this example, the program had started at int main() and obtained the two input values before jumping to line 5.
With this in mind, it’s important to remember that a program might never reach a given breakpoint.
GDB also has a feature we could use for gathering information about how a program uses variables and buffers, and this might help a lot when it comes to analysing decompiled executables with the original identifiers missing. To view the current value of a variable:
(gdb) print variable
And change the contents of the variable’s buffer with:
(gdb) set variable = value
We can also watch for changes in variables as the program’s running with:
(gdb) watch variable
If there is more than a single function, a watchpoint cannot just be assigned to a local variable. A breakpoint must first be set somewhere within the same function. Here I had to quit GDB (using the ‘quit‘ command), restart it, set a breakpoint in the right function, then wait until execution reached that before setting the watch point: