When You Hit a Segmentation Fault in a GPU Service: A Practical Approach from Analysis to Resolution

When You Hit a Segmentation Fault in a GPU Service: A Practical Approach from Analysis to Resolution

Hello!

Today, let's take a look at analyzing and dealing with segmentation faults in services that combine virtualized environments with GPUs.

What a Segmentation Fault Really Is

A segmentation fault is an exception raised by the OS when a program attempts to access a protected region of memory.
In our case, it occurred when several GPU services (that is, processes using the GPU) were running and we restarted one of them.
It does not happen every time. In fact, it occurs perhaps once in several hundred startups — but even a single occurrence can be devastating. The failure of one GPU service becomes a SPOF (single point of failure) that affects the entire service. Worse, once a segmentation fault occurred, the offending process would never start again — a truly troublesome phenomenon.

This characteristic — "it normally works fine, then suddenly stops working" — is what makes debugging extremely difficult.

In our experience, the combination of GPU and virtualization noticeably raises the probability of occurrence compared to traditional C++ applications.

To summarize the characteristics:

  • High frequency in GPU-based services (image recognition, machine learning workloads, and so on)
  • Tends to occur suddenly after long periods of operation
  • The difference from a CUDA unknown error: the latter may work if you wait and retry, but with a segmentation fault, the process never starts again.

(For readers with deep expertise who are itching to object: "segmentation fault" is admittedly a very coarse-grained error category, so please read this as "the one particular case we encountered.")

Common causes of segmentation faults include:

  • Dereferencing a NULL pointer
  • Accessing freed memory
  • Stack overflow
  • Writing to read-only memory

In a GPU-plus-virtualization environment, however, most of the time you simply cannot tell which of these is to blame.

A Real-World Case

Now, let us walk through a case we actually experienced recently.

When we tried to run a GPU-based application, the following log was output.

import 'scipy.special._orthogonal' # <_frozen_importlib_external.SourceFileLoader object at 0x7fc006e862b0>
# /home/user/anaconda3/envs/image_recognition_env/lib/python3.9/site-packages/scipy/special/__pycache__/_spfun_stats.cpython-39.pyc matches ...
...
import 'yaml' # <_frozen_importlib_external.SourceFileLoader object at 0x7fc003d215e0>
...
Segmentation fault (core dumped)

Core Dump Analysis: Getting to the Heart of the Problem

In GPU application development and operations — especially with C/C++ programs and Python C extension modules — the unexpected "Segmentation fault" error can strike. Even in Python applications that seemingly have nothing to do with C, the underlying libraries or the CUDA stack can be the cause (these days, that is the most common pattern).

This error usually appears with the message "Segmentation fault (core dumped)."

So what exactly is this "core dump"?

What is a core dump?

A core dump is a file capturing the memory state of a program at the moment it crashed. Analyzing it lets you see in detail where and in what state the program stopped, which helps you pinpoint the root cause.

STEP 1: Enable Core Dumps

By default, core dump generation may be restricted. First, lift that restriction.

# Remove the core dump size limit
ulimit -c unlimited

# Specify where core dumps are saved and how they are named (Ubuntu/Debian)
echo '/tmp/core.%e.%p.%t' | sudo tee /proc/sys/kernel/core_pattern

With this in place, on a crash a core dump file is saved in the /tmp directory, with a name containing the program name (%e), process ID (%p), and timestamp (%t).

STEP 2: Analyze the Core Dump with GDB

Use the GNU Debugger (GDB) to analyze the generated core dump.

# Analyze the core dump
# Example: for a Python program
gdb python /tmp/core.python.12345.1620000000

When GDB starts, you get a prompt. From there, use the following commands to dig into the problem.

STEP 2-1. Show the backtrace (stack trace)

(gdb) bt full

This command displays a detailed stack trace showing where the program stopped.

STEP 2-2. Move to a specific frame

When you want to drill into a particular function or operation in the stack trace, move to it by specifying the frame number.

