Introduction to gdb Debugging
This tutorial introduces the GNU Debugger (gdb), a vital tool for debugging C programs. Learn how to effectively use gdb to examine program states, set breakpoints, and step through code to resolve issues. We begin by verifying gdb installation on an Ubuntu 22.04 Docker container. Next, we'll craft a basic C program containing a runtime error and demonstrate how to debug it with gdb. Finally, we'll explore debugging a multithreaded C program using gdb, a critical skill for systemadmin tasks.
Getting Started with gdb
This section covers the fundamentals of the GNU Debugger (gdb) and its application in debugging C programs. gdb empowers you to inspect the inner workings of a running application, establish breakpoints at key points, and meticulously step through the code to pinpoint and rectify errors.
First, confirm gdb is installed on your Ubuntu 22.04 Docker container. Execute the following commands:
sudo apt-get update
sudo apt-get install -y gdb
Example output:
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
libasan6 libubsan1 python3-gdb
Suggested packages:
gdb-multiarch
The following NEW packages will be installed:
gdb libasan6 libubsan1 python3-gdb
0 upgraded, 4 newly installed, 0 to remove and 0 not upgraded.
Need to get 3,470 kB of archives.
After this operation, 13.5 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Now, create a simple C program for debugging. In the ~/project
directory, create a file named example.c
with the code below:
#include <stdio.h>
int main() {
int a = 5;
int b = 0;
int c = a / b;
printf("The result is: %d\n", c);
return 0;
}
This program will intentionally trigger a division by zero error at runtime.
Debug this program with gdb by following these steps:
- Compile the program including debugging symbols using the
-g
flag:
gcc -g -o example example.c
- Launch the gdb debugger and load the compiled executable:
gdb ./example
- Within the gdb prompt, set a breakpoint at the beginning of the
main
function:
(gdb) break main
Breakpoint 1 at 0x11a6: file example.c, line 4.
- Execute the program:
(gdb) run
Starting program: /home/labex/project/example
- When execution halts at the breakpoint, you can examine variables, step through the code, and debug the error:
Breakpoint 1, main () at example.c:4
4 int a = 5;
(gdb) print a
$1 = 5
(gdb) next
5 int b = 0;
(gdb) print b
$2 = 0
(gdb) next
6 int c = a / b;
As demonstrated, gdb effectively catches the runtime error caused by division by zero, enabling in-depth debugging.
Debugging a C Program Step-by-Step with gdb
This section guides you through debugging a simple C program with a runtime error using gdb.
Start by creating a new C file named simple.c
in the ~/project
directory and adding the following content:
#include <stdio.h>
int main() {
int x = 10;
int y = 0;
int z = x / y;
printf("The result is: %d\n", z);
return 0;
}
This program deliberately divides by zero, which will generate a runtime error.
Compile the program with the -g
flag to enable debugging symbols:
gcc -g -o simple simple.c
Launch the gdb debugger and load the compiled program:
gdb ./simple
In the gdb prompt, set a breakpoint at the main
function:
(gdb) break main
Breakpoint 1 at 0x11a6: file simple.c, line 4.
Now, execute the program:
(gdb) run
Starting program: /home/labex/project/simple
Once the program stops at the breakpoint, you can inspect variables, step through the code line by line, and effectively debug the problem:
Breakpoint 1, main () at simple.c:4
4 int x = 10;
(gdb) print x
$1 = 10
(gdb) next
5 int y = 0;
(gdb) print y
$2 = 0
(gdb) next
6 int z = x / y;
As the program attempts to divide by zero, it will raise a runtime error which you can diagnose and address using gdb.
Using gdb to Debug Multithreaded C Programs
This segment demonstrates how to use gdb to debug a multithreaded C program containing a race condition. This is crucial for systemadmin tasks involving concurrent processes.
Begin by creating a new C file called multithreaded.c
in the ~/project
directory with the following code:
#include <stdio.h>
#include <pthread.h>
int shared_variable = 0;
void* thread_function(void* arg) {
for (int i = 0; i < 1000000; i++) {
shared_variable++;
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, thread_function, NULL);
pthread_create(&thread2, NULL, thread_function, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("Final value of shared_variable: %d\n", shared_variable);
return 0;
}
This program creates two threads, each incrementing a shared variable 1,000,000 times. Because of the inherent race condition, the shared variable's final value may not reach the expected 2,000,000.
Compile the program, ensuring to include debugging symbols with the -g
flag and linking the pthread library:
gcc -g -o multithreaded multithreaded.c -lpthread
Start the gdb debugger and load the compiled program:
gdb ./multithreaded
Within the gdb prompt, set a breakpoint at the start of the thread_function
:
(gdb) break thread_function
Breakpoint 1 at 0x11b6: file multithreaded.c, line 7.
Execute the program:
(gdb) run
Starting program: /home/labex/project/multithreaded
When the program hits the breakpoint, you can examine variables, step through the code, and debug the race condition:
Breakpoint 1, thread_function (arg=0x0) at multithreaded.c:7
7 for (int i = 0; i < 1000000; i++) {
(gdb) print shared_variable
$1 = 0
(gdb) step
8 shared_variable++;
(gdb) print shared_variable
$2 = 1
By stepping through the code and observing the shared variable, you can visualize the race condition and identify the source of the problem within the multithreaded program. This is a key skill for any systemadmin managing Linux systems.
Conclusion
This lab introduced the GNU Debugger (gdb) and its critical role in debugging C programs. We confirmed gdb's installation on an Ubuntu 22.04 Docker container, then created a simple C program with a runtime error for debugging practice. Compilation involved the -g
flag to include debugging symbols. We launched gdb, set a breakpoint at the main
function, and ran the program. Upon reaching the breakpoint, we examined variables, stepped through the code, and resolved the issue.
This tutorial covered the essential techniques for using gdb to debug both simple and multithreaded C programs. Through step-by-step instructions, learners gained practical experience with this powerful debugging tool, an invaluable asset for any systemadmin.