Debugging and Profiling Linux Applications with GDB and strace

Debugging and profiling are critical skills in a developer's toolbox, especially when working with low-level system applications. Whether you're tracking down a segmentation fault in a C program or understanding why a daemon fails silently, mastering tools like GDB (GNU Debugger) and strace can dramatically improve your efficiency and understanding of program behavior.
In this guide, we’ll dive deep into these two powerful tools, exploring how they work, how to use them effectively, and how they complement each other in diagnosing and resolving complex issues.
The Essence of Debugging and Profiling
What is Debugging?Debugging is the systematic process of identifying, isolating, and fixing bugs—errors or unexpected behaviors in your code. It’s an integral part of development that ensures software quality and stability. While high-level languages may offer interactive debuggers, compiled languages like C and C++ often require robust tools like GDB for line-by-line inspection.
What is Profiling?Profiling, on the other hand, is about performance analysis. It helps you understand where your application spends time, which functions are called frequently, and how system resources are being utilized. While GDB can aid in debugging, strace provides a view of how a program interacts with the operating system, making it ideal for performance tuning and root cause analysis of runtime issues.
Getting Hands-On with GDB
What is GDB?GDB is the standard debugger for GNU systems. It allows you to inspect the internal state of a program while it’s running or after it crashes. With GDB, you can set breakpoints, step through code, inspect variables, view call stacks, and even modify program execution flow.
Preparing Your ProgramTo make your program debuggable with GDB, compile it with debug symbols using the -g
flag:
gcc -g -o myapp myapp.c
This embeds symbol information like function names, variable types, and line numbers, which are essential for meaningful debugging.
Basic GDB CommandsHere are some fundamental commands you'll use frequently:
gdb ./myapp # Start GDB with your program run # Start the program inside GDB break main # Set a breakpoint at the 'main' function break filename:line# Break at specific line next # Step over a function step # Step into a function continue # Resume program execution print varname # Inspect the value of a variable backtrace # Show the current function call stack quit # Exit GDB
Suppose you have this buggy C program:
#include <stdio.h> int main() { int *ptr = NULL; *ptr = 5; // Segmentation fault return 0; }
Steps to debug:
-
Compile:
gcc -g -o crash crash.c
-
Launch:
gdb ./crash
-
Run:
run
-
When it crashes:
backtrace
to see the stack,list
to show code,print ptr
to see it'sNULL
.
GDB clearly shows you tried to dereference a NULL pointer.
Exploring System Interactions with strace
What is strace?While GDB looks inside your program, strace watches how it communicates with the operating system. It intercepts and records system calls—requests made by your application to the Linux kernel. This includes file access, memory management, network communication, and process control.
Common Use Cases-
Investigating why a program fails to open a file
-
Detecting missing libraries or incorrect paths
-
Debugging permission issues
-
Profiling syscall-heavy applications
strace ./myapp # Trace all system calls strace -p <PID> # Attach to a running process strace -o trace.log ./app # Save output to file
To limit output to specific syscalls:
strace -e trace=open,read,write ./myapp
A typical line looks like this:
open("config.txt", O_RDONLY) = -1 ENOENT (No such file or directory)
This tells you your application tried to open config.txt
but it didn’t exist, which could explain a crash or misbehavior.
If your program silently exits without logging anything, run:
strace ./myapp
If you see something like:
open("/etc/myapp.conf", O_RDONLY) = -1 ENOENT
Now you know it expects a configuration file that’s missing. Problem identified without touching the source code.
Advanced Techniques and Tips
Debugging Core Dumps with GDBSometimes a program crashes outside your control. Linux can generate a core dump—a snapshot of the program’s memory at the moment it crashed.
Enable core dumps:
ulimit -c unlimited
After a crash, run:
gdb ./myapp core
You can then inspect the call stack, variables, and memory state as if you were there at the crash.
Using strace with Other Tools-
lsof: List open files.
-
ltrace: Trace library calls (complements
strace
). -
perf: Advanced profiling and performance analysis.
-
valgrind: For memory leaks and heap corruption.
Combining tools provides a fuller picture of application behavior.
Automating and LoggingSave GDB sessions with logging:
set logging on run quit
You can also script GDB with .gdbinit
files or use expect
scripts to automate interactive sessions for testing and CI pipelines.
Conclusion
Debugging and profiling aren’t just about fixing bugs—they’re about understanding your application deeply. With GDB, you step through logic and memory; with strace, you observe your program’s interaction with the operating system.
Each tool tells a different story:
-
GDB is the detective, examining the scene of the crash.
-
strace is the reporter, recording everything the suspect said to the operating system.
When used together, they become an unstoppable force in diagnosing and resolving complex problems.