Simple debugging with gdb
Debugging
Finding and repairing software errors is known as debugging. When a program has bugs, it can crash, behave unpredictably, or generate inaccurate results. In most cases, the steps involved in debugging are:
Identifying the Bug: This can be done through testing or user reports. The developer needs to understand the circumstances under which the bug occurs.
Isolating the Problem: The developer narrows down the part of the code where the bug is located. This might involve examining error messages, using debug logs, or employing debugging tools.
Understanding the Bug: It’s important to understand why the bug is occurring. This might involve studying the relevant section of the code and understanding how it interacts with the rest of the system.
Fixing the Bug: Once the bug is understood, the developer modifies the code to fix it. This also involves ensuring that the fix doesn’t introduce new bugs.
Testing the Fix: After the bug is fixed, the software must be tested to ensure that the bug is truly gone and that no new issues have been introduced.
Documenting the Change: It's good practice to document the changes made during debugging, especially in a team environment or for future reference.
Debugging can be challenging, especially in complex software systems, as bugs can be subtle and hard to reproduce.
GDB (GNU Debugger)
GDB is the GNU Project's debugger, a powerful tool used in software development for debugging applications written in C, C++, and other programming languages. Key features and uses of GDB include:
Breakpoints: Setting breakpoints allows the program to be stopped at specific points to inspect the current state.
Stepping Through Code: GDB allows developers to execute code line by line (or instruction by instruction) to observe the behavior of the program at each step.
Inspecting Variables: It can be used to print out the current values of variables at any point during the program execution.
Watching Variables: GDB can watch variables for changes, which helps in understanding how and why their values are altered.
Evaluating Expressions: Evaluating expressions at runtime can help in understanding the program flow and logic.
Call Stack Inspection: It allows inspection of the call stack, helping to trace the sequence of function calls leading to a particular point in execution.
Handling Signals and Exceptions: GDB can catch signals and exceptions, providing a way to debug issues related to these events.
Post-Mortem Debugging: It can analyze core dumps, which are snapshots of a program's state at the time of a crash, allowing post-mortem debugging.
Remote Debugging: GDB can be used to debug programs running on different machines.
Though it may be intimidating at first, the command-line tool GDB provides profound insights into a program's operation. Its versatility, power, and the wealth of information it gives developers make it a popular choice.
To demonstrate how GDB can be used for debugging, let's go through a simple example. We'll create a basic C program that has a bug, and then use GDB to find and fix the bug.Certainly! Let's revisit the debugging process of the sample C program using GDB, incorporating additional commands such as continue
, list
, step
, and run
.
Step 1: Write a Sample C Program
Here's the C program with a bug:
#include <stdio.h>
int factorial(int n) {
if (n == 1) return 1;
return n * factorial(n - 1); // Bug: No base case for n=0
}
int main() {
int num = 5;
printf("Factorial of %d is %d\n", num, factorial(num));
return 0;
}
Save this as example.c
.
Step 2: Compile with Debug Information
Compile the program to include debug information:
gcc -g example.c -o example
Step 3: Start GDB
Launch GDB:
gdb ./example
Step 4: Set a Breakpoint
Set a breakpoint at the start of main
:
(gdb) break main
Step 5: Run the Program
Start the program execution:
(gdb) run
Execution will stop at the beginning of main
.
Step 6: List Source Code
List the source code around the current line:
(gdb) list
This will display the source code near the current breakpoint.
Step 7: Step Into Functions
Step into the factorial
function:
(gdb) step
This will take you to the first line of the factorial
function.
Step 8: Inspect and Step Through
Inspect variables and step through the code:
(gdb) print n
(gdb) step
Repeat the print
and step
commands to observe how n
changes with each recursive call.
Step 9: Continue Execution
Once you've identified the bug (no base case for n == 0
), you can let the program continue running:
(gdb) continue
The program will run to completion or until it hits another breakpoint.
Step 10: Fix the Bug
Exit GDB and fix the bug in example.c
:
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
Step 11: Rebuild and Rerun
Recompile the program and start GDB again:
gcc -g example.c -o example
gdb ./example
Step 12: Debug the Fixed Program
Set the breakpoint, run the program, and use list
, step
, print
, and continue
as needed to ensure the bug is fixed.
Step 13: Exit GDB
Once satisfied with the fix:
(gdb) quit
This demonstrates a more detailed debugging session using GDB, utilizing a variety of commands to navigate and inspect the program execution.
Subscribe to my newsletter
Read articles from Jyotiprakash Mishra directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Jyotiprakash Mishra
Jyotiprakash Mishra
I am Jyotiprakash, a deeply driven computer systems engineer, software developer, teacher, and philosopher. With a decade of professional experience, I have contributed to various cutting-edge software products in network security, mobile apps, and healthcare software at renowned companies like Oracle, Yahoo, and Epic. My academic journey has taken me to prestigious institutions such as the University of Wisconsin-Madison and BITS Pilani in India, where I consistently ranked among the top of my class. At my core, I am a computer enthusiast with a profound interest in understanding the intricacies of computer programming. My skills are not limited to application programming in Java; I have also delved deeply into computer hardware, learning about various architectures, low-level assembly programming, Linux kernel implementation, and writing device drivers. The contributions of Linus Torvalds, Ken Thompson, and Dennis Ritchie—who revolutionized the computer industry—inspire me. I believe that real contributions to computer science are made by mastering all levels of abstraction and understanding systems inside out. In addition to my professional pursuits, I am passionate about teaching and sharing knowledge. I have spent two years as a teaching assistant at UW Madison, where I taught complex concepts in operating systems, computer graphics, and data structures to both graduate and undergraduate students. Currently, I am an assistant professor at KIIT, Bhubaneswar, where I continue to teach computer science to undergraduate and graduate students. I am also working on writing a few free books on systems programming, as I believe in freely sharing knowledge to empower others.