Delphi 的 Exception 机制浅探
savetime2k@yahoo.com 2004.4.24 http://savetime.delphibbs.com
目 录
=============================================================================== ⊙
===============================================================================
本文排版格式为:
正文由窗口自动换行;所有代码以 80 字符为边界;中英文字符以空格符分隔。
(作者保留对本文的所有权利,未经作者同意请勿在在任何公共媒体转载。)
=============================================================================== ⊙ 问题
=============================================================================== EXCEPTION_DISPOSITION __cdecl _except_handler ( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame,
struct _CONTEXT *ContextRecord, void * DispatcherContext ); /*
* Exception disposition return values. */
typedef enum _EXCEPTION_DISPOSITION { ExceptionContinueExecution, ExceptionContinueSearch, ExceptionNestedException, ExceptionCollidedUnwind } EXCEPTION_DISPOSITION;
Exception 是线程相关的
{$IFDEF PIC}
{$IFDEF PC_MAPPED_EXCEPTIONS}
RaiseExceptionProc := @RaiseException; 被执行了两次 _StartExe,initialization中都有
正 文
=============================================================================== ⊙ 数据结构
=============================================================================== cContinuable = 0; cNonContinuable = 1; cUnwinding = 2; cUnwindingForExit = 4;
cUnwindInProgress = cUnwinding or cUnwindingForExit; cDelphiException = $0EEDFADE; cDelphiReRaise = $0EEDFADF; cDelphiExcept = $0EEDFAE0;
cDelphiFinally = $0EEDFAE1; cDelphiTerminate = $0EEDFAE2; cDelphiUnhandled = $0EEDFAE3; cNonDelphiException = $0EEDFAE4; cDelphiExitFinally = $0EEDFAE5;
cCppException = $0EEFFACE; { used by BCB } EXCEPTION_CONTINUE_SEARCH = 0; EXCEPTION_EXECUTE_HANDLER = 1; EXCEPTION_CONTINUE_EXECUTION = -1;
JmpInstruction = packed record opCode: Byte; distance: Longint; end;
TExcDescEntry = record vTable: Pointer; handler: Pointer; end;
PExcDesc = ^TExcDesc; TExcDesc = packed record jmp: JmpInstruction; case Integer of
0: (instructions: array [0..0] of Byte);
1{...}: (cnt: Integer; excTab: array [0..0{cnt-1}] of TExcDescEntry); end;
PExcFrame = ^TExcFrame; TExcFrame = record next: PExcFrame; desc: PExcDesc; hEBP: Pointer; case Integer of 0: ( );
1: ( ConstructedObject: Pointer ); 2: ( SelfOfMethod: Pointer ); end;
PExceptionRecord = ^TExceptionRecord; TExceptionRecord = record
ExceptionCode : LongWord; ExceptionFlags : LongWord;
OuterException : PExceptionRecord; ExceptionAddress : Pointer; NumberParameters : Longint; case {IsOsException:} Boolean of
True: (ExceptionInformation : array [0..14] of Longint); // 15 * 4 bytes False: (ExceptAddr: Pointer; ExceptObject: Pointer); // 16 bytes end;
typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags;
struct _EXCEPTION_RECORD* ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;
=============================================================================== ⊙ InitExceptions procedure
=============================================================================== SysUtils.pas 的 initialization 中 InitExceptions;
{ SysUtils.InitExceptions } procedure InitExceptions; begin
OutOfMemory := EOutOfMemory.CreateRes(@SOutOfMemory);
InvalidPointer := EInvalidPointer.CreateRes(@SInvalidPointer); ErrorProc := ErrorHandler; ExceptProc := @ExceptHandler; ExceptionClass := Exception;
ExceptClsProc := @GetExceptionClass; ExceptObjProc := @GetExceptionObject;
AssertErrorProc := @AssertErrorHandler; AbstractErrorProc := @AbstractErrorHandler; end;
{ SysUtils.ErrorHandler }
procedure ErrorHandler(ErrorCode: Byte; ErrorAddr: Pointer); export; var
E: Exception; begin
case ErrorCode of Ord(reOutOfMemory): E := OutOfMemory; Ord(reInvalidPtr): E := InvalidPointer;
Ord(reDivByZero)..Ord(High(TRuntimeError)): begin
with ExceptMap[ErrorCode] do E := EClass.Create(EIdent); end; else
E := CreateInOutError; end;
raise E at ErrorAddr; end;
{ SysUtils.ExceptHandler }
procedure ExceptHandler(ExceptObject: TObject; ExceptAddr: Pointer); far; begin
ShowException(ExceptObject, ExceptAddr); Halt(1); end;
=============================================================================== ⊙ DoneExceptions procedure
=============================================================================== procedure DoneExceptions; begin
if Assigned(OutOfMemory) then
begin
OutOfMemory.AllowFree := True; OutOfMemory.FreeInstance; OutOfMemory := nil; end;
if Assigned(InvalidPointer) then begin
InvalidPointer.AllowFree := True; InvalidPointer.Free; InvalidPointer := nil; end;
ErrorProc := nil; ExceptProc := nil; ExceptionClass := nil; ExceptClsProc := nil; ExceptObjProc := nil; AssertErrorProc := nil; end;
=============================================================================== ⊙ SetExceptionHandler procedure
=============================================================================== System._StartExe 中启动 SetExceptionHandler 过程设置缺省的异常处理函数(System._ExceptionHandler)。
{ System.SetExceptionHandler }
procedure SetExceptionHandler; asm
XOR EDX,EDX { using [EDX] saves some space over [0] } LEA EAX,[EBP-12] // EBP-12 是 TExcFrame.desc
MOV ECX,FS:[EDX] { ECX := head of chain } MOV FS:[EDX],EAX { head of chain := @exRegRec }
MOV [EAX].TExcFrame.next,ECX
MOV [EAX].TExcFrame.desc,offset _ExceptionHandler MOV [EAX].TExcFrame.hEBP,EBP MOV InitContext.ExcFrame,EAX end;
=============================================================================== ⊙ _ExceptionHandler procedure
===============================================================================
{ System._ExceptionHandler }
procedure _ExceptionHandler; asm
MOV EAX,[ESP+4]
TEST [EAX].TExceptionRecord.ExceptionFlags,cUnwindInProgress JNE @@exit
CMP BYTE PTR DebugHook,0 JA @@ExecuteHandler LEA EAX,[ESP+4] PUSH EAX
CALL UnhandledExceptionFilter
CMP EAX,EXCEPTION_CONTINUE_SEARCH JNE @@ExecuteHandler JMP @@exit
@@ExecuteHandler:
MOV EAX,[ESP+4] CLD
CALL _FpuInit MOV EDX,[ESP+8]
PUSH 0 PUSH EAX
PUSH offset @@returnAddress PUSH EDX
CALL RtlUnwindProc
@@returnAddress:
MOV EBX,[ESP+4]
CMP [EBX].TExceptionRecord.ExceptionCode,cDelphiException MOV EDX,[EBX].TExceptionRecord.ExceptAddr MOV EAX,[EBX].TExceptionRecord.ExceptObject JE @@DelphiException2
MOV EDX,ExceptObjProc TEST EDX,EDX
JE MapToRunError MOV EAX,EBX CALL EDX TEST EAX,EAX
JE MapToRunError
MOV EDX,[EBX].TExceptionRecord.ExceptionAddress
@@DelphiException2:
CALL NotifyUnhandled MOV ECX,ExceptProc TEST ECX,ECX
JE @@noExceptProc
CALL ECX { call ExceptProc(ExceptObject, ExceptAddr) }
@@noExceptProc:
MOV ECX,[ESP+4] MOV EAX,217
MOV EDX,[ECX].TExceptionRecord.ExceptAddr MOV [ESP],EDX JMP _RunError
@@exit:
XOR EAX,EAX end;
=============================================================================== ⊙ _RaiseExcept procedure
===============================================================================
{ System._RaiseExcept }
procedure _RaiseExcept; asm
{ When making changes to the way Delphi Exceptions are raised, } { please realize that the C++ Exception handling code reraises } { some exceptions as Delphi Exceptions. Of course we want to }