exception

CPU 예외 발생시 VEH 전역체인에 등록된 순서대로 호출. SEH는 __try/__except 로 처리하며 스택프레임별로 체인이 만들어짐.

// veh_seh_cpp.cpp
// 데모 목적: VEH(전역) → SEH(스택 프레임) → C++ 예외(언어 레벨) 흐름을 비교
// 빌드: cl /EHsc /Zi veh_seh_cpp.cpp   (또는 동작 비교용 /EHa)

#include <windows.h>
#include <excpt.h>     // __try/__except, GetExceptionInformation
#include <iostream>
#include <stdexcept>
#include <string>
#include <cstdio>

static LONG CALLBACK VectoredHandler(PEXCEPTION_POINTERS ep)
{
    DWORD code = ep->ExceptionRecord->ExceptionCode;
    std::cout << "[VEH] First-chance: code=0x" << std::hex << code << std::dec << "\n";

    // 로그만 남기고 계속 아래 체인(SEH, Unhandled)으로 넘김
    return EXCEPTION_CONTINUE_SEARCH;

    // 만약 여기서 복구가 가능하고 실제로 계속 실행하고 싶다면(매우 특수한 경우)
    // return EXCEPTION_CONTINUE_EXECUTION;  // <- 위험! 레지스터/상태 복구 필요
}

static int SehFilter(PEXCEPTION_POINTERS ep)
{
    DWORD code = ep->ExceptionRecord->ExceptionCode;
    std::cout << "[SEH] Handled: code=0x" << std::hex << code << std::dec << "\n";
    // 이 프레임에서 처리하고 언와인딩 진행
    return EXCEPTION_EXECUTE_HANDLER;

    // 디버깅 위해 계속 탐색시키고 싶다면:
    // return EXCEPTION_CONTINUE_SEARCH;
}

static void demo_access_violation_with_SEH()
{
    std::cout << "== 1) SEH로 접근위반(AV) 처리 ==\n";
    __try
    {
        std::cout << "   (SEH 블록 내부) 의도적 AV 발생\n";
        volatile int* p = nullptr;
        *p = 1; // AV
        std::cout << "   이 줄은 실행되지 않아야 함\n";
    }
    __except (SehFilter(GetExceptionInformation()))
    {
        std::cout << "   [SEH] 예외 처리 후 안전하게 계속 진행\n";
    }
}

static void demo_raiseexception_with_SEH()
{
    std::cout << "== 2) SEH로 소프트웨어 예외 처리(RaiseException) ==\n";
    __try
    {
        std::cout << "   RaiseException(0xE0000001) 호출\n";
        RaiseException(0xE0000001, 0, 0, nullptr);
        std::cout << "   이 줄은 실행되지 않아야 함\n";
    }
    __except (SehFilter(GetExceptionInformation()))
    {
        std::cout << "   [SEH] RaiseException 처리 완료\n";
    }
}

static void demo_cpp_exception()
{
    std::cout << "== 3) C++ 예외 try/catch ==\n";
    try
    {
        std::cout << "   throw std::runtime_error\n";
        throw std::runtime_error("cpp error");
    }
    catch (const std::exception& e)
    {
        std::cout << "   [C++] caught std::exception: " << e.what() << "\n";
    }
    catch (...)
    {
        std::cout << "   [C++] caught unknown\n";
    }
}

static void demo_seh_as_cpp_exception()
{
    std::cout << "== 4) (옵션) SEH를 C++처럼 catch(...)로 잡기 (/EHa 필요) ==\n";
    try
    {
        std::cout << "   의도적 AV 발생 (nullptr write)\n";
        volatile int* p = nullptr;
        *p = 1; // AV

        std::cout << "   이 줄은 /EHa 아닐 땐 실행되지 않거나, 프로세스 크래시로 끝날 수 있음\n";
    }
    catch (const std::exception& e)
    {
        std::cout << "   [C++ under /EHa] std::exception: " << e.what() << "\n";
    }
    catch (...)
    {
        std::cout << "   [C++ under /EHa] caught non-C++ exception (likely SEH)\n";
    }
}

int main()
{
    // 0) VEH 등록: First-chance에서 모든 예외를 "가장 먼저" 관찰
    PVOID veh = AddVectoredExceptionHandler(/*FirstHandler=*/1, VectoredHandler);
    if (!veh) {
        std::cerr << "AddVectoredExceptionHandler failed\n";
        return 1;
    }
    std::cout << "[Init] VEH installed\n\n";

    // 1) SEH로 하드웨어 예외demo_access_violation_(AV) 처리
    demo_access_violation_with_SEH();
    std::cout << "\n";

    // 2) SEH로 RaiseException 처리
    demo_raiseexception_with_SEH();
    std::cout << "\n";

    // 3) C++ 예외
    demo_cpp_exception();
    std::cout << "\n";

    // 4) (/EHa 빌드 시) SEH를 C++ catch로도 포착되는지 비교
    std::cout << "   (다음 동작은 /EHa 일 때만 C++ catch에 걸릴 수 있음)\n";
    demo_seh_as_cpp_exception();
    std::cout << "\n";

    // 마무리
    if (veh) RemoveVectoredExceptionHandler(veh);
    std::cout << "[Done]\n";
    return 0;
}