(gdb) frame 3

STEP 2-3. Inspect variables

You can inspect the values of variables within a specific frame.

(gdb) print variable_name

STEP 2-4. Inspect memory contents

You can directly examine what is stored at a memory address.

(gdb) x/10x memory_address

An Example of a Real Error Case

In a common real-world scenario involving Python C extensions or GPU libraries, you might get a backtrace like the following.

#0  0x00007f92d3b7c7a0 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007f92d3b7e8fa in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007f92d5a1c063 in THCudaCheck () from /usr/local/lib/python3.9/site-packages/torch/lib/libtorch_cuda.so
#3  0x00007f92d5a2e4f5 in cudaLaunchKernel () from /usr/local/lib/python3.9/site-packages/torch/lib/libtorch_cuda.so

We can see that the problem occurred when a CUDA kernel was launched.

In cases like this involving external libraries, the cause is often a library configuration issue or a GPU driver version mismatch — but fully identifying the root cause is, practically speaking, nearly impossible (time-wise).

STEP 3: Analyze System Logs

Beyond core dump analysis, system-level logs also provide useful information. Check the relevant logs with the following commands.

dmesg | grep -i segfault
journalctl | grep -i segfault

This retrieves kernel-level messages, which can sometimes reveal information about memory issues or problems specific to virtualized environments. In practice, though, you rarely have the luxury of going this far.

Problems Specific to Virtualized Environments and Countermeasures

These days, systems commonly run in multi-layered, millefeuille-like virtualization stacks — Docker, VMs, and so on.
In such environments, privilege levels, resource limits, and memory access issues become far more complicated — reliably draining your motivation to debug.

STEP 1. Deal with the ptrace problem in Docker

When debugging inside a Docker container, ptraceattempting to use a debugger that relies on system calls (GDB, for example) can fail with a permission error.
This is because Docker, for security reasons, disables the SYS_PTRACE capability by default.

To solve this, you need to explicitly allow SYS_PTRACE. You can configure it with commands like the following.

# Enable SYS_PTRACE when starting the container
docker run --cap-add=SYS_PTRACE image_name

# Add SYS_PTRACE to an already-running container
docker update --cap-add=SYS_PTRACE container_name

With this, the debugger can run safely and correctly inside the container.

STEP 2. Capture stack traces with pstack

Checking what state a process is in when a problem occurs is a fundamental part of debugging.pstack is a tool that makes it easy to capture the stack trace of a running process. It is especially useful for investigating frozen processes or the causes of abnormal termination.

You can install and use pstack as follows.

sudo apt install pstack
pstack process_id

Running the command above immediately displays the current stack trace of the specified process. From the output, you can review the function call history and narrow down where the bug occurred.

STEP 3. Lightweight diagnosis of running processes and memory snapshots

When a running process starts behaving unexpectedly, taking a complete snapshot including its memory state allows for detailed investigation later. The tool for this is gcore.gcore saves the current state of the specified process as a core dump.

Usage is as follows.

gcore process_id  # Generate a core dump of the specified process

Once the core dump is generated, you can later use a debugger such as GDB to deeply analyze the process's memory contents and stack state at that moment. This lets you work on pinpointing the problem without stopping the running process.

As you can see, debugging problems specific to virtualized environments can be handled fairly effectively with the right tools and a healthy dose of grit. In Docker in particular, capability management is key, and combining it with process diagnostic tools (pstack and gcore) can — with persistence — let you identify and resolve the problem.

System Design for Preventing Recurrence

For sudden, hard-to-predict errors like segmentation faults, completely preventing them is difficult. The more realistic approach is to build mechanisms that minimize the impact when they do occur.

Implementing restarts and a watchdog process

You may groan, "That's your answer?!" — but in the end, a restart is the most effective remedy.

For GPU-based services, the most basic and effective way to prepare for the risk of a process dying suddenly is to implement a watchdog that monitors process liveness and automatically attempts a restart when the process stops.

Below is a simple example of a watchdog implemented in Python.

import subprocess
import time
import logging

def run_with_watchdog(command, max_retries=3, wait_time=5):
    for attempt in range(max_retries):
        try:
            process = subprocess.Popen(command, shell=True)
            return_code = process.wait()
            if return_code == 0:
                return True
            logging.warning(f"Process exited with return code {return_code}. Retry {attempt + 1}/{max_retries}")
        except Exception as e:
            logging.error(f"An exception occurred: {e}. Retry {attempt + 1}/{max_retries}")
        time.sleep(wait_time)
    return False

# Usage example
run_with_watchdog("CUDA_VISIBLE_DEVICES=1 python image_recognition_server.py")

A watchdog like this is especially useful in production, allowing recovery from temporary outages caused by segmentation faults.

Service supervision with systemd

On Linux, the recommended, more robust, and standard approach to process management is automatic service restart via systemd.

Here is an example systemd configuration.

# /etc/systemd/system/image-recognition.service
[Unit]
Description=Image Recognition Service
After=network.target

[Service]
User=app_user
WorkingDirectory=/opt/app
ExecStart=/usr/bin/python3 /opt/app/image_recognition_server.py
Restart=on-failure
RestartSec=5s
Environment="CUDA_VISIBLE_DEVICES=0"
Environment="PYTHONPATH=/opt/app"

[Install]
WantedBy=multi-user.target

With systemd, the process is automatically restarted when it terminates abnormally, keeping the service available.

Monitoring GPU and process memory usage

By monitoring GPU and process memory usage, you can detect memory fragmentation and resource exhaustion early.

# Check GPU memory status
nvidia-smi

# Check the process memory map
cat /proc/<PID>/maps

Using memory error detection tools

# AddressSanitizer (detects memory leaks and overflows)
PYTHONMALLOC=malloc ASAN_OPTIONS=detect_leaks=0 python -m pip install --no-binary :all: problematic_package

# Detailed memory leak investigation with Valgrind
valgrind --leak-check=full python script.py

# Capture a stack trace of a running process
pstack process_id

A Practical Solution

After all this discussion, the bottom line: a restart is still the strongest remedy.

But even a restart should follow a staged approach.

STEP 1. Re-run with minor environment variable adjustments

CUDA_VISIBLE_DEVICES=1 python script.py
CUDA_VISIBLE_DEVICES=0 PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128 python script.py

STEP 2. Restart the virtual environment or container

For Docker:

docker restart container_name

For WSL:

wsl --shutdown

STEP 3. Restart the entire host system

Linux:

sudo reboot

Windows:

Choose Restart

Try restarts in stages like this.
For GPU + virtualized environment + segmentation fault situations, in our experience the best-balanced option is STEP 2: restarting the container or virtual environment. As in our case, when multiple processes share a single GPU's memory and one of them hits a segmentation fault, restarting just that process often fails to recover it — presumably because complex memory fragmentation across multiple processes has built up inside the GPU. Restarting the container or virtual environment, by contrast, forcibly detaches every process holding GPU memory at once. It also restores service considerably faster than a full OS reboot, which is another attractive point.

Preventing Recurrence with Regular Maintenance

Of course, the most fundamental way to head off sudden problems is to schedule regular restarts and maintenance in the first place.

Conclusion

In this article, we started from the segmentation fault and
took a quick tour of its causes, analysis methods, remedies when it strikes, and prevention. With segmentation faults, fully identifying the root cause is not always possible, but by combining layered approaches on both the analysis and prevention fronts, we will keep working to maintain high practicality and system availability. Yes, this article too ended with "so it's a restart after all" — but we believe the essence of engineering is choosing simple, robust solutions to complex problems, and we will keep striving to minimize the impact of such issues through the right combination of monitoring, automation, and regular maintenance.

The "Unknown Error" Problem on Production PyTorch + CUDA Servers and How to Address It
https://journal.qualiteg.com/how_to_address_cuda_unknown_error/

Read more