Решил реализовать SEH в Delphi по статье. Сначала есно попробовал в с++, все нормально. Перешел на Delphi. Мдаааа... Чего-то не очень Код (Text): const ExceptionContinueExecution = 0; ExceptionContinueSearch = 1; ExceptionNestedException = 2; ExceptionCollidedUnwind = 3; function _except_handler(ExceptionRecord: _EXCEPTION_RECORD; EstablisherFrame: DWORD; ContextRecord: _CONTEXT; DispatcherContext: DWORD): Integer; cdecl; begin MessageBox(0, 'Error', 'Error', MB_OK or MB_ICONWARNING); ContextRecord.Eax := integer(@iData); Result := ExceptionContinueExecution; end; procedure TForm1.Button1Click(Sender: TObject); var p: pointer; begin p := @_except_handler; asm push p push FS:[0] mov FS:[0], ESP mov EAX, 0 mov [EAX], 1 end; MessageBox(0, '1', '1', MB_OK); asm mov EAX, [ESP] mov FS:[0], EAX add ESP, 8 end; end; Что заметил. Если мой обработчик отказывается обрабатывать сообщения, то выскакивает ошибка, как и должно быть. Если соглашается обработать, то в регистре EAX 0 и опять вызывается мой обработчик. Такая ситуация в стандартном Application. При консольном проекте прога просто вылетает без ошибок... Может наставите на путь...
1) В обработчик передаются адреса структур, а не сами структуры; 2) Чтобы программа нормально продолжила своё выполнение нужно в обработчике исключения внести адрес безопасного места в структуру CONTEXT.
Dimson прав по п.1), в _except_handler параметры должны быть либо указателями на соответсвующие структуры, либо var-параметрами (что соб-но одно и тоже) Если у тебя _CONTEXT это не указатель, а сама структура, то присваивание ContextRecord.Eax пишет фиг знает куда в стек, но не в структуру контекст
SnugForce, дельфи это работает особо тот случий что ты указал Код (Text): mov EAX, 0 mov [EAX], 1 or PDWORD(0)^ := 0; только чуть код подправь Код (Text): asm mov SEH.SaveEip, offset Exception // это метка, куда тебе нужно будет попасть после исключения mov SEH.OrgEbp, ebp push offset _except_handler push large dword ptr fs:0 mov SEH.OrgEsp, esp mov large fs:0, esp end; PDWORD(0)^ := 0; Writeln('тут мне не нсуждено оказаться'); asm Exception: pop large dword ptr fs:0 add esp, 4 end; Writeln('а тут сам бог велел быть');
О-о, сколько сторонников "безопасного места" Если сам пишешь обработчик под конкретное исключение в конкретном месте (как в данном случае), то все эти излишества совершенно никчему. Нужно просто или 1) устранить условие ошибки - в данном случае изменить значение в EAX на валидный адрес для записи и повторить операцию (SnugForce так и делает) или 2) перепрыгнуть ошибочную инструкцию, увеличив CONTEXT.EIP на ее длину (на асме можно конечно непосредственно адрес "безопасного места" в regEIP забить, но на дельфях все метки - локальные для процедур)
leo, я пока еще не встречал нормальный обработчик исключений писанный на асме, если честно сам потратил не мало времени с разборм этой темы, но пришел к выводу, что так легко это не делается, если исключение происходит на уровне выполнения каких либо команд и вам заранее известно какие регистры надо править, то все вери гуд, а вот если вы работаете к примеру в ядре и используете RTL-функции то тут не все просто, если у вас есть наработки по данной теме может поделитесь?
LuckyDevil Собственых наработок по универсальным SEH'ам у меня нет, т.к. сложные вещи пишу на тех же дельфях c try\except, а на асме использую только простые локальные обработчики. А в дельфях кстати никакой safeplace тоже специально не сохраняется, а используется хитрая комбинация из 2-3х прыжков (недавно эта тема пролетала). Примерно вот так: Код (Text): _try ;устанавливаем SEH push _jmp_to_SEH ;пушится не сам обработчик, а jmp на него push fs:[0] mov fs:[0],esp ......... _except: ;=safeplace ......... _end: pop fs:[0] add esp,4 jmp _continue ;обходим прыжки для продолжения _jmp_to_SEH: jmp @_except_handler ;прыжок на универсальный обработчик jmp _except ;сюда обработчик возвращает управление _continue: ..... function _except_handler(..):integer; //универсальный обработчик asm ....... mov edx,[esp+8] ;адрес EstablisherFrame = Exception_Registration mov edx,[edx+4] ;адрес обработчика = _jmp_to_SEH add edx,5 ;прибавляем длину инструкции jmp и получаем адрес инструкции jmp _except для возврата ;пишем edx в CONTEXT.regEIP ...... end;
try except finally я тоже использую, но у меня задача сделать глобальный обработчик исключение в программе, плюс ко всему говорят, что передачу по SEH довольно проблематично найти, нежели простой вызов функции. Т.е. какая конкретно ошибка будет я знаю т.к. сам ее буду вызывать.
SnugForce, на сайте есть очень много интересных статей на эту тему, так что почву для размышлений пожно узреть оттуда, еще стоит посмотреть как устроен обработчик исключений в С\С++, я тут еще прикрепил файлик это код выдернутый из Filemon, реализация глобального исключения, как раз таки быть может из-за него теперь не доступны соурсы самого Filemon, может это поможет тебе в разборе данной темы. 2098244473__except_handler3.txt
all Сознаюсь... понадеялся на delphi и не стал смотреть в объявления структуры... Стыдно-то как... Код (Text): function _except_handler(ExceptionRecord: PExceptionRecord; EstablisherFrame: DWORD; ContextRecord: PContext; DispatcherContext: DWORD): Integer; cdecl;
LuckyDevil Я глобальный имел ввиду про исключение в пределах потока. try except ведь не позволит этого делать.
SnugForce Это тебе нужно смотреть в сторону UnhandledExceptionFilter А в самом простом случае на дельфи я когда-то делал так Код (Text): program SEH; uses Windows; type _SEH = record SafeEip:DWORD; // The offset where it's safe to continue execution PrevEsp:DWORD; // The previous value of esp PrevEbp:DWORD; // The previous value of ebp end; // // Exception disposition return values. // EXCEPTION_DISPOSITION = DWORD; const ExceptionContinueExecution = 0; ExceptionContinueSearch = 1; ExceptionNestedException = 2; ExceptionCollidedUnwind = 3; szCaption : PChar = 'SEH Test' + #0; szNotOccured : PChar = 'Exception NOT occured !' + #0; szOccured : PChar = 'Exception occured !' + #0; var __seh : _SEH; lpText : PAnsiChar; label _SafePlace; {$O+} function _except_handler( ExceptionRecord:PExceptionRecord; EstablisherFrame:Pointer; ContextRecord:PContext; DispatcherContext:Pointer): EXCEPTION_DISPOSITION; cdecl; begin ContextRecord.Eip := __seh.SafeEip; ContextRecord.Esp := __seh.PrevEsp; ContextRecord.Ebp := __seh.PrevEbp; // Tell the OS to restart the faulting instruction Result := ExceptionContinueExecution; end; {$O-} begin lpText := szOccured; asm push OFFSET _except_handler push DWORD PTR fs:[0] // address of next ERR structure mov fs:[0], esp // give FS:[0] the ERR address just made mov __seh.SafeEip, OFFSET _SafePlace mov __seh.PrevEsp, esp mov __seh.PrevEbp, ebp end; asm db 0CCh end; lpText := szNotOccured; _SafePlace: asm pop DWORD PTR fs:[0] // restore next ERR structure to FS:[0] add esp, 4 // throw away rest of ERR structure end; MessageBox(0, lpText, szCaption, MB_OK or MB_ICONINFORMATION); end.
Единственное, чего не могу понять - почему все норовят сохранить EIP,ESP,EBP в глобальной переменной, хотя в книжках рекомендуют все это пихать в стек в EstablisherFrame )
SnagForce Дык это же есть в статьях Питрека (ч.2 Расширенный фрейм обработки исключений) и Гордона (структура ERR). Обязательными полями EstablisherFrame является только запись EXCEPTION_REGISTRATION (указатель на предыдущую запись и указатель на наш обработчик). Но никто не запрещает перед указателем на обработчик запушить в стек и другие параметры, в частности safeplace и EBP. Насколько я понимаю, стандартов тут нет (у Гордона и Питрека расширенные записи несколько разные). Поэтому для себя можешь выбрать любой фиксированный порядок. Например: Код (Text): push epb ;[Frame+0Ch] = prevEBP push offset _SafePlace ;[Frame+08h] = prevEIP push offset _except_handler ;Frame+4 push fs:[0] ;Frame (указатель на Frame и есть prevESP, поэтому сохранять его незачем) mov fs:[0],esp Сответсвенно в обработчике у тебя будет Код (Text): mov eax,[esp+8] ;prevESP = указателю EstablisherFrame mov edx,[eax+8] ;prevEIP = _SafePlace mov ecx,[eax+0Ch] ;prevEBP Ну и ес-но при снятии обработчика не забываем очистить стек от доп.параметров (pop fs:[0] + add esp,4*3)
Ничего не понимаю. Вот этот код написано сохраняет адрес предыдущего обработчика. Код (Text): push OFFSET _except_handler push DWORD PTR fs:[0] mov fs:[0], esp А чего прога тогда после вызова моего обработчки вылетает? Упарился уже, даже как-то до синего экрана дошло...
Может это из-за того что занесение нового обработчика я делал в процедуре? Как только перенес в код - Старый обработчик вызывается уже нормально, а как же устанавливать в функции?
Я тоже ничего не понимаю Куда чего перенес, что вызывается нормально, а что вылетает - объясни толком Приведенный кусок 100% рабочий, значит дело в чем-то другом - посмотри в отладчике где возникает ошибка
вот как раз под отладчиком и синий экран появляется Код (Text): procedure Start; asm push OFFSET _except_handler push DWORD PTR fs:[0] mov fs:[0], esp end; ... Start; asm mov eax, 0 mov [eax], 1 end; Stop;
Дык в какой момент ошибка, то появляется ? В обработчик _except_handler управление передается или нет ? Может ты чего в обработчике опять намудрил ) Хоть код обработчика приведи, что ли...