실행결과

모든 예외는 VEH에서 먼저 잡히고, 이후에 SEH나 C++예외에서 잡힌다.
SEH는 필터가 받아서 처리하고 일반 예외는 catch에서 처리한다.

[Init] VEH installed

== 1) SEH로 접근위반(AV) 처리 ==
   (SEH 블록 내부) 의도적 AV 발생
[VEH] First-chance: code=0xc0000005
[SEH] Handled: code=0xc0000005
   [SEH] 예외 처리 후 안전하게 계속 진행

== 2) SEH로 소프트웨어 예외 처리(RaiseException) ==
   RaiseException(0xE0000001) 호출
[VEH] First-chance: code=0xe0000001
[SEH] Handled: code=0xe0000001
   [SEH] RaiseException 처리 완료

== 3) C++ 예외 try/catch ==
   throw std::runtime_error
[VEH] First-chance: code=0xe06d7363
   [C++] caught std::exception: cpp error

   (다음 동작은 /EHa 일 때만 C++ catch에 걸릴 수 있음)
== 4) (옵션) SEH를 C++처럼 catch(...)로 잡기 (/EHa 필요) ==
   의도적 AV 발생 (nullptr write)
[VEH] First-chance: code=0xc0000005

SEH VEH는 체인 형태라서 SEH는 스택처럼 최신등록한것부터 실행되고, VEH는 등록한순서대로 실행된다. SEH는 커스텀이 가능함.
최신 등록이라는거는 그냥 내부 exception이 실행된다는 의미임.

VEH든 SEH든 EXCEPTION_CONTINUE_SEARCH 이걸 리턴하면 다음 핸들러에서 처리하겠다는 의미임. EXCEPTION_EXECUTE_HANDLER 이걸 리턴하면 처리가 완료됐다는 의미라서 다음 예외체인에 전파되지 않는다.

// seh_chain_demo.cpp
#include <windows.h>
#include <excpt.h>
#include <iostream>

static LONG CALLBACK VehHandler1(PEXCEPTION_POINTERS ep) {
    std::cout << "[VEH1] called, code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
    // 계속 아래로 넘김
    return EXCEPTION_CONTINUE_SEARCH;
}
static LONG CALLBACK VehHandler2(PEXCEPTION_POINTERS ep) {
    std::cout << "[VEH2] called, code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
    return EXCEPTION_CONTINUE_SEARCH;
}

int nestedSehFilterA(PEXCEPTION_POINTERS ep) {
    std::cout << "  [SEH A filter] code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
    // 여기서 처리하자
    return EXCEPTION_EXECUTE_HANDLER;
}

int nestedSehFilterB(PEXCEPTION_POINTERS ep) {
    std::cout << "    [SEH B filter] code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
    // A는 처리하지 않고 다음(밖)의 SEH로 넘김
    return EXCEPTION_CONTINUE_SEARCH;
}

int main() {
    // VEH 등록 순서: 먼저 등록한게 먼저 호출됨(우리는 1->2로 등록)
    PVOID v1 = AddVectoredExceptionHandler(1, VehHandler1);
    PVOID v2 = AddVectoredExceptionHandler(1, VehHandler2);
    if (!v1 || !v2) {
        std::cerr << "VEH 등록 실패\n";
        return 1;
    }
    std::cout << "VEH 등록 완료\n\n";

    std::cout << "---- 예외 유도: 중첩 SEH 데모 ----\n";

    // 바깥 SEH (A)
    __try {
        std::cout << "Entered SEH A block\n";

        // 안쪽 SEH (B)
        __try {
            std::cout << "  Entered SEH B block\n";
            // 의도적 접근 위반
            volatile int* p = nullptr;
            *p = 123; // AV 발생
            std::cout << "  (이 지점은 실행되지 않음)\n";
        }
        __except (nestedSehFilterB(GetExceptionInformation())) {
            std::cout << "  [SEH B handler] after filter returned EXCEPTION_EXECUTE_HANDLER\n";
        }

        std::cout << "Exited SEH B block\n";
    }
    __except (nestedSehFilterA(GetExceptionInformation())) {
        std::cout << "[SEH A handler] after filter\n";
    }

    std::cout << "Program continues after SEH blocks\n";

    // VEH 제거
    RemoveVectoredExceptionHandler(v2);
    RemoveVectoredExceptionHandler(v1);
    return 0;
}

실행해보면 아래와같다. 모든 핸들러가 다 실행됨.

VEH 등록 완료

---- 예외 유도: 중첩 SEH 데모 ----
Entered SEH A block
  Entered SEH B block
[VEH2] called, code=0xc0000005
[VEH1] called, code=0xc0000005
    [SEH B filter] code=0xc0000005
  [SEH A filter] code=0xc0000005
[SEH A handler] after filter
Program continues after SEH blocks

리버싱하는 입장에서는 코드 흐름이 비정상적으로 변경돼서 복잡하게 느낄 수 있다.
일부러 코드에서 예외 발생시켜서 예외핸들러로 넘김

핸들러에서 보안로직을 실행하거나 코드를 복원하는 등의 작업을 함

Comments

ESC
Type to search...