Thread x (app) received signal SIGSEGV, Segmentation fault.

Thread x (app) received signal SIGSEGV, Segmentation fault.

In C or C++ applications, segmentation faults (SIGSEGV) are common memory issues, especially in multithreaded programs. When you encounter a SIGSEGV, it means a thread is trying to access invalid memory.

To resolve this, review your code to ensure all memory allocations are done correctly.

For debugging, use gdb with debug symbols. Set breakpoints, focus on the specific thread causing the issue, and when the segmentation fault occurs, use the backtrace command to trace the source of the error.

Let’s take a simple example of a producer-consumer problem in C++, where the goal is to synchronize based on a given buffer length.

A beginner in C++ might easily make this kind of mistake, and sometimes these small errors can be tricky to debug.

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

#define MAX_LENGTH 10
class SyncQueue {
  public:
    SyncQueue();
    SyncQueue(uint32_t maxlength);
    int pop();
    void push(const int& val);
    bool isempty();
    bool isfull();
    void reset();
    ~SyncQueue() = default;
  private:
    uint32_t maxlength_ = MAX_LENGTH;
    int* buffer_;
    int front_ = 0;
    int rear_ = 0;
    std::mutex mtx_;
    std::condition_variable producer_cv_;
    std::condition_variable consumer_cv_;
};

SyncQueue::SyncQueue() {
    buffer_ = new int(maxlength_);
}

SyncQueue::SyncQueue(const uint32_t maxlength) : maxlength_(maxlength){
    buffer_ = new int(maxlength_);
}

bool SyncQueue::isempty() {
    if(rear_ == 0 || rear_ == front_) {
        return true;
    }
    return false;
}

bool SyncQueue::isfull() {
    if(rear_ == maxlength_) {
        return true;
    }
    return false;
}

void SyncQueue::reset(){
    if(front_ == rear_) {
        front_ = rear_ = 0;
    }
}

int SyncQueue::pop() {
    std::unique_lock<std::mutex> lock(mtx_);
    consumer_cv_.wait(lock, [this](){return !isempty(); });
    int data = buffer_[front_++];
    reset();
    producer_cv_.notify_one();
    return data;
}

void SyncQueue::push(const int& val) {
    std::unique_lock<std::mutex> lock(mtx_);
    producer_cv_.wait(lock, [this](){ return !isfull(); });
    buffer_[rear_++] = val;
    reset();
    consumer_cv_.notify_one();
}

void producer(SyncQueue& syncQueue, int range) {
    for(int i=0; i < range; i++) {
        std::cout << "Produer:: data to queue: " << i <<std::endl;
        syncQueue.push(i);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void consumer(SyncQueue& syncQueue, int range) {
    for(int i=0; i < range; i++) {
        std::this_thread::sleep_for(std::chrono::milliseconds(150));
        std::cout << "Consumer:: data from queue: " << syncQueue.pop() << std::endl;
    }
}

int main() {
    SyncQueue syncQueue;
    std::thread prod(producer, std::ref(syncQueue), 10);
    std::thread cons(consumer, std::ref(syncQueue), 10);
    prod.join();
    cons.join();
    return 0;
}

A buggy code to generate the error.

Error you will be getting upon gdb

Thread 2 "a.out" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7a8f640 (LWP 16899)]
0x00007ffff7e6eadb in std::default_deletestd::thread::_State::operator() (__ptr=0x418ed0, this=)
at /usr/src/debug/gcc-11.4.1-3.el9.x86_64/obj-x86_64-redhat-linux/x86_64-redhat-linux/libstdc++-v3/include/bits/unique_ptr.h:79
79 operator()(_Tp* __ptr) const
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.34-100.el9_4.3.x86_64

Before going further, try to find the mistake in the code.

The behavior you're seeing is a common result of undefined behavior in C++. I have used () instead of [] while creating a dynamic array variable. Let's analyze it -

  • Incorrect Buffer Allocation
    buffer_ = new int(maxlength_); This allocated only one integer, but both the producer and consumer think there is space for maxlength_ integers. As a result, when the producer writes more than one value, it is writing beyond the allocated memory, causing a buffer overflow.

  • Why Didn’t the Producer Fail?
    When you write past the bounds of an allocated memory region in C++, undefined behavior occurs. The key aspect of undefined behavior is that it doesn't necessarily cause a crash immediately; it can appear to work, crash later, or even produce corrupted data.

    • Memory layout: After allocating memory with new int(maxlength_);, the runtime allocates only enough memory for one integer. However, the system memory around this allocation might not be used by anything else (yet), so the producer is able to write beyond the allocated memory without immediately triggering a segfault.
    • No immediate error: If the memory following your allocated integer buffer isn't actively being used (e.g., by another variable or system resource), the producer can keep writing into those invalid memory locations without triggering an error. This is often referred to as a silent memory corruption.
  • Why Did the Consumer Fail?
    The consumer reads from the buffer by accessing the same out-of-bounds memory locations that the producer wrote into. However, accessing this invalid memory for reading is more likely to cause a segmentation fault because:

    • Memory protection mechanisms: On some systems, reading from invalid memory addresses can trigger a segfault, especially if those addresses belong to protected areas (such as parts of the stack or heap that the process doesn’t have permission to access).
    • Delayed detection: Depending on the operating system's memory management, a segfault might not be triggered until a read operation happens. The producer writes into undefined memory, but the OS may not actively monitor that space. When the consumer tries to access the invalid memory, the system's memory protection mechanisms may finally detect that the access is invalid.

In multithreaded programs (or any program), memory bugs like this can lead to unpredictable and subtle issues. Tools like AddressSanitizer (ASan) or Valgrind can help detect buffer overflows and memory access violations, which will provide more immediate feedback about these kinds of issues.