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
리버싱하는 입장에서는 코드 흐름이 비정상적으로 변경돼서 복잡하게 느낄 수 있다. 일부러 코드에서 예외 발생시켜서 예외핸들러로 넘김
핸들러에서 보안로직을 실행하거나 코드를 복원하는 등의 작업을 함