1 / 40

异常处理得与失

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

abra-daniel
Télécharger la présentation

异常处理得与失

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 异常处理得与失 张银奎 yinkui.zhang@gmail.com

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

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

  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

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

  6. 异常来源 • CPU检测到的执行错误 • 除0,GP,无效指令 • 程序产生 • Int 3, throw E • Machine Check Exceptions • 总线错误,ECC错误,Cache错误

  7. 异常来源

  8. 分类 • C++异常处理(EH) • C++标准,关键字:try, catch, throw • 结构化异常处理(SEH) • Structured Exception Handling • Windows操作系统的异常机制 • 具体实现依赖于编译器:__try, __except, __finally • 向量式异常处理(VEH) • Vectored Exception Handling • Windows XP引入,对SEH的补充

  9. 异常处理五阶段 • 发生 • 表征 • 捕捉分发 • 处理 • EXCEPTION_CONTINUE_SEARCH (0) • EXCEPTION_EXECUTE_HANDLER (1) • EXCEPTION_CONTINUE_EXECUTION (-1) • 善后 • 恢复(resuming exception) • 终止(terminating exception)

  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; • }

  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; • }

  12. 主动抛出异常 E e = E(); _CxxThrowException(&e,E_E XCPT_INFO_ADDR); throw E();

  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 • ); • }

  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); • }

  15. ExceptionFlags • 0 • 可恢复继续执行的异常 • EXCEPTION_NONCONTINUABLE (1) • 不可恢复继续执行的异常,如果企图恢复继续执行,则会导致EXCEPTION_NONCONTINUABLE_EXCEPTION异常

  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

  17. 直接调用RaiseException • ChildEBP RetAddr Args to Child • 0012fbac 77f5bf94 77f96673 0012fea8 0012fbc4 SharedUserData!SystemCallStub+0x2 (FPO: [0,0,0]) • 0012fbb0 77f96673 0012fea8 0012fbc4 00000001 ntdll!NtRaiseException+0xc (FPO: [3,0,0]) • 0012fe98 77e738b2 0012fea8 0012ff10 e0000001 ntdll!RtlRaiseException+0x93 • 0012fef8 004010d5 e0000001 00000000 00000000 kernel32!RaiseException+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!mainCRTStartup+0xe9 (CONV: cdecl) [crt0.c @ 206] • 0012fff0 00000000 004015f0 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])

  18. 进入内核 RaiseException RtlRaiseException NtRaiseException ZwRaiseException KiDispatchException

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

  20. KiDispatchException 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); Windows NT/2000 Native API Reference by Gary Nebbett

  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

  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

  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; • };

  24. 时间开销

  25. 未处理的异常 – Windows XP

  26. 未处理的异常 – Win2K Server

  27. 未处理的异常 - BSOD • Blue Screen of Death • 内核模式下操作系统强制STOP • KebugCheckEx()

  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.

  29. 反对意见 – 违反OO原则 • Issues with Exception Handling in Object-Oriented Systems by Robert Miller and Anand Tripathi in Computer Science Department of University of Minnesota • Abstraction: generalization of operations and composition construction 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.

  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.

  31. John Robbins的意见 • 缺点1:不具有纯语言特征 • 缺点2:容易被滥用 • “许多人抛出的论点是,使用C++异常的最好理由是程序员从来不检查函数的返回值。这样的论点不仅是完全不对的,而且是糟糕的编程方法的借口。” • “本机应用程序中导致性能下降的最大根源是不必要的异常。”

  32. John Robbins的意见 • 绝对、一定、永远不要使用catch(…) • “catch(…)结构为我的银行帐号做过很多贡献了” • 没有办法知道为何到此! • 不仅捕获C++异常,还捕获SHE异常,难以分析。

  33. 关键问题 • 不可预知的出口 • 影响性能, branch prediction失败 • 破坏结构化和封装原则 • 性能上的副作用 • 难以在软件工程中实施 • [何时何处] [可以/不可] [抛出/捕捉]

  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.

  35. 监视异常

  36. VEH • PVOIDAddVectoredExceptionHandler(ULONGFirstHandler, PVECTORED_EXCEPTION_HANDLERVectoredHandler ); • LONG WINAPIVectoredHandler(PEXCEPTION_POINTERSExceptionInfo );

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

  38. 解论 • 模块内和内部模块之间不要用抛出异常代替错误返回; • 设置全局性的异常预处理和为处理异常捕捉措施; • 定义完善的错误处理政策和机制 • 错误日志 • 错误代码格式

  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

  40. Q & Ayinkui.zhang@gmail.com

More Related