Отлов исключений внутри DLL: зверь побеждён!
 Добавлено: 31.12.2009 20:32:03
Добавлено: 31.12.2009 20:32:03Как всем, наверно, известно, системные исключения (типа AV) под виндовз в dll блоками try не ловятся, прошибая прямо до ближайшего блока try в основном exe. Что самое обидное - в линуксе всё отлично работает.
Если dll чужая - трудно что-то сделать, но если вы её делаете сами (модульная структура у вашей программы, например) - то решение, которое я нашёл - 100% для вас. Зверь повержен!
Вкратце: устанавливаем собственный хандлер исключений. В нём проверяем адрес исключения: если он не принадлежит искомой dll, то вызываем старый хандлер (который установила RTL при старте программы). Если принадлежит - подтасовываем адрес возврата на процедуру, которая вызовет специальную процедуру вашей dll, вызывающую языковое исключение. Которое затем отлично ловится всеми блоками try!
Всё на буржуинском, поскольку хочу ещё на форум pascalgamedevelopment запостить.
Если хотите более внятных сообщений об ошибках - организуйте в собственном хандлере механизм записи информации об исключении (хотя бы кода исключения) в глобальную переменную и затем передавайте исключениевызывающей функции dll, чтобы та уже сочиняла более подробные сообщения об ошибках.
Хак проверен на практике под Windows XP и под Wine, полёт нормальный.
(P.S. Это FreePascal 2.2.x)
sehhck_dll.pas:
sehhck_exe.pas:
			Если dll чужая - трудно что-то сделать, но если вы её делаете сами (модульная структура у вашей программы, например) - то решение, которое я нашёл - 100% для вас. Зверь повержен!
Вкратце: устанавливаем собственный хандлер исключений. В нём проверяем адрес исключения: если он не принадлежит искомой dll, то вызываем старый хандлер (который установила RTL при старте программы). Если принадлежит - подтасовываем адрес возврата на процедуру, которая вызовет специальную процедуру вашей dll, вызывающую языковое исключение. Которое затем отлично ловится всеми блоками try!
Всё на буржуинском, поскольку хочу ещё на форум pascalgamedevelopment запостить.
Если хотите более внятных сообщений об ошибках - организуйте в собственном хандлере механизм записи информации об исключении (хотя бы кода исключения) в глобальную переменную и затем передавайте исключениевызывающей функции dll, чтобы та уже сочиняла более подробные сообщения об ошибках.
Хак проверен на практике под Windows XP и под Wine, полёт нормальный.
(P.S. Это FreePascal 2.2.x)
sehhck_dll.pas:
- Код: Выделить всё
- library sehhck_dll;
 {$mode objfpc}
 {$longstrings on}
 uses
 Classes, SysUtils, Windows;
 procedure mytestproc; cdecl; export;
 begin
 try
 WriteLn('Now DLL will raise an Access Violation...');
 byte(nil^):= 0;
 except
 WriteLn('DLL caught an exception:');
 WriteLn(' "' + (ExceptObject as Exception).Message
 + '" of ' + (ExceptObject as Exception).ClassName);
 end;
 end;
 procedure myraiseproc; cdecl; export;
 begin
 WriteLn('Now DLL will raise a Pascal exception...');
 Raise Exception.Create('Manually raised Pascal exception');
 end;
 exports
 mytestproc name 'mytestproc',
 myraiseproc name 'myraiseproc';
 end.
sehhck_exe.pas:
- Код: Выделить всё
- program sehhk_exe;
 {$mode delphi}
 {$longstrings on}
 {$apptype console}
 uses
 Classes, SysUtils, Windows;
 //Copy-pasted from the System unit --------------------------------------------\
 {
 Error code definitions for the Win32 API functions
 Values are 32 bit values layed out as follows:
 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
 +---+-+-+-----------------------+-------------------------------+
 |Sev|C|R| Facility | Code |
 +---+-+-+-----------------------+-------------------------------+
 where
 Sev - is the severity code
 00 - Success
 01 - Informational
 10 - Warning
 11 - Error
 C - is the Customer code flag
 R - is a reserved bit
 Facility - is the facility code
 Code - is the facility's status code
 }
 const
 SEVERITY_SUCCESS = $00000000;
 SEVERITY_INFORMATIONAL = $40000000;
 SEVERITY_WARNING = $80000000;
 SEVERITY_ERROR = $C0000000;
 const
 STATUS_SEGMENT_NOTIFICATION = $40000005;
 DBG_TERMINATE_THREAD = $40010003;
 DBG_TERMINATE_PROCESS = $40010004;
 DBG_CONTROL_C = $40010005;
 DBG_CONTROL_BREAK = $40010008;
 STATUS_GUARD_PAGE_VIOLATION = $80000001;
 STATUS_DATATYPE_MISALIGNMENT = $80000002;
 STATUS_BREAKPOINT = $80000003;
 STATUS_SINGLE_STEP = $80000004;
 DBG_EXCEPTION_NOT_HANDLED = $80010001;
 STATUS_ACCESS_VIOLATION = $C0000005;
 STATUS_IN_PAGE_ERROR = $C0000006;
 STATUS_INVALID_HANDLE = $C0000008;
 STATUS_NO_MEMORY = $C0000017;
 STATUS_ILLEGAL_INSTRUCTION = $C000001D;
 STATUS_NONCONTINUABLE_EXCEPTION = $C0000025;
 STATUS_INVALID_DISPOSITION = $C0000026;
 STATUS_ARRAY_BOUNDS_EXCEEDED = $C000008C;
 STATUS_FLOAT_DENORMAL_OPERAND = $C000008D;
 STATUS_FLOAT_DIVIDE_BY_ZERO = $C000008E;
 STATUS_FLOAT_INEXACT_RESULT = $C000008F;
 STATUS_FLOAT_INVALID_OPERATION = $C0000090;
 STATUS_FLOAT_OVERFLOW = $C0000091;
 STATUS_FLOAT_STACK_CHECK = $C0000092;
 STATUS_FLOAT_UNDERFLOW = $C0000093;
 STATUS_INTEGER_DIVIDE_BY_ZERO = $C0000094;
 STATUS_INTEGER_OVERFLOW = $C0000095;
 STATUS_PRIVILEGED_INSTRUCTION = $C0000096;
 STATUS_STACK_OVERFLOW = $C00000FD;
 STATUS_CONTROL_C_EXIT = $C000013A;
 STATUS_FLOAT_MULTIPLE_FAULTS = $C00002B4;
 STATUS_FLOAT_MULTIPLE_TRAPS = $C00002B5;
 STATUS_REG_NAT_CONSUMPTION = $C00002C9;
 EXCEPTION_EXECUTE_HANDLER = 1;
 EXCEPTION_CONTINUE_EXECUTION = -1;
 EXCEPTION_CONTINUE_SEARCH = 0;
 EXCEPTION_MAXIMUM_PARAMETERS = 15;
 CONTEXT_X86 = $00010000;
 CONTEXT_CONTROL = CONTEXT_X86 or $00000001;
 CONTEXT_INTEGER = CONTEXT_X86 or $00000002;
 CONTEXT_SEGMENTS = CONTEXT_X86 or $00000004;
 CONTEXT_FLOATING_POINT = CONTEXT_X86 or $00000008;
 CONTEXT_DEBUG_REGISTERS = CONTEXT_X86 or $00000010;
 CONTEXT_EXTENDED_REGISTERS = CONTEXT_X86 or $00000020;
 CONTEXT_FULL= CONTEXT_CONTROL or CONTEXT_INTEGER or CONTEXT_SEGMENTS;
 MAXIMUM_SUPPORTED_EXTENSION = 512;
 type
 PFloatingSaveArea = ^TFloatingSaveArea;
 TFloatingSaveArea = packed record
 ControlWord : Cardinal;
 StatusWord : Cardinal;
 TagWord : Cardinal;
 ErrorOffset : Cardinal;
 ErrorSelector : Cardinal;
 DataOffset : Cardinal;
 DataSelector : Cardinal;
 RegisterArea : array[0..79] of Byte;
 Cr0NpxState : Cardinal;
 end;
 PContext = ^TContext;
 TContext = packed record
 //
 // The flags values within this flag control the contents of
 // a CONTEXT record.
 //
 ContextFlags : Cardinal;
 //
 // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
 // set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT
 // included in CONTEXT_FULL.
 //
 Dr0, Dr1, Dr2,
 Dr3, Dr6, Dr7 : Cardinal;
 //
 // This section is specified/returned if the
 // ContextFlags word contains the flag CONTEXT_FLOATING_POINT.
 //
 FloatSave : TFloatingSaveArea;
 //
 // This section is specified/returned if the
 // ContextFlags word contains the flag CONTEXT_SEGMENTS.
 //
 SegGs, SegFs,
 SegEs, SegDs : Cardinal;
 //
 // This section is specified/returned if the
 // ContextFlags word contains the flag CONTEXT_INTEGER.
 //
 Edi, Esi, Ebx,
 Edx, Ecx, Eax : Cardinal;
 //
 // This section is specified/returned if the
 // ContextFlags word contains the flag CONTEXT_CONTROL.
 //
 Ebp : Cardinal;
 Eip : Cardinal;
 SegCs : Cardinal;
 EFlags, Esp, SegSs : Cardinal;
 //
 // This section is specified/returned if the ContextFlags word
 // contains the flag CONTEXT_EXTENDED_REGISTERS.
 // The format and contexts are processor specific
 //
 ExtendedRegisters : array[0..MAXIMUM_SUPPORTED_EXTENSION-1] of Byte;
 end;
 type
 PExceptionRecord = ^TExceptionRecord;
 TExceptionRecord = packed record
 ExceptionCode : cardinal;
 ExceptionFlags : Longint;
 ExceptionRecord : PExceptionRecord;
 ExceptionAddress : Pointer;
 NumberParameters : Longint;
 ExceptionInformation : array[0..EXCEPTION_MAXIMUM_PARAMETERS-1] of Pointer;
 end;
 PExceptionPointers = ^TExceptionPointers;
 TExceptionPointers = packed record
 ExceptionRecord : PExceptionRecord;
 ContextRecord : PContext;
 end;
 { type of functions that should be used for exception handling }
 TTopLevelExceptionFilter
 = function (excep : PExceptionPointers) : Longint;stdcall;
 function SetUnhandledExceptionFilter( lpTopLevelExceptionFilter:
 TTopLevelExceptionFilter) : TTopLevelExceptionFilter;
 stdcall;external 'kernel32' name 'SetUnhandledExceptionFilter';
 //end copy-paste --------------------------------------------------------------/
 var
 Mydll: THandle = 0;
 OldFilter: TTopLevelExceptionFilter = nil;
 answer: string;
 dllraiseproc: procedure; cdecl;
 dlltestproc: procedure; cdecl;
 procedure JumpToDllRaiseFunction;
 begin
 SysResetFPU;
 dllraiseproc;
 raise Exception.Create('Oops... Should''ve never reached this point!');
 end;
 function GetModuleByAddr(addr: pointer): THandle;
 var
 Tmm: TMemoryBasicInformation;
 begin
 if VirtualQuery(addr, @Tmm, SizeOf(Tmm)) <> sizeof(Tmm)
 then Result:=0
 else Result:= THandle(Tmm.AllocationBase);
 end;
 function MyExceptionFilter(excep : PExceptionPointers) : Longint; stdcall;
 var
 res: longint;
 err: byte;
 must_reset_fpu: boolean;
 begin
 WriteLn('System called our top level unhandled exception filter.'#10#13
 +' Exception code = '
 + IntToHex(excep^.ExceptionRecord^.ExceptionCode, 8));
 if MyDll = GetModuleByAddr(pointer(excep^.ContextRecord^.Eip)) then begin
 WriteLn('The exception adress does belong to our DLL.');
 excep^.ContextRecord^.Eip := Longint(@JumpToDllRaiseFunction);
 excep^.ExceptionRecord^.ExceptionCode := 0;
 Result := EXCEPTION_CONTINUE_EXECUTION;
 end
 else begin
 WriteLn('The exception address doesn''t belong to our DLL.'#10#13
 +' Calling the old filter installed by RTL.');
 Result:= OldFilter(excep);
 end;
 end;
 procedure InstallHack;
 begin
 OldFilter:= SetUnhandledExceptionFilter(MyExceptionFilter);
 Writeln('Ensuring that the hack didn''t broke our own exception mechanism...');
 try
 WriteLn('Raising an AV...');
 byte(nil^):= 0;
 except
 WriteLn('Exe caught an exception: '#10#13' "'
 + (ExceptObject as Exception).Message
 + '" by ' + (ExceptObject as Exception).ClassName);
 end;
 end;
 begin
 try
 WriteLn('Loading the dll...');
 MyDll:= LoadLibrary('./sehhck_dll.dll');
 if MyDll = 0
 then raise Exception.Create('Failed to load the dll! :(');
 dllraiseproc:= GetProcAddress(MyDll, PChar('myraiseproc'));
 dlltestproc:= GetProcAddress(MyDll, PChar('mytestproc'));
 if not Assigned(@dlltestproc) or not Assigned(@dllraiseproc)
 then raise Exception.Create('Failed to load procedures from the DLL');
 WriteLn('Use the SEH hack? ("yes" / "no")') ;
 answer:= '';
 repeat
 if answer <> '' then Writeln('Please enter "yes" or "no".');
 readln(answer);
 if answer = '' then Halt(0);
 answer:= UpperCase(answer);
 until (answer = 'YES') or (answer = 'NO');
 if answer = 'YES' then InstallHack;
 WriteLn('Testing!');
 dlltestproc;
 except
 WriteLn('Exe caught an exception: '#10#13' "'
 + (ExceptObject as Exception).Message
 + '" by ' + (ExceptObject as Exception).ClassName);
 end;
 WriteLn('Press Enter to close.');
 ReadLn;
 end.

