Структурированный обработчик исключений и 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.

Другие вопросы по тегам