Debugging and Profiling Linux Applications with GDB and strace

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 Program

To 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 Commands

Here 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

Example: Debugging a Segmentation Fault

Suppose you have this buggy C program:

#include <stdio.h> int main() { int *ptr = NULL; *ptr = 5; // Segmentation fault return 0; }

Steps to debug:

  1. Compile: gcc -g -o crash crash.c

  2. Launch: gdb ./crash

  3. Run: run

  4. When it crashes: backtrace to see the stack, list to show code, print ptr to see it's NULL.

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

Basic Usage

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

Understanding Output

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.

Example: Diagnosing a Startup Failure

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 GDB

Sometimes 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 Logging

Save 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.

George Whittaker is the editor of Linux Journal, and also a regular contributor. George has been writing about technology for two decades, and has been a Linux user for over 15 years. In his free time he enjoys programming, reading, and gaming.

Load Disqus comments