异常处理得与失

40
异异异异异异异 异异异 [email protected]

Upload: abra-daniel

Post on 30-Dec-2015

69 views

Category:

Documents


5 download

DESCRIPTION

异常处理得与失. 张银奎 [email protected]. 摘要. 异常机制为处理软件执行过程中的“意外”情况提供了一种重要手段,但它是否可以取代传统的错误处理方法呢? C++ 语言规范定义了异常处理,但没有规定如何来实现。操作系统是如何分发异常的,异常处理的开销有多大呢?本演讲以 Windows 操作系统和 Visual C++ 编译器为例层层剥茧,由浅入深的回答了以上问题,比较深入的探讨了如何在软件开发中正确的使用异常处理机制,并以实验数据和演示分析了滥用异常处理会导致的严重问题。. 在我公司的咨询业务中 C++ 异常处理一直是最令软件开发公司迷惑的一个问题。. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 异常处理得与失

异常处理得与失

张银奎[email protected]

Page 2: 异常处理得与失

摘要• 异常机制为处理软件执行过程中的“意外”情况

提供了一种重要手段,但它是否可以取代传统的错误处理方法呢? C++ 语言规范定义了异常处理,但没有规定如何来实现。操作系统是如何分发异常的,异常处理的开销有多大呢?本演讲以 Windows 操作系统和 Visual C++ 编译器为例层层剥茧,由浅入深的回答了以上问题,比较深入的探讨了如何在软件开发中正确的使用异常处理机制,并以实验数据和演示分析了滥用异常处理会导致的严重问题。

Page 3: 异常处理得与失

在我公司的咨询业务中 C++ 异常处理一直是最令软件开发公司迷惑的一个问题。

——John Robbins

Page 4: 异常处理得与失

The popular belief is that exceptions provide a straightforward mechanism for adding reliable error handling to our programs. On the contrary, I see exceptions as a mechanism that may cause more ills than it cures.

EXCEPTION HANDLING: A FALSE SENSE OF SECURITY by Tom Cargill

Page 5: 异常处理得与失

目录• 异常过程• 未处理的异常• 异常处理的开销• 观点和建议

Page 6: 异常处理得与失

异常来源• CPU 检测到的执行错误

– 除 0 , GP ,无效指令• 程序产生

– Int 3, throw E

• Machine Check Exceptions– 总线错误, ECC 错误, Cache 错误

Page 7: 异常处理得与失

异常来源

Page 8: 异常处理得与失

分类• C++ 异常处理( EH )

– C++ 标准,关键字: try, catch, throw

• 结构化异常处理( SEH )– Structured Exception Handling– Windows 操作系统的异常机制– 具体实现依赖于编译器: __try, __except, __finally

• 向量式异常处理( VEH )– Vectored Exception Handling – Windows XP 引入,对 SEH 的补充

Page 9: 异常处理得与失

异常处理五阶段1. 发生2. 表征3. 捕捉分发4. 处理

EXCEPTION_CONTINUE_SEARCH (0) EXCEPTION_EXECUTE_HANDLER (1) EXCEPTION_CONTINUE_EXECUTION (-1)

5. 善后 恢复( resuming exception ) 终止( terminating exception )

Page 10: 异常处理得与失

异常处理五阶段 - SEH• int filter(void)• {• /* Stage 4 */• }• int main(void)• {• __try• {• if (some_error) /* Stage 1 */• RaiseException(...); /* Stage 2 */• /* Stage 5 of resuming exception */ • }• __except(filter()) /* Stage 3 */• {• /* Stage 5 of terminating exception */• }• return 0;• }

Page 11: 异常处理得与失

异常处理五阶段 - EH• int main(void)• {• try• {• if (some_error) /* Stage 1 */• thow E(); /* Stage 2 */• }• carch(E&) /* Stage 3 */• {• /* Stage 4 */• }• /* Stage 5 */ • return 0;• }

Page 12: 异常处理得与失

主动抛出异常

E e = E(); _CxxThrowException(&e,E_E XCPT_INFO_ADDR);

throw E();

Page 13: 异常处理得与失

CxxThrowException• #define CXX_FRAME_MAGIC 0x19930520• #define CXX_EXCEPTION 0xe06d7363

• VOID• NTAPI• _CxxThrowException (• IN PVOID Object,• IN PVOID CxxExceptionType• )• {• ULONG_PTR ExceptionInformation[3];• ExceptionInformation[0] = CXX_FRAME_MAGIC;• ExceptionInformation[1] = (ULONG_PTR) Object;• ExceptionInformation[2] = (ULONG_PTR) CxxExceptionType;• RaiseException(• CXX_EXCEPTION,• EXCEPTION_NONCONTINUABLE,• 3,• ExceptionInformation• );• }

Page 14: 异常处理得与失

RaiseException• VOID• NTAPI• RaiseException (• IN ULONG ExceptionCode,• IN ULONG ExceptionFlags,• IN ULONG NumberParameters,• IN CONST ULONG_PTR *ExceptionInformation• )• {• EXCEPTION_RECORD ExceptionRecord = {• ExceptionCode,• ExceptionFlags & EXCEPTION_NONCONTINUABLE,• NULL,• RaiseException, // 指向本函数的指针• NumberParameters > EXCEPTION_MAXIMUM_PARAMETERS ?• EXCEPTION_MAXIMUM_PARAMETERS : NumberParameters• };• RtlCopyMemory(• ExceptionRecord.ExceptionInformation,• ExceptionInformation,• ExceptionRecord.NumberParameters * sizeof(ULONG_PTR)• );• RtlRaiseException(&ExceptionRecord);• }

Page 15: 异常处理得与失

ExceptionFlags

• 0– 可恢复继续执行的异常

• EXCEPTION_NONCONTINUABLE (1)– 不可恢复继续执行的异常,如果企图恢复继续

执行,则会导致 EXCEPTION_NONCONTINUABLE_EXCEPTION 异常

Page 16: 异常处理得与失

实例• Args to Child • 0012fe28 0012fb44 00000001 ntdll!NtRaiseException+0xa

• 0012fe28 004251b8 e06d7363 ntdll!RtlRaiseException+0x93

• e06d7363 00000001 00000003 kernel32!RaiseException+0x51

• 0012ff18 00426680 0012ff80 vcexp!_CxxThrowException+0x39

Page 17: 异常处理得与失

直接调用 RaiseException• ChildEBP RetAddr Args to Child • 0012fbac 77f5bf94 77f96673 0012fea8 0012fbc4 SharedUserData!S

ystemCallStub+0x2 (FPO: [0,0,0])• 0012fbb0 77f96673 0012fea8 0012fbc4 00000001 ntdll!NtRaiseExc

eption+0xc (FPO: [3,0,0])• 0012fe98 77e738b2 0012fea8 0012ff10 e0000001 ntdll!RtlRaiseExc

eption+0x93• 0012fef8 004010d5 e0000001 00000000 00000000 kernel32!Raise

Exception+0x51 (FPO: [Non-Fpo])• 0012ff80 004016d9 00000001 003715a0 00371618 se!main+0xc5

(CONV: cdecl) [C:\dig\dbg\exception\advexp\se\se.cpp @ 34]• 0012ffc0 77e8141a 00000000 00000000 7ffdf000 se!mainCRTStart

up+0xe9 (CONV: cdecl) [crt0.c @ 206]• 0012fff0 00000000 004015f0 00000000 78746341 kernel32!BasePr

ocessStart+0x23 (FPO: [Non-Fpo])

Page 18: 异常处理得与失

进入内核

ZwRaiseException

KiDispatchException

RaiseException

RtlRaiseException

NtRaiseException

Page 19: 异常处理得与失

ZwRaiseException

• NTSYSAPI• NTSTATUS• NTAPI• ZwRaiseException(• IN PEXCEPTION_RECORD ExceptionRecord,• IN PCONTEXT Context,• IN BOOLEAN SearchFrames• );

Page 20: 异常处理得与失

KiDispatchException

Windows NT/2000 Native API Reference by Gary Nebbett

if (SearchFrames){if (PsGetCurrentProcess()->DebugPort == 0

|| KdIsThisAKdTrap(Tf, &Context)) {if (KiDebugRoutine &&

KiDebugRoutine(Tf, Reserved, Er, &Context,PreviousMode, FirstChance) != 0) break;

}if (DbgkForwardException(Tf, DebugEvent,FirstChance) != 0) return;if (valid_user_mode_stack_with_enough_space) {

Tf->Eip = KeUserExceptionDispatcher;return;

}}if (DbgkForwardException(Tf, DebugEvent,LastChance) != 0) return;if (DbgkForwardException(Tf, ExceptionEvent,LastChance) != 0) return;ZwTerminateThread(NtCurrentThread(), Er->ExceptionCode);

Page 21: 异常处理得与失

返回用户态• ntdll!RtlpUnlinkHandler+0xd• vcexp!_UnwindNestedFrames+0x2c• vcexp!__FrameUnwindToState+0x16d• vcexp!__InternalCxxFrameHandler+0x319• vcexp!__InternalCxxFrameHandler+0xe3• vcexp!__CxxFrameHandler+0x2c• ntdll!ExecuteHandler2+0x26• ntdll!ExecuteHandler+0x24• ntdll!KiUserExceptionDispatcher+0xe• kernel32!RaiseException+0x51• vcexp!_CxxThrowException+0x39• vcexp!vc_throw(void)+0x4c [C:\DIG\DBG\EXCEPTION\advexp\vcexp.cpp

@ 27]• vcexp!main(void)+0x1d [C:\DIG\DBG\EXCEPTION\advexp\vcexp.cpp @ 44]• vcexp!mainCRTStartup(void)+0xe9 [crt0.c @ 206]• kernel32!BaseProcessStart+0x23

Page 22: 异常处理得与失

ExecuteHandler2• ntdll!ExecuteHandler2:• 77fb1708 55 push ebp• 77fb1709 8bec mov ebp,esp• 77fb170b ff750c push dword ptr [ebp+0xc]• 77fb170e 52 push edx• 77fb170f 64ff3500000000 push dword ptr fs:[00000000]• 77fb1716 64892500000000 mov fs:[00000000],esp• 77fb171d ff7514 push dword ptr [ebp+0x14]• 77fb1720 ff7510 push dword ptr [ebp+0x10]• 77fb1723 ff750c push dword ptr [ebp+0xc]• 77fb1726 ff7508 push dword ptr [ebp+0x8]• 77fb1729 8b4d18 mov ecx,[ebp+0x18]• 77fb172c ffd1 call ecx {se!_except_handler3 (004014d4)}• 77fb172e 648b2500000000 mov esp,fs:[00000000]• 77fb1735 648f0500000000 pop fs:[00000000]• 77fb173c 8be5 mov esp,ebp• 77fb173e 5d pop ebp• 77fb173f c21400 ret 0x14• 77fb1742 8b4c2404 mov ecx,[esp+0x4]• 77fb1746 f7410406000000 test dword ptr [ecx+0x4],0x6• 77fb174d b801000000 mov eax,0x1• 77fb1752 7512 jnz ntdll!ExecuteHandler2+0x5e (77fb1766)• 77fb1754 8b4c2408 mov ecx,[esp+0x8]• 77fb1758 8b542410 mov edx,[esp+0x10]• 77fb1783 8b4108 mov eax,[ecx+0x8]• 77fb1786 8902 mov [edx],eax• 77fb1788 b803000000 mov eax,0x3• 77fb178d c21000 ret 0x10

Page 23: 异常处理得与失

空间开销• struct tryblock• {• DWORD start_id;• DWORD end_id;• DWORD u1;• DWORD catchblock_count;• catchblock *pcatchblock_table;• };

• enum VAL_OR_REF {BY_VALUE = 0, BY_REFERENCE = 8};•• struct catchblock• {• DWORD val_or_ref; //Whether the exception is passed• //by val or by reference• type_info *ti; • int offset; //offset from stack frame pointer• //(EBP) where exception should be• //copied.• DWORD catchblock_addr;• };

• struct funcinfo• {• DWORD sig;• DWORD unwind_count; //number of entries in unwindtable• unwind *punwindtable;• DWORD tryblock_count; //number of entries in tryblocktable• tryblock *ptryblock_table;• };

Page 24: 异常处理得与失

时间开销Happy Path Unhappy Path

Traditional Method

20 ms 21 ms

Exceptional method

60 ms 3294 ms

Page 25: 异常处理得与失

未处理的异常 – Windows XP

Page 26: 异常处理得与失

未处理的异常 – Win2K Server

Page 27: 异常处理得与失

未处理的异常 - BSOD

• Blue Screen of Death

• 内核模式下操作系统强制 STOP

• KebugCheckEx()

Page 28: 异常处理得与失

反对意见 - Tom Cargill

• EXCEPTION HANDLING: A FALSE SENSE OF SECURITY– First appeared in C++ Report, Volume 6, Number 9,

November-December 1994 – While entirely in favor of robust error handling, I have

serious doubts that exceptions will engender software that is any more robust than that achieved by other means. I am concerned that exceptions will lull programmers into a false sense of security, believing that their code is handling errors when in reality the exceptions are actually compounding errors and hindering the software.

Page 29: 异常处理得与失

反对意见 – 违反 OO 原则• Issues with Exception Handling in Object-Oriented S

ystems by Robert Miller and Anand Tripathi in Computer Science Department of University of Minnesota– Abstraction: generalization of operations and composition constr

uction may involve changing of abstraction levels and dealing with partial states.

– Encapsulation: the exception context may leak information that allows implementation details or private data of the signaler to be revealed or accessed.

– Modularity: design evolution (function and implementation) maybe inhibited by exception conformance.

– Inheritance: the inheritance anomaly can occur when a language does not support exception handling augmentation in a modular way.

Page 30: 异常处理得与失

反对意见 - Joel Spolsky

• I consider exceptions to be no better than "goto's", considered harmful since the 1960s, in that they create an abrupt jump from one point of code to another. In fact they are significantly worse than goto's: – They are invisible in the source code. – They create too many possible exit points for

a function.

Page 31: 异常处理得与失

John Robbins 的意见• 缺点 1 :不具有纯语言特征• 缺点 2 :容易被滥用• “ 许多人抛出的论点是,使用 C++ 异常的

最好理由是程序员从来不检查函数的返回值。这样的论点不仅是完全不对的,而且是糟糕的编程方法的借口。”

• “ 本机应用程序中导致性能下降的最大根源是不必要的异常。”

Page 32: 异常处理得与失

John Robbins 的意见• 绝对、一定、永远不要使用 catch(…)

• “catch(…) 结构为我的银行帐号做过很多贡献了”– 没有办法知道为何到此!– 不仅捕获 C++ 异常,还捕获 SHE 异常,难以

分析。

Page 33: 异常处理得与失

关键问题• 不可预知的出口

–影响性能, branch prediction 失败–破坏结构化和封装原则

• 性能上的副作用• 难以在软件工程中实施

– [ 何时何处 ] [ 可以 / 不可 ] [ 抛出 / 捕捉 ]

Page 34: 异常处理得与失

Joel 的建议• Never throw an exception of my own.

• Always catch any possible exception that might be thrown by a library I'm using on the same line as it is thrown and deal with it immediately.

Page 35: 异常处理得与失

监视异常

Page 36: 异常处理得与失

VEH

• PVOID AddVectoredExceptionHandler( ULONG FirstHandler, PVECTORED_EXCEPTION_HANDLER VectoredHandler );

• LONG WINAPI VectoredHandler( PEXCEPTION_POINTERS ExceptionInfo );

Page 37: 异常处理得与失

VEH 的优点• 不绑定任何函数和栈• 代码显式加入,不像 EH 有很多隐式处理• 进程内的全局性• 早于其它机制得到处理权

Page 38: 异常处理得与失

解论• 模块内和内部模块之间不要用抛出异常代替错误返回;

• 设置全局性的异常预处理和为处理异常捕捉措施;

• 定义完善的错误处理政策和机制– 错误日志– 错误代码格式

Page 39: 异常处理得与失

参考文献 /资源• A Crash Course on the Depths of Win32™

Structured Exception Handling by Matt Pietrek

• Handling Exceptions in C and C++ by Robert Schmidt

• http://www.advdbg.com

Page 40: 异常处理得与失

Q & [email protected]