exception

exception

2025년 10월 10일

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

  1// veh_seh_cpp.cpp
  2// 데모 목적: VEH(전역) → SEH(스택 프레임) → C++ 예외(언어 레벨) 흐름을 비교
  3// 빌드: cl /EHsc /Zi veh_seh_cpp.cpp   (또는 동작 비교용 /EHa)
  4
  5#include <windows.h>
  6#include <excpt.h>     // __try/__except, GetExceptionInformation
  7#include <iostream>
  8#include <stdexcept>
  9#include <string>
 10#include <cstdio>
 11
 12static LONG CALLBACK VectoredHandler(PEXCEPTION_POINTERS ep)
 13{
 14    DWORD code = ep->ExceptionRecord->ExceptionCode;
 15    std::cout << "[VEH] First-chance: code=0x" << std::hex << code << std::dec << "\n";
 16
 17    // 로그만 남기고 계속 아래 체인(SEH, Unhandled)으로 넘김
 18    return EXCEPTION_CONTINUE_SEARCH;
 19
 20    // 만약 여기서 복구가 가능하고 실제로 계속 실행하고 싶다면(매우 특수한 경우)
 21    // return EXCEPTION_CONTINUE_EXECUTION;  // <- 위험! 레지스터/상태 복구 필요
 22}
 23
 24static int SehFilter(PEXCEPTION_POINTERS ep)
 25{
 26    DWORD code = ep->ExceptionRecord->ExceptionCode;
 27    std::cout << "[SEH] Handled: code=0x" << std::hex << code << std::dec << "\n";
 28    // 이 프레임에서 처리하고 언와인딩 진행
 29    return EXCEPTION_EXECUTE_HANDLER;
 30
 31    // 디버깅 위해 계속 탐색시키고 싶다면:
 32    // return EXCEPTION_CONTINUE_SEARCH;
 33}
 34
 35static void demo_access_violation_with_SEH()
 36{
 37    std::cout << "== 1) SEH로 접근위반(AV) 처리 ==\n";
 38    __try
 39    {
 40        std::cout << "   (SEH 블록 내부) 의도적 AV 발생\n";
 41        volatile int* p = nullptr;
 42        *p = 1; // AV
 43        std::cout << "   이 줄은 실행되지 않아야 함\n";
 44    }
 45    __except (SehFilter(GetExceptionInformation()))
 46    {
 47        std::cout << "   [SEH] 예외 처리 후 안전하게 계속 진행\n";
 48    }
 49}
 50
 51static void demo_raiseexception_with_SEH()
 52{
 53    std::cout << "== 2) SEH로 소프트웨어 예외 처리(RaiseException) ==\n";
 54    __try
 55    {
 56        std::cout << "   RaiseException(0xE0000001) 호출\n";
 57        RaiseException(0xE0000001, 0, 0, nullptr);
 58        std::cout << "   이 줄은 실행되지 않아야 함\n";
 59    }
 60    __except (SehFilter(GetExceptionInformation()))
 61    {
 62        std::cout << "   [SEH] RaiseException 처리 완료\n";
 63    }
 64}
 65
 66static void demo_cpp_exception()
 67{
 68    std::cout << "== 3) C++ 예외 try/catch ==\n";
 69    try
 70    {
 71        std::cout << "   throw std::runtime_error\n";
 72        throw std::runtime_error("cpp error");
 73    }
 74    catch (const std::exception& e)
 75    {
 76        std::cout << "   [C++] caught std::exception: " << e.what() << "\n";
 77    }
 78    catch (...)
 79    {
 80        std::cout << "   [C++] caught unknown\n";
 81    }
 82}
 83
 84static void demo_seh_as_cpp_exception()
 85{
 86    std::cout << "== 4) (옵션) SEH를 C++처럼 catch(...)로 잡기 (/EHa 필요) ==\n";
 87    try
 88    {
 89        std::cout << "   의도적 AV 발생 (nullptr write)\n";
 90        volatile int* p = nullptr;
 91        *p = 1; // AV
 92
 93        std::cout << "   이 줄은 /EHa 아닐 땐 실행되지 않거나, 프로세스 크래시로 끝날 수 있음\n";
 94    }
 95    catch (const std::exception& e)
 96    {
 97        std::cout << "   [C++ under /EHa] std::exception: " << e.what() << "\n";
 98    }
 99    catch (...)
100    {
101        std::cout << "   [C++ under /EHa] caught non-C++ exception (likely SEH)\n";
102    }
103}
104
105int main()
106{
107    // 0) VEH 등록: First-chance에서 모든 예외를 "가장 먼저" 관찰
108    PVOID veh = AddVectoredExceptionHandler(/*FirstHandler=*/1, VectoredHandler);
109    if (!veh) {
110        std::cerr << "AddVectoredExceptionHandler failed\n";
111        return 1;
112    }
113    std::cout << "[Init] VEH installed\n\n";
114
115    // 1) SEH로 하드웨어 예외demo_access_violation_(AV) 처리
116    demo_access_violation_with_SEH();
117    std::cout << "\n";
118
119    // 2) SEH로 RaiseException 처리
120    demo_raiseexception_with_SEH();
121    std::cout << "\n";
122
123    // 3) C++ 예외
124    demo_cpp_exception();
125    std::cout << "\n";
126
127    // 4) (/EHa 빌드 시) SEH를 C++ catch로도 포착되는지 비교
128    std::cout << "   (다음 동작은 /EHa 일 때만 C++ catch에 걸릴 수 있음)\n";
129    demo_seh_as_cpp_exception();
130    std::cout << "\n";
131
132    // 마무리
133    if (veh) RemoveVectoredExceptionHandler(veh);
134    std::cout << "[Done]\n";
135    return 0;
136}

실행결과

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

 1[Init] VEH installed
 2
 3== 1) SEH로 접근위반(AV) 처리 ==
 4   (SEH 블록 내부) 의도적 AV 발생
 5[VEH] First-chance: code=0xc0000005
 6[SEH] Handled: code=0xc0000005
 7   [SEH] 예외 처리 후 안전하게 계속 진행
 8
 9== 2) SEH로 소프트웨어 예외 처리(RaiseException) ==
10   RaiseException(0xE0000001) 호출
11[VEH] First-chance: code=0xe0000001
12[SEH] Handled: code=0xe0000001
13   [SEH] RaiseException 처리 완료
14
15== 3) C++ 예외 try/catch ==
16   throw std::runtime_error
17[VEH] First-chance: code=0xe06d7363
18   [C++] caught std::exception: cpp error
19
20   (다음 동작은 /EHa 일 때만 C++ catch에 걸릴 수 있음)
21== 4) (옵션) SEH를 C++처럼 catch(...)로 잡기 (/EHa 필요) ==
22   의도적 AV 발생 (nullptr write)
23[VEH] First-chance: code=0xc0000005

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

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

 1// seh_chain_demo.cpp
 2#include <windows.h>
 3#include <excpt.h>
 4#include <iostream>
 5
 6static LONG CALLBACK VehHandler1(PEXCEPTION_POINTERS ep) {
 7    std::cout << "[VEH1] called, code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
 8    // 계속 아래로 넘김
 9    return EXCEPTION_CONTINUE_SEARCH;
10}
11static LONG CALLBACK VehHandler2(PEXCEPTION_POINTERS ep) {
12    std::cout << "[VEH2] called, code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
13    return EXCEPTION_CONTINUE_SEARCH;
14}
15
16int nestedSehFilterA(PEXCEPTION_POINTERS ep) {
17    std::cout << "  [SEH A filter] code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
18    // 여기서 처리하자
19    return EXCEPTION_EXECUTE_HANDLER;
20}
21
22int nestedSehFilterB(PEXCEPTION_POINTERS ep) {
23    std::cout << "    [SEH B filter] code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
24    // A는 처리하지 않고 다음(밖)의 SEH로 넘김
25    return EXCEPTION_CONTINUE_SEARCH;
26}
27
28int main() {
29    // VEH 등록 순서: 먼저 등록한게 먼저 호출됨(우리는 1->2로 등록)
30    PVOID v1 = AddVectoredExceptionHandler(1, VehHandler1);
31    PVOID v2 = AddVectoredExceptionHandler(1, VehHandler2);
32    if (!v1 || !v2) {
33        std::cerr << "VEH 등록 실패\n";
34        return 1;
35    }
36    std::cout << "VEH 등록 완료\n\n";
37
38    std::cout << "---- 예외 유도: 중첩 SEH 데모 ----\n";
39
40    // 바깥 SEH (A)
41    __try {
42        std::cout << "Entered SEH A block\n";
43
44        // 안쪽 SEH (B)
45        __try {
46            std::cout << "  Entered SEH B block\n";
47            // 의도적 접근 위반
48            volatile int* p = nullptr;
49            *p = 123; // AV 발생
50            std::cout << "  (이 지점은 실행되지 않음)\n";
51        }
52        __except (nestedSehFilterB(GetExceptionInformation())) {
53            std::cout << "  [SEH B handler] after filter returned EXCEPTION_EXECUTE_HANDLER\n";
54        }
55
56        std::cout << "Exited SEH B block\n";
57    }
58    __except (nestedSehFilterA(GetExceptionInformation())) {
59        std::cout << "[SEH A handler] after filter\n";
60    }
61
62    std::cout << "Program continues after SEH blocks\n";
63
64    // VEH 제거
65    RemoveVectoredExceptionHandler(v2);
66    RemoveVectoredExceptionHandler(v1);
67    return 0;
68}

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

 1VEH 등록 완료
 2
 3---- 예외 유도: 중첩 SEH 데모 ----
 4Entered SEH A block
 5  Entered SEH B block
 6[VEH2] called, code=0xc0000005
 7[VEH1] called, code=0xc0000005
 8    [SEH B filter] code=0xc0000005
 9  [SEH A filter] code=0xc0000005
10[SEH A handler] after filter
11Program continues after SEH blocks

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

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

comments powered by Disqus