Отлов исключений внутри DLL: зверь побеждён!

Общие вопросы программирования, алгоритмы и т.п.

Модератор: Модераторы

Отлов исключений внутри DLL: зверь побеждён!

Сообщение Cheb » 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:
Код: Выделить всё
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.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Отлов исключений внутри DLL: зверь побеждён!

Сообщение FedeX » 01.01.2010 00:53:51

ИМХО при использовании длл лучше вообще не использовать такие средства, гораздо привычней и проще(?) возвращать коды ошибок и тихо выходить из функций ИМХО..
Аватара пользователя
FedeX
постоялец
 
Сообщения: 422
Зарегистрирован: 27.03.2006 09:25:34
Откуда: украина, житомир

Re: Отлов исключений внутри DLL: зверь побеждён!

Сообщение Cheb » 01.01.2010 03:20:38

Ы?.. Какие коды ошибок?
Один вшивый AV - и исполнение вылетает из DLL со свистом, хорошо если вы вызов dll-ной функции блоком try обернули.
Никто никому ничего возвратит не даст.

А если это была лишь попытка пролистать превьювом повреждённый gif?

А если у человека проект модульный, и львиная доля кода расположена в DLL, а основной екзешник - так, подставка?
Кстати, могу из классики привести Quake2. Вся игровая механика - отдельная dll. Весь рендер - другая dll.

З.Ы. А хотя бы call stack простейший посмотреть, чтобы узнать, где грохнулось?
Изображение
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Отлов исключений внутри DLL: зверь побеждён!

Сообщение FedeX » 01.01.2010 04:36:16

Ну да это верно, если используеш в коде длл чужие исходы в которых при малейшей некорректности данных вызываються АВшки.. Я же имел в виду ситуацию, когда весь код длл пишеш ты сам.. Но вообще про то что можно использовать ещё и чужой код я сейчас вообще забыл - в Новый Год, периодически вставая из-за стола, лучше мне вообще на форум не заходить... Сорри :oops:
Аватара пользователя
FedeX
постоялец
 
Сообщения: 422
Зарегистрирован: 27.03.2006 09:25:34
Откуда: украина, житомир


Вернуться в Общее

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 26

Рейтинг@Mail.ru