A (Re-)introduction to GDB

One of the most useful tools in your arsenal when writing C programs is the debugger. Unlike Java, C doesn’t provide a stack trace when the program crashes – most of the time you’ll get something thoroughly unhelpful along the lines of SEGFAULT (core dumped). Sometimes the issue is evident simply by reading through the source code, but most of the time you’ll need some help - this is where GDB comes in. Some editors/IDEs do support debugging C programs with GDB, but this tutorial will focus on the command line interface as it’s less temperamental to set up.

We’re going to use GDB to debug and fix a bug in a factorial program. Start by downloading this zip archive which contains the program source code and a Makefile. Read through the source to see what it does, then compile and execute it (make && ./fact). As you can see, it doesn’t give the correct answer, so we’ll need to debug it to figure out why.

Start by loading the program in the debugger: gdb fact (on MacOS, this will be lldb fact). Before executing the program, set a breakpoint on line 10 using the command break <line-number>. You can then run the program with the command run. Step through the code with the command next to execute the code line-by-line to see if you can identify the problem. You may find it useful to print the value of various local variables with the command p <variable-name>. Once you’ve identified the problem(s) with the factorial program, make the necessary changes and test it to see if you were right.

Note: GDB is commonly available on Linux (and on WSL if you install it). On Windows, you can use gdb outside WSL if you use a compatible compiler toolchain like MinGW-w64 since msvc (Microsoft's Visual C++ compiler) uses a different ABI (Application Binary Interface) and requires a debugger that understands this ABI. MacOS uses the LLVM toolchain with LLDB as the debugger. However, a port of gdb does exist on brew if you prefer gdb. All of the features we’ll be using today are available in both LLDB and GDB, but the syntax for some of the commands is slightly different. You can consult the GDB to LLDB command map if you run into issues with the instructions in this tutorial.

Running tests while GDB is open

If you have a failing test, you may want to run it while using the debugger. This is not as simple as running the shell script in GDB. If you want to debug the same sequence of commands you run in a test, you will first need to load mysmtpd into gdb by running gdb ./mysmtpd, set helpful breakpoints, then run <port-number>. On another terminal, run netcat and pipe in your test input file, nc localhost <port-number> < <input-file>. Look up Tutorial-2's Part 1 for more explanation of how this works and how to create the input file. In tutorial 2, we used netcat as a server because we implemented a DICT client. With SMTP, we implement a server and let netcat be a mock client.

Sample Solution

int main()
{
	int i, num, j = 1;
	printf ("Enter the number: ");
	scanf ("%d", &num );
  
	for (i=1; i<=num; i++)
		j=j*i;    

	printf("The factorial of %d is %d\n",num,j);
}