Обработчик структурированных исключений и Delphi
Я пытаюсь установить SEH
без использования try except
(Это для моих личных знаний, чтобы лучше понять, как работает SEH)
Следующий код не работает
type
TSeh = packed record
OldSeh:DWORD;
NewSeh:DWORD;
end;
procedure test;
begin
WriteLn('Hello from seh');
end;
var
eu:TSeh;
old_seh:DWORD;
begin
asm
mov eax,fs:[0]
mov old_seh,eax
end;
eu.OldSeh := old_seh;
eu.NewSeh := DWORD(@test);
asm
mov eax,offset eu
mov fs:[0],eax
ret //This will cause an exception because jumps on an invalid memory address
end;
end.
Но это делает
procedure test;
begin
WriteLn('Hello from seh');
end;
begin
asm
push offset test
push fs:[0]
mov fs:[0],esp
ret //This will cause an exception because jumps on an invalid memory address
end;
end.
Что я делаю неправильно? В чем разница между первым кодом и вторым?
Ответы
Ответ 1
Windows требует, чтобы все стековые фреймы находились внутри стека, выделенного системой. Он также требует, чтобы стеки кадров были в последовательном порядке в стеке. Кроме того, для обработки исключений требуется, чтобы все "записи об исключениях" находились в стеке, и для их
последовательный порядок через стек памяти.
Я понял это/читал это где-то лет назад, когда писал библиотеку микропотоков (http://www.eternallines.com/microthreads).
Ответ 2
Вы не можете использовать процедуру test
в качестве функции обратного вызова исключения, поскольку функция обратного вызова исключения имеет другой прототип. Прочтите статью Мэтта Пьетрека, ИМО - лучший источник информации о Win32 SEH.
Обновление
Для дальнейших исследований я бы рекомендовал следующие изменения в коде, чтобы сделать проблему более чистой:
function test: Integer;
begin
WriteLn('Hello from seh');
Result:= 0;
end;
(поскольку обратный вызов exception должен возвращать целочисленное значение в EAX)
И для первого фрагмента кода
begin
asm
mov eax,fs:[0]
mov old_seh,eax
end;
eu.OldSeh := old_seh;
eu.NewSeh := Cardinal(@test);
asm
lea eax, eu
mov fs:[0],eax
mov ds:[0],eax //This will cause an AV exception
end;
end.
Теперь вы видите, что исключение обрабатывается правильно:
---------------------------
Debugger Fault Notification
---------------------------
Project C:\Users\Serg\Documents\RAD Studio\Projects\Project13.exe faulted with
message: 'access violation at 0x004050f5: write of address 0x00000000'. Process
Stopped. Use Step or Run to continue.
---------------------------
но не обработчиком исключений. Вероятно, OS игнорирует записи регистрации исключений, которые не основаны на стеках (ОС легко справится, поскольку она знает минимальные и максимальные значения стека)
Ответ 3
Для первого кода TSeh
находится в глобальной секции DATA исполняемого файла, тогда как второй код хранит его в стеке.
Это ИМХО, где разница. Структура _EXCEPTION_REGISTRATION_RECORD должна, вероятно, находиться в стеке. Не знаю, почему, честно (какой-то низкоуровневый трюк SS?).
Чтобы поднять исключение, лучше попробовать что-то вроде деления на ноль или доступ к абсолютному адресу nil:
PInteger(nil)^ := 0; // will always raise an exception
asm
xor eax,eax
mov [eax],eax // will always raise an exception
end;
О том, как перехватывать исключения в Delphi, посмотрите в этой статье. Фактически, Delphi добавляет некоторый пользовательский слой поверх SEH поверх Windows.
Также обратите внимание, что изменения обработки исключений в режиме Win64. Стоит прочитать при переходе к Delphi XE2.