{**************************************************************************************************} { } { Project JEDI Code Library (JCL) } { } { The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); } { you may not use this file except in compliance with the License. You may obtain a copy of the } { License at http://www.mozilla.org/MPL/ } { } { Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF } { ANY KIND, either express or implied. See the License for the specific language governing rights } { and limitations under the License. } { } { The Original Code is JclHookExcept.pas. } { } { The Initial Developers of the Original Code are documented in the accompanying help file } { JCLHELP.hlp. Portions created by these individuals are Copyright (C) of these individuals. } { } {**************************************************************************************************} { } { Exception hooking routines } { } { Unit owner: Petr Vones } { } {**************************************************************************************************} // $Id: JclHookExcept.pas,v 1.2 2004/04/14 21:55:07 druid Exp $ unit JclHookExcept; interface {$I jcl.inc} uses Windows, SysUtils; //-------------------------------------------------------------------------------------------------- // Exception hooking notifiers routines //-------------------------------------------------------------------------------------------------- type TJclExceptNotifyProc = procedure(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean); TJclExceptNotifyMethod = procedure(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean) of object; TJclExceptNotifyPriority = (npNormal, npFirstChain); function JclAddExceptNotifier(const NotifyProc: TJclExceptNotifyProc; Priority: TJclExceptNotifyPriority = npNormal): Boolean; overload; function JclAddExceptNotifier(const NotifyMethod: TJclExceptNotifyMethod; Priority: TJclExceptNotifyPriority = npNormal): Boolean; overload; function JclRemoveExceptNotifier(const NotifyProc: TJclExceptNotifyProc): Boolean; overload; function JclRemoveExceptNotifier(const NotifyMethod: TJclExceptNotifyMethod): Boolean; overload; procedure JclReplaceExceptObj(NewExceptObj: Exception); //-------------------------------------------------------------------------------------------------- // Exception hooking routines //-------------------------------------------------------------------------------------------------- function JclHookExceptions: Boolean; function JclUnhookExceptions: Boolean; function JclExceptionsHooked: Boolean; function JclHookExceptionsInModule(Module: HMODULE): Boolean; function JclUnkookExceptionsInModule(Module: HMODULE): Boolean; //-------------------------------------------------------------------------------------------------- // Exceptions hooking in libraries //-------------------------------------------------------------------------------------------------- type TJclModuleArray = array of HMODULE; function JclInitializeLibrariesHookExcept: Boolean; function JclHookedExceptModulesList(var ModulesList: TJclModuleArray): Boolean; //-------------------------------------------------------------------------------------------------- // Hooking routines location info helper //-------------------------------------------------------------------------------------------------- function JclBelongsHookedCode(Addr: Pointer): Boolean; implementation uses Classes, JclBase, JclPeImage, JclSysInfo, JclSysUtils; type PExceptionArguments = ^TExceptionArguments; TExceptionArguments = record ExceptAddr: Pointer; ExceptObj: Exception; end; TNotifierItem = class(TObject) private FNotifyMethod: TJclExceptNotifyMethod; FNotifyProc: TJclExceptNotifyProc; FPriority: TJclExceptNotifyPriority; public constructor Create(const NotifyProc: TJclExceptNotifyProc; Priority: TJclExceptNotifyPriority); overload; constructor Create(const NotifyMethod: TJclExceptNotifyMethod; Priority: TJclExceptNotifyPriority); overload; procedure DoNotify(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean); property Priority: TJclExceptNotifyPriority read FPriority; end; var ExceptionsHooked: Boolean; Kernel32_RaiseException: procedure (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD; lpArguments: PDWORD); stdcall; SysUtils_ExceptObjProc: function (P: PExceptionRecord): Exception; Notifiers: TThreadList; const JclHookExceptDebugHookName = '__JclHookExcept'; type TJclHookExceptDebugHook = procedure(Module: HMODULE; Hook: Boolean); stdcall; TJclHookExceptModuleList = class(TObject) private FModules: TThreadList; protected procedure HookStaticModules; public constructor Create; destructor Destroy; override; class function JclHookExceptDebugHookAddr: Pointer; procedure HookModule(Module: HMODULE); procedure List(var ModulesList: TJclModuleArray); procedure UnhookModule(Module: HMODULE); end; var HookExceptModuleList: TJclHookExceptModuleList; JclHookExceptDebugHook: Pointer; {$IFDEF HOOK_DLL_EXCEPTIONS} exports JclHookExceptDebugHook name JclHookExceptDebugHookName; {$ENDIF HOOK_DLL_EXCEPTIONS} {$STACKFRAMES OFF} threadvar Recursive: Boolean; NewResultExc: Exception; //================================================================================================== // Helper routines //================================================================================================== function RaiseExceptionAddress: Pointer; begin Result := GetProcAddress(GetModuleHandle(kernel32), 'RaiseException'); Assert(Result <> nil); end; //-------------------------------------------------------------------------------------------------- procedure FreeNotifiers; var I: Integer; begin with Notifiers.LockList do try for I := 0 to Count - 1 do TObject(Items[I]).Free; finally Notifiers.UnlockList; end; FreeAndNil(Notifiers); end; //================================================================================================== // TNotifierItem //================================================================================================== constructor TNotifierItem.Create(const NotifyProc: TJclExceptNotifyProc; Priority: TJclExceptNotifyPriority); begin // (rom) added inherited Create inherited Create; FNotifyProc := NotifyProc; FPriority := Priority; end; //-------------------------------------------------------------------------------------------------- constructor TNotifierItem.Create(const NotifyMethod: TJclExceptNotifyMethod; Priority: TJclExceptNotifyPriority); begin // (rom) added inherited Create inherited Create; FNotifyMethod := NotifyMethod; FPriority := Priority; end; //-------------------------------------------------------------------------------------------------- procedure TNotifierItem.DoNotify(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean); begin if Assigned(FNotifyProc) then FNotifyProc(ExceptObj, ExceptAddr, OSException) else if Assigned(FNotifyMethod) then FNotifyMethod(ExceptObj, ExceptAddr, OSException); end; //-------------------------------------------------------------------------------------------------- {$STACKFRAMES ON} procedure DoExceptNotify(ExceptObj: Exception; ExceptAddr: Pointer; OSException: Boolean); var Priorities: TJclExceptNotifyPriority; I: Integer; begin if Recursive then Exit; if Assigned(Notifiers) then begin Recursive := True; NewResultExc := nil; try with Notifiers.LockList do try for Priorities := High(Priorities) downto Low(Priorities) do for I := 0 to Count - 1 do with TNotifierItem(Items[I]) do if Priority = Priorities then DoNotify(ExceptObj, ExceptAddr, OSException); finally Notifiers.UnlockList; end; finally Recursive := False; end; end; end; //-------------------------------------------------------------------------------------------------- procedure HookedRaiseException(ExceptionCode, ExceptionFlags, NumberOfArguments: DWORD; Arguments: PExceptionArguments); stdcall; const {$IFDEF DELPHI2} cDelphiException = $0EEDFACE; {$ELSE} cDelphiException = $0EEDFADE; {$ENDIF DELPHI2} cNonContinuable = 1; begin if (ExceptionFlags = cNonContinuable) and (ExceptionCode = cDelphiException) and (NumberOfArguments = 7) and (DWORD(Arguments) = DWORD(@Arguments) + 4) then DoExceptNotify(Arguments.ExceptObj, Arguments.ExceptAddr, False); Kernel32_RaiseException(ExceptionCode, ExceptionFlags, NumberOfArguments, PDWORD(Arguments)); end; //-------------------------------------------------------------------------------------------------- function HookedExceptObjProc(P: PExceptionRecord): Exception; var NewResultExcCache: Exception; // TLS optimization begin Result := SysUtils_ExceptObjProc(P); DoExceptNotify(Result, P^.ExceptionAddress, True); NewResultExcCache := NewResultExc; if NewResultExcCache <> nil then Result := NewResultExcCache; end; {$IFNDEF STACKFRAMES_ON} {$STACKFRAMES OFF} {$ENDIF STACKFRAMES_ON} //-------------------------------------------------------------------------------------------------- // Do not change ordering of HookedRaiseException, HookedExceptObjProc and JclBelongsHookedCode routines function JclBelongsHookedCode(Addr: Pointer): Boolean; begin Result := (Cardinal(@HookedRaiseException) < Cardinal(@JclBelongsHookedCode)) and (Cardinal(@HookedRaiseException) <= Cardinal(Addr)) and (Cardinal(@JclBelongsHookedCode) > Cardinal(Addr)); end; //-------------------------------------------------------------------------------------------------- function JclAddExceptNotifier(const NotifyProc: TJclExceptNotifyProc; Priority: TJclExceptNotifyPriority): Boolean; begin Result := Assigned(NotifyProc); if Result then with Notifiers.LockList do try Add(TNotifierItem.Create(NotifyProc, Priority)); finally Notifiers.UnlockList; end; end; //-------------------------------------------------------------------------------------------------- function JclAddExceptNotifier(const NotifyMethod: TJclExceptNotifyMethod; Priority: TJclExceptNotifyPriority): Boolean; begin Result := Assigned(NotifyMethod); if Result then with Notifiers.LockList do try Add(TNotifierItem.Create(NotifyMethod, Priority)); finally Notifiers.UnlockList; end; end; //-------------------------------------------------------------------------------------------------- function JclRemoveExceptNotifier(const NotifyProc: TJclExceptNotifyProc): Boolean; var O: TNotifierItem; I: Integer; begin Result := Assigned(NotifyProc); if Result then with Notifiers.LockList do try for I := 0 to Count - 1 do begin O := TNotifierItem(Items[I]); if @O.FNotifyProc = @NotifyProc then begin O.Free; Items[I] := nil; end; end; Pack; finally Notifiers.UnlockList; end; end; //-------------------------------------------------------------------------------------------------- function JclRemoveExceptNotifier(const NotifyMethod: TJclExceptNotifyMethod): Boolean; var O: TNotifierItem; I: Integer; begin Result := Assigned(NotifyMethod); if Result then with Notifiers.LockList do try for I := 0 to Count - 1 do begin O := TNotifierItem(Items[I]); if (TMethod(O.FNotifyMethod).Code = TMethod(NotifyMethod).Code) and (TMethod(O.FNotifyMethod).Data = TMethod(NotifyMethod).Data) then begin O.Free; Items[I] := nil; end; end; Pack; finally Notifiers.UnlockList; end; end; //-------------------------------------------------------------------------------------------------- procedure JclReplaceExceptObj(NewExceptObj: Exception); begin Assert(Recursive); NewResultExc := NewExceptObj; end; //-------------------------------------------------------------------------------------------------- function JclHookExceptions: Boolean; var RaiseExceptionAddressCache: Pointer; begin if not ExceptionsHooked then begin Recursive := False; RaiseExceptionAddressCache := RaiseExceptionAddress; with TJclPeMapImgHooks do Result := ReplaceImport(SystemBase, kernel32, RaiseExceptionAddressCache, @HookedRaiseException); if Result then begin @Kernel32_RaiseException := RaiseExceptionAddressCache; SysUtils_ExceptObjProc := System.ExceptObjProc; System.ExceptObjProc := @HookedExceptObjProc; end; ExceptionsHooked := Result; end else Result := True; end; //-------------------------------------------------------------------------------------------------- function JclUnhookExceptions: Boolean; begin if ExceptionsHooked then begin with TJclPeMapImgHooks do ReplaceImport(SystemBase, kernel32, @HookedRaiseException, @Kernel32_RaiseException); System.ExceptObjProc := @SysUtils_ExceptObjProc; @SysUtils_ExceptObjProc := nil; @Kernel32_RaiseException := nil; Result := True; ExceptionsHooked := False; end else Result := True; end; //-------------------------------------------------------------------------------------------------- function JclExceptionsHooked: Boolean; begin Result := ExceptionsHooked; end; //-------------------------------------------------------------------------------------------------- function JclHookExceptionsInModule(Module: HMODULE): Boolean; begin Result := ExceptionsHooked and TJclPeMapImgHooks.ReplaceImport(Pointer(Module), kernel32, RaiseExceptionAddress, @HookedRaiseException); end; //-------------------------------------------------------------------------------------------------- function JclUnkookExceptionsInModule(Module: HMODULE): Boolean; begin Result := ExceptionsHooked and TJclPeMapImgHooks.ReplaceImport(Pointer(Module), kernel32, @HookedRaiseException, @Kernel32_RaiseException); end; //================================================================================================== // Exceptions hooking in libraries //================================================================================================== procedure JclHookExceptDebugHookProc(Module: HMODULE; Hook: Boolean); stdcall; begin if Hook then HookExceptModuleList.HookModule(Module) else HookExceptModuleList.UnhookModule(Module); end; //-------------------------------------------------------------------------------------------------- function CallExportedHookExceptProc(Module: HMODULE; Hook: Boolean): Boolean; var HookExceptProcPtr: PPointer; HookExceptProc: TJclHookExceptDebugHook; begin HookExceptProcPtr := TJclHookExceptModuleList.JclHookExceptDebugHookAddr; Result := Assigned(HookExceptProcPtr); if Result then begin @HookExceptProc := HookExceptProcPtr^; if Assigned(HookExceptProc) then HookExceptProc(Module, True); end; end; //-------------------------------------------------------------------------------------------------- function JclInitializeLibrariesHookExcept: Boolean; begin {$IFDEF HOOK_DLL_EXCEPTIONS} if IsLibrary then Result := CallExportedHookExceptProc(SystemTObjectInstance, True) else begin if not Assigned(HookExceptModuleList) then HookExceptModuleList := TJclHookExceptModuleList.Create; Result := True; end; {$ELSE HOOK_DLL_EXCEPTIONS} Result := True; {$ENDIF HOOK_DLL_EXCEPTIONS} end; //-------------------------------------------------------------------------------------------------- function JclHookedExceptModulesList(var ModulesList: TJclModuleArray): Boolean; begin {$IFDEF HOOK_DLL_EXCEPTIONS} Result := Assigned(HookExceptModuleList); if Result then HookExceptModuleList.List(ModulesList); {$ELSE HOOK_DLL_EXCEPTIONS} Result := False; {$ENDIF HOOK_DLL_EXCEPTIONS} end; //-------------------------------------------------------------------------------------------------- procedure FinalizeLibrariesHookExcept; begin FreeAndNil(HookExceptModuleList); if IsLibrary then CallExportedHookExceptProc(SystemTObjectInstance, False); end; //================================================================================================== // TJclHookExceptModuleList //================================================================================================== constructor TJclHookExceptModuleList.Create; begin // (rom) added inherited Create inherited Create; FModules := TThreadList.Create; HookStaticModules; JclHookExceptDebugHook := @JclHookExceptDebugHookProc; end; //-------------------------------------------------------------------------------------------------- destructor TJclHookExceptModuleList.Destroy; begin JclHookExceptDebugHook := nil; FreeAndNil(FModules); inherited Destroy; end; //-------------------------------------------------------------------------------------------------- procedure TJclHookExceptModuleList.HookModule(Module: HMODULE); begin with FModules.LockList do try if IndexOf(Pointer(Module)) = -1 then begin Add(Pointer(Module)); JclHookExceptionsInModule(Module); end; finally FModules.UnlockList; end; end; //-------------------------------------------------------------------------------------------------- procedure TJclHookExceptModuleList.HookStaticModules; var ModulesList: TStringList; I: Integer; Module: HMODULE; begin ModulesList := nil; with FModules.LockList do try ModulesList := TStringList.Create; if LoadedModulesList(ModulesList, GetCurrentProcessId, True) then for I := 0 to ModulesList.Count - 1 do begin Module := HMODULE(ModulesList.Objects[I]); if GetProcAddress(Module, JclHookExceptDebugHookName) <> nil then HookModule(Module); end; finally FModules.UnlockList; ModulesList.Free; end; end; //-------------------------------------------------------------------------------------------------- class function TJclHookExceptModuleList.JclHookExceptDebugHookAddr: Pointer; var HostModule: HMODULE; begin HostModule := GetModuleHandle(nil); Result := GetProcAddress(HostModule, JclHookExceptDebugHookName); end; //-------------------------------------------------------------------------------------------------- procedure TJclHookExceptModuleList.List(var ModulesList: TJclModuleArray); var I: Integer; begin with FModules.LockList do try SetLength(ModulesList, Count); for I := 0 to Count - 1 do ModulesList[I] := HMODULE(Items[I]); finally FModules.UnlockList; end; end; //-------------------------------------------------------------------------------------------------- procedure TJclHookExceptModuleList.UnhookModule(Module: HMODULE); begin with FModules.LockList do try Remove(Pointer(Module)); finally FModules.UnlockList; end; end; //-------------------------------------------------------------------------------------------------- initialization Notifiers := TThreadList.Create; finalization {$IFDEF HOOK_DLL_EXCEPTIONS} FinalizeLibrariesHookExcept; {$ENDIF HOOK_DLL_EXCEPTIONS} FreeNotifiers; end.