Articles

How to Debug a Segmentation Fault in C on Linux Using GDB

Debugging Segmentation Faults in C on Linux: A Practical Hands-On Guide

If you have ever written C code on Linux, you have probably seen the dreaded message: Segmentation fault (core dumped). It is one of the most common and frustrating errors in C programming, but with the right tools and approach, finding the exact line that crashes your program takes only a few minutes.

This tutorial walks you through the full workflow: reproducing the bug, capturing a core dump, loading it in GDB, and using commands like backtrace, frame, and print to nail down the root cause. We will also cover the most frequent segfault patterns with working code examples.

linux terminal debugging

What Is a Segmentation Fault?

A segmentation fault (SIGSEGV, signal 11) happens when a process tries to access a memory region it is not allowed to touch. The Linux kernel detects the violation through the MMU, kills the process, and (if configured) writes a core dump containing a snapshot of the program’s memory at the moment of the crash.

Common triggers include:

  • Dereferencing a NULL pointer
  • Writing past the end of an array (buffer overflow)
  • Using a dangling pointer after free()
  • Infinite recursion causing a stack overflow
  • Writing to read-only memory (string literals)

Step 1: Compile With Debug Symbols

Before you can debug anything meaningful, recompile your program with the -g flag and disable optimizations:

gcc -g -O0 -Wall -Wextra myprogram.c -o myprogram

Without -g, GDB will only show you addresses and assembly instructions instead of source lines and variable names.

linux terminal debugging

Step 2: Enable Core Dumps

On most modern Linux distributions, core dumps are disabled by default. Check the current limit:

ulimit -c

If it returns 0, enable unlimited core dumps for the current shell:

ulimit -c unlimited

You also need to know where the core file will land. On systems using systemd-coredump (Ubuntu, Fedora, Debian), cores are stored in the journal. Retrieve them with:

coredumpctl list
coredumpctl gdb

On systems without systemd-coredump, check the core_pattern:

cat /proc/sys/kernel/core_pattern

To force core files into the current directory:

echo "core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern

Step 3: Reproduce the Crash

Let’s use a buggy program with a classic null pointer dereference:

// segfault.c
#include <stdio.h>
#include <string.h>

void corrupt(char *dest, const char *src) {
    strcpy(dest, src);
}

int main(void) {
    char *buffer = NULL;
    corrupt(buffer, "hello world");
    printf("%s\n", buffer);
    return 0;
}

Compile and run:

gcc -g -O0 segfault.c -o segfault
./segfault
Segmentation fault (core dumped)

Step 4: Load the Core Dump in GDB

If you got a core file in the directory:

gdb ./segfault core.segfault.12345

If using systemd-coredump:

coredumpctl gdb segfault

You can also run the binary directly inside GDB without needing a core file:

gdb ./segfault
(gdb) run
linux terminal debugging

Step 5: Use GDB to Find the Faulting Line

The backtrace command

Once GDB stops on the crash, type:

(gdb) bt

You will see something like:

#0  0x00007ffff7e3a1c0 in __strcpy_avx2 () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x0000555555555169 in corrupt (dest=0x0, src=0x555555556004 "hello world") at segfault.c:5
#2  0x000055555555519a in main () at segfault.c:10

Frame #1 is your code. The argument dest=0x0 immediately tells you a NULL pointer was passed in.

The frame command

Switch to the frame that belongs to your code:

(gdb) frame 1

GDB will show the exact source line:

#1  corrupt (dest=0x0, src=0x555555556004 "hello world") at segfault.c:5
5           strcpy(dest, src);

The print command

Inspect variables and pointers in scope:

(gdb) print dest
$1 = 0x0
(gdb) print src
$2 = 0x555555556004 "hello world"

Confirmed: dest is NULL. Walking up to main shows where the bad value originated:

(gdb) frame 2
(gdb) print buffer
$3 = 0x0

Essential GDB Commands Cheat Sheet

Command Purpose
run / r Start the program inside GDB
bt / backtrace Show full call stack at crash
bt full Backtrace with all local variables
frame N Jump to stack frame number N
list / l Show source code around current line
print var / p var Print value of a variable
info locals Show all local variables
info registers Dump CPU registers
x/10xw addr Examine 10 words of memory at addr
break file:line Set a breakpoint
quit Exit GDB

Common Segmentation Fault Causes With Real Examples

1. NULL Pointer Dereference

int *p = NULL;
*p = 42;  // CRASH

GDB clue: the faulting instruction touches address 0x0. print p shows 0x0.

2. Dangling Pointer After free()

char *s = malloc(16);
strcpy(s, "hello");
free(s);
printf("%s\n", s);  // undefined behavior, often segfault

GDB clue: the pointer holds an address that no longer maps to a valid heap region. Combine GDB with Valgrind (valgrind --track-origins=yes ./prog) to confirm use-after-free.

3. Stack Overflow Through Infinite Recursion

void recurse(int n) {
    char buffer[4096];
    buffer[0] = (char)n;
    recurse(n + 1);
}

GDB clue: bt displays thousands of identical frames. The crash address is just below the current stack pointer ($rsp).

4. Buffer Overflow on Stack

char buf[8];
strcpy(buf, "this string is way too long");

GDB clue: the return address corruption often causes a crash inside a garbage address when the function returns. Compile with -fsanitize=address to catch it instantly.

5. Writing to Read-Only Memory

char *s = "constant";
s[0] = 'C';  // CRASH on most systems

GDB clue: the address belongs to the .rodata segment. Check with info proc mappings.

linux terminal debugging

Bonus: Combine GDB With Sanitizers

Modern GCC and Clang ship with AddressSanitizer (ASan), which catches many memory bugs before they turn into a generic segfault:

gcc -g -O0 -fsanitize=address -fno-omit-frame-pointer prog.c -o prog
./prog

ASan prints a precise report with the buggy line, the allocation site, and the free site. For threaded code, ThreadSanitizer (-fsanitize=thread) and UndefinedBehaviorSanitizer (-fsanitize=undefined) are equally valuable.

Workflow Summary

  1. Compile with -g -O0
  2. Enable core dumps with ulimit -c unlimited
  3. Reproduce the crash
  4. Open the binary and core file in GDB
  5. Run bt to see the call stack
  6. Use frame to jump to your code
  7. Use print and info locals to inspect state
  8. Cross-check with Valgrind or AddressSanitizer if needed

FAQ

Why does my program say “Segmentation fault” without creating a core dump?

Either ulimit -c is set to 0, or core_pattern sends the dump to a system service like systemd-coredump or apport. Use coredumpctl list to find them, or set ulimit -c unlimited in your shell.

Can I debug a segfault without a core dump?

Yes. Run the program directly inside GDB with gdb ./prog followed by run. GDB will halt at the moment of the crash with the same context a core dump provides.

What’s the difference between SIGSEGV and SIGBUS?

SIGSEGV means an invalid virtual memory access (no mapping or wrong permissions). SIGBUS usually means the address is mapped but unaligned, or a memory-mapped file backing was truncated.

Why does the bug disappear when I add a printf?

You are likely dealing with undefined behavior such as use of uninitialized memory or a buffer overflow. The printf changes register and stack layout, masking the symptom. Use AddressSanitizer to find the real cause.

Is GDB the only option on Linux?

No. LLDB works similarly with Clang-built binaries. Graphical front-ends like gdbgui, seer, or IDE integrations in VS Code and CLion wrap GDB with a friendlier interface.

How do I debug a segfault that only happens in production?

Ship binaries with separated debug symbols (objcopy --only-keep-debug), enable core dumps on the production host, then load the core on a developer machine with the matching debug symbols using gdb -s prog.debug -c core prog.

Final Thoughts

Debugging a segmentation fault in C on Linux is far less mysterious once you have a repeatable workflow: compile with debug info, capture a core dump, open it in GDB, and read the backtrace. Add Valgrind or AddressSanitizer to the toolbox and the vast majority of memory bugs will surrender within minutes instead of hours.

Latest Posts

No Posts Found!

Banner of the Day

NewsLetter

Do not miss our news
Sign up and receive the latest news of our company
Newsletter

Contact Info
Copyright © 2022 I-4 Linux. All Rights Reserved.