Skip to content

[CRITICAL] VEH fails to catch access violation / null pointer dereference on MinGW-w64 in Mate++ #2

Description

@pmgdev64

[CRITICAL] VEH Fails to Catch Access Violation / Null Pointer Dereference on MinGW-w64 in Mate++

Issue ID: #BUG-001
Priority: CRITICAL
Status: Open
Project: Mate++ Wallpaper Engine v2.0
Platform: Windows 10/11 (x64)
Compiler: MinGW-w64 (GCC 12.0+)
Reported: 2026-08-09


1. Executive Summary

The current exception handling system in Mate++ Wallpaper Engine using Vectored Exception Handler (VEH) does not work correctly on MinGW-w64. When the application encounters a hardware exception (access violation, null pointer dereference, invalid pointer access), the VEH handler either fails to catch it or cannot process it properly, resulting in an unhandled crash without proper cleanup, logging, or graceful exit.

Impact: Application crashes without recovery, no crash report generated, resources not cleaned up, poor user experience.


2. Problem Description

2.1 Current Broken Implementation

Mate++ currently uses a combination of signal() handlers and AddVectoredExceptionHandler() for crash handling:

// CURRENT BROKEN CODE - main.cpp

static void SignalHandler(int signal) {
    // This NEVER fires for access violation on Windows
    const char* sigName = "UNKNOWN";
    switch(signal) {
        case SIGABRT: sigName = "SIGABRT (Abort/assert)"; break;
        case SIGTERM: sigName = "SIGTERM (Termination)"; break;
        case SIGINT:  sigName = "SIGINT (Interrupt)"; break;
    }
    
    LogToFile("[SIGNAL] Caught signal: %s (%d)", sigName, signal);
    InterlockedIncrement(&g_exceptionCount);
    WriteCrashReport(sigName);
    
    if(g_hasJumpBuffer) {
        longjmp(g_jumpBuffer, 1); // UNDEFINED BEHAVIOR
    } else {
        exit(1);
    }
}

static LONG WINAPI VectoredCrashHandler(PEXCEPTION_POINTERS ExceptionInfo) {
    DWORD code = ExceptionInfo->ExceptionRecord->ExceptionCode;
    // ... handling ...
    
    // Attempts to use longjmp() - UNSAFE
    if (g_hasJumpBuffer) {
        longjmp(g_jumpBuffer, 1); // UNDEFINED BEHAVIOR
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

static void InitExceptionHandlers() {
    signal(SIGABRT, SignalHandler);
    signal(SIGTERM, SignalHandler);
    signal(SIGINT, SignalHandler);
    signal(SIGSEGV, SignalHandler); // DOES NOT WORK
    
    AddVectoredExceptionHandler(1, VectoredCrashHandler);
}

2.2 Why This Fails

Issue 1: signal() Does Not Work for Hardware Exceptions on Windows

  • signal(SIGSEGV, ...) DOES NOT catch access violations on Windows
  • This is a documented limitation of the Microsoft C Runtime (CRT)
  • Only works for SIGABRT (from abort() or assert()) and console events (SIGTERM, SIGINT)

Reference: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal

Issue 2: longjmp() from VEH is Undefined Behavior

  • VEH callbacks run in kernel-mode callback context
  • longjmp() is not designed to unwind from kernel context
  • Causes stack corruption, memory leaks, or silent crashes
  • Cannot guarantee proper resource cleanup

Issue 3: No Graceful Shutdown

  • When crash occurs, resources are not cleaned up:
    • D2D render targets not released
    • File handles not closed
    • Threads not terminated properly
    • Crash log incomplete or not written

Issue 4: MinGW-w64 Compiler Limitations

  • MinGW-w64 does NOT support __try/__except (MSVC extension)
  • Only uses SEH internally for C++ exceptions with -fseh flag
  • Cannot use MSVC-style exception handling syntax

3. Technical Analysis

3.1 Exception Types in Windows

Exception Type Description How to Catch
Hardware Exceptions Access violation, div-by-zero, stack overflow VEH only
C++ Exceptions throw std::exception, throw "error" try/catch
CRT Signals SIGABRT, SIGTERM, SIGINT signal()
SEH Exceptions __try/__except (MSVC only) Not available in MinGW

3.2 VEH vs Other Methods

Method MSVC MinGW Catch Access Violation Safe
signal(SIGSEGV) Yes No No N/A
__try/__except Yes No Yes Yes
AddVectoredExceptionHandler Yes Yes Yes No (with longjmp)
SetUnhandledExceptionFilter Yes Yes No Yes

Conclusion: AddVectoredExceptionHandler is the ONLY viable option for MinGW-w64 to catch hardware exceptions, but must be used WITHOUT longjmp().


4. Proposed Solution

4.1 Architecture Overview

Hardware Exception (Access Violation)
    |
    v
VEH Handler
    |
    v
Log + Write Crash Report
    |
    v
Set g_exceptionFlag = 1
    |
    v
Wake Up All Threads
    |
    v
Terminate Threads (Forced)
    |
    v
ExitProcess(1)

4.2 Key Changes Required

  1. Remove all signal() calls - Useless on Windows for hardware exceptions
  2. Remove jmp_buf and longjmp() - Unsafe from VEH
  3. Add g_exceptionFlag - Signal for threads to exit
  4. Add std::set_terminate() - Catch unhandled C++ exceptions
  5. Add try/catch in WinMain and all threads - Handle C++ exceptions properly
  6. Add exception flag checks in all thread loops - Clean exit on crash
  7. Use ExitProcess(1) in VEH - Force exit after signaling

4.3 Fixed Code

// ============================================================
//  EXCEPTION HANDLING - FIXED IMPLEMENTATION
// ============================================================

static volatile LONG g_exceptionFlag = 0;

// Write crash report with exception details
static void WriteCrashReport(const char* reason, PEXCEPTION_POINTERS info = nullptr) {
    FILE* f = fopen("crash_report.txt", "w");
    if (f) {
        SYSTEMTIME st;
        GetLocalTime(&st);
        fprintf(f, "=== Mate++ Wallpaper Engine Crash Report ===\n");
        fprintf(f, "Date: %04d-%02d-%02d %02d:%02d:%02d\n",
                st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
        fprintf(f, "Reason: %s\n", reason);
        
        if (info) {
            fprintf(f, "Exception Code: 0x%08lX\n", 
                    info->ExceptionRecord->ExceptionCode);
            fprintf(f, "Exception Address: 0x%p\n", 
                    info->ExceptionRecord->ExceptionAddress);
            
            if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
                fprintf(f, "Access Violation: %s at address 0x%p\n",
                        info->ExceptionRecord->ExceptionInformation[0] == 0 ? "Read" : "Write",
                        (void*)info->ExceptionRecord->ExceptionInformation[1]);
            }
        }
        
        // Stack trace
        void* stack[64];
        USHORT frames = CaptureStackBackTrace(0, 64, stack, NULL);
        fprintf(f, "Call Stack:\n");
        for (USHORT i = 0; i < frames; i++) {
            fprintf(f, "  #%d: 0x%p\n", i, stack[i]);
        }
        fclose(f);
    }
}

// VEH Handler - ONLY for hardware exceptions
static LONG WINAPI VectoredCrashHandler(PEXCEPTION_POINTERS ExceptionInfo) {
    DWORD code = ExceptionInfo->ExceptionRecord->ExceptionCode;
    
    // Only handle REAL hardware exceptions
    // Skip C++ EH codes (0xE06D7363), debugger breaks, etc.
    switch (code) {
        case EXCEPTION_ACCESS_VIOLATION:
        case EXCEPTION_ILLEGAL_INSTRUCTION:
        case EXCEPTION_INT_DIVIDE_BY_ZERO:
        case EXCEPTION_FLT_DIVIDE_BY_ZERO:
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
        case EXCEPTION_PRIV_INSTRUCTION:
        case EXCEPTION_IN_PAGE_ERROR:
            break;
        case EXCEPTION_STACK_OVERFLOW:
            _resetstkoflw();  // Restore guard page
            break;
        default:
            // Not a hardware exception - let Windows handle it
            return EXCEPTION_CONTINUE_SEARCH;
    }
    
    // Log exception
    LogToFile("[VEH] Caught: 0x%08lX at 0x%p", code, 
              ExceptionInfo->ExceptionRecord->ExceptionAddress);
    InterlockedIncrement(&g_exceptionCount);
    
    // Write crash report
    WriteCrashReport("Hardware Exception", ExceptionInfo);
    
    // Signal all threads to exit
    g_running = false;
    InterlockedExchange(&g_exceptionFlag, 1);
    
    // Wake up waiting threads
    if (g_hKickEvent) SetEvent(g_hKickEvent);
    if (g_hFrameConsumed) SetEvent(g_hFrameConsumed);
    
    // Terminate threads - we're about to exit
    if (g_hDecodeThread) {
        TerminateThread(g_hDecodeThread, 1);
        CloseHandle(g_hDecodeThread);
        g_hDecodeThread = NULL;
    }
    
    if (g_hRenderThread) {
        TerminateThread(g_hRenderThread, 1);
        CloseHandle(g_hRenderThread);
        g_hRenderThread = NULL;
    }
    
    // Allow time for cleanup
    Sleep(500);
    
    // Force exit - cannot recover safely
    ExitProcess(1);
    return EXCEPTION_EXECUTE_HANDLER;
}

// C++ Terminate Handler - for unhandled C++ exceptions
static void TerminateHandler() {
    LogToFile("[TERMINATE] std::terminate called - unhandled C++ exception");
    WriteCrashReport("std::terminate (unhandled C++ exception)");
    ExitProcess(1);
}

// Initialize exception handlers
static void InitExceptionHandlers() {
    // Register VEH - catches hardware exceptions
    PVOID handle = AddVectoredExceptionHandler(1, VectoredCrashHandler);
    if (handle) {
        LogToFile("[EXCEPTION] VEH registered successfully");
    } else {
        LogToFile("[EXCEPTION] VEH registration failed: %d", GetLastError());
    }
    
    // Register C++ terminate handler
    std::set_terminate(TerminateHandler);
    
    // DO NOT use signal() - does not work on Windows
    LogToFile("[EXCEPTION] Exception handlers initialized");
}

4.4 Thread Loop Updates

// Decode Thread (video_decoder.cpp)
static DWORD WINAPI DecodeThreadProc(LPVOID pArg) {
    try {
        // ... initialization ...
        
        while (!isStale() && !g_exceptionFlag) {
            // ... decode loop ...
        }
        
        // ... cleanup ...
        
    } catch (const std::exception& e) {
        LogToFile("[Decode] C++ exception: %s", e.what());
    } catch (...) {
        LogToFile("[Decode] Unknown C++ exception");
    }
    return 0;
}

// Render Thread (main.cpp)
static DWORD WINAPI RenderThread(LPVOID) {
    try {
        // ... initialization ...
        
        while(g_running && !g_exceptionFlag) {
            // ... render loop ...
        }
        
        // ... cleanup ...
        
    } catch (const std::exception& e) {
        LogToFile("[Render] C++ exception: %s", e.what());
    } catch (...) {
        LogToFile("[Render] Unknown C++ exception");
    }
    return 0;
}

// Main Message Loop (main.cpp - WinMain)
MSG msg;
while(g_running && GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    
    // Check exception flag
    if (g_exceptionFlag) {
        LogToFile("[Main] Exception flag detected, exiting");
        break;
    }
}

5. Files to Modify

5.1 main.cpp

Section Change
Globals Add volatile LONG g_exceptionFlag = 0;
Globals Move g_running and g_hKickEvent before VEH
Exception Handlers Replace entire section with new implementation
Signal Handlers REMOVE entirely
Render Thread Add !g_exceptionFlag to while loop
Render Thread Wrap with try/catch
WinMain Add try/catch around entire function
WinMain Add exception flag check in message loop

5.2 common.h

Section Change
Globals Add extern volatile LONG g_exceptionFlag;

5.3 video_decoder.cpp

Section Change
Includes Add extern volatile LONG g_exceptionFlag;
Decode Thread Add !g_exceptionFlag to while loop
Decode Thread Wrap with try/catch

6. Testing

6.1 Test Cases

Test Case Description Expected Result
TC-01 Null pointer dereference VEH catches, logs crash, exits
TC-02 Divide by zero VEH catches, logs crash, exits
TC-03 C++ exception with catch Handled by try/catch
TC-04 C++ exception without catch Terminate handler catches
TC-05 Stack overflow VEH catches, restores guard page, exits
TC-06 Corrupt video decode Handled gracefully

6.2 Test Code

// Test null pointer dereference
void TestAccessViolation() {
    int* p = nullptr;
    *p = 123; // Should trigger VEH
}

// Test divide by zero
void TestDivByZero() {
    int a = 5;
    int b = 0;
    int c = a / b; // Should trigger VEH
}

// Test C++ exception
void TestCppException() {
    throw std::runtime_error("Test exception");
}

7. Environment

7.1 Development Environment

  • OS: Windows 10/11 (x64)
  • Compiler: MinGW-w64 (x86_64-w64-mingw32-g++)
  • GCC Version: 12.0+
  • Build System: Code::Blocks / Makefile
  • Debugger: GDB

7.2 Build Flags

CFLAGS = -fexceptions -mwindows -O2 -g
LDFLAGS = -lwinhttp -lpsapi -lavformat -lavcodec -lavutil -lswscale

7.3 Dependencies

  • FFmpeg libraries (avformat, avcodec, avutil, swscale)
  • Windows SDK (user32, gdi32, d2d1, dwrite, dwmapi)
  • WinHTTP (for network features)

8. Additional Notes

8.1 Important Observations

  1. signal(SIGSEGV, ...) DOES NOT work on Windows - This is documented by Microsoft and cannot be fixed.

  2. __try/__except is MSVC-only - Not available in MinGW-w64. Cannot use this approach.

  3. VEH is the ONLY reliable method - For catching hardware exceptions on Windows with MinGW-w64.

  4. DO NOT use longjmp() from VEH - Causes undefined behavior. Use ExitProcess() instead.

  5. C++ exceptions are separate - Hardware exceptions (access violation) are different from C++ exceptions (throw). Handle them separately.

8.2 References


9. Risk Assessment

Risk Probability Impact Mitigation
VEH not registered Low High Check return value, log error
Thread termination hangs Low Medium Use timeout on WaitForSingleObject
Crash report not written Medium Medium Verify file write, use fallback logging
ExitProcess too aggressive Low Medium Allow time for cleanup (500ms)

10. Timeline

Phase Duration Description
Implementation 2 hours Apply code changes
Testing 2 hours Run test cases
Integration 1 hour Merge with main branch
QA 4 hours Full regression testing
Total 9 hours -

11. Checklist

  • Remove all signal() calls
  • Remove jmp_buf and longjmp()
  • Add g_exceptionFlag
  • Implement new VEH handler
  • Implement std::set_terminate() handler
  • Add try/catch in WinMain
  • Add try/catch in RenderThread
  • Add try/catch in DecodeThread
  • Add exception flag checks in thread loops
  • Add exception flag check in message loop
  • Update common.h with new extern declaration
  • Test with null pointer dereference
  • Test with divide by zero
  • Test with C++ exceptions
  • Test with stack overflow
  • Verify crash report generation
  • Verify graceful exit

12. Labels

bug, critical, exception-handling, mingw64, windows, mate++, crash, stability, seh, veh

Reported by: @PmgTeam
Assigned to: @pmgdev64
Priority: CRITICAL
Severity: Critical - System Crash

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions