Структурированный обработчик исключений и 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.
Что я делаю неправильно? В чем разница между первым кодом и вторым?
3 ответа
Windows требует, чтобы все кадры стека находились внутри стека, выделенного системой. Это также требует, чтобы кадры стека были в последовательном порядке в стеке. Кроме того, для обработки исключений требуется, чтобы все "записи исключений" находились в стеке, и чтобы они последовательно соединялись через стековую память.
Я понял это / прочитал это где-то несколько лет назад, когда писал библиотеку микропотоков (http://www.eternallines.com/microthreads).
Вы не можете использовать test
Процедура как функция обратного вызова исключения, потому что функция обратного вызова исключения имеет другой прототип. Прочитайте статью Мэтта Пьетрека, IMO лучший источник информации о Win32 SEH.
Обновить
Для дальнейших исследований я бы порекомендовал следующие изменения в коде, чтобы сделать проблему немного более чистой:
function test: Integer;
begin
WriteLn('Hello from seh');
Result:= 0;
end;
(потому что исключение обратного вызова должно возвращать целочисленное значение в 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.
---------------------------
но не вашим обработчиком исключений. Вероятно, ОС игнорирует записи регистрации исключений, которые не основаны на стеке (ОС может легко сделать это, потому что она знает минимальные и максимальные значения стека)
Для первого кода TSeh
находится в глобальном разделе DATA исполняемого файла, тогда как 2-й код хранит его в стеке.
Это ИМХО, где разница. Структура _EXCEPTION_REGISTRATION_RECORD, вероятно, должна быть в стеке. Не знаю почему, если честно (какой-то трюк с регистрацией низкого уровня в SS?).
Чтобы вызвать исключение, вам лучше попробовать что-то вроде деления на ноль или доступа к нулевому абсолютному адресу:
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.