结构化异常处理程序和Delphi

12

我想在不使用try except的情况下设置SEH
(这是为了我自己的个人知识,以更好地了解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.

我做错了什么?第一段代码和第二段代码有什么区别?


6
尝试一些奇怪的事情得到加一分。 - Warren P
1
+1 也是为了在 Delphi 中尝试一些奇怪的东西和汇编而努力。 - EMBarbosa
3个回答

6

Windows要求所有的栈帧都在系统分配的堆栈内。它还要求栈帧在堆栈上按顺序排列。此外,在异常处理中,它要求所有“异常记录”都在堆栈上,并通过堆栈内存以顺序链接。

我在写微线程库时(http://www.eternallines.com/microthreads),几年前就想出了这个解决方案/读到了这个信息。


谢谢你的回答,解决了我疑惑的问题。非常感谢。 - opc0de

4

由于异常回调函数有不同的原型,因此您不能将test过程用作异常回调函数。请阅读Matt Pietrek的文章,在我看来这是关于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.
---------------------------

但不是通过您的异常处理程序。可能操作系统会忽略非基于堆栈的异常注册记录(操作系统可以轻松地做到这一点,因为它知道最小和最大堆栈值)。


我知道我已经读过那个,但是让我困惑的是为什么第二段代码片段能够工作? - opc0de
异常处理函数是stdcall定义的,因此它可以与没有任何参数定义的过程一起使用,例如test。 - Arnaud Bouchez
@opc0de 有趣的是第一个代码片段没有处理异常。看起来操作系统不喜欢非基于堆栈的异常注册记录。 - kludg

2

对于第一段代码,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在Windows的SEH之上添加了一些自定义层。
此外,请注意Win64模式下的异常处理变化。当升级到Delphi XE2时,值得一读。

ret引起了一个异常,因为我在调试器中看不到它 ;) 我尝试按照你说的方式触发异常,但仍然得到相同的结果...所以这不是问题,谢谢你的回复。 - opc0de
你在子过程中尝试过你的代码吗?TSeh实例将会在堆栈上,就像第二段代码一样。据我所知,唯一的区别是_EXCEPTION_REGISTRATION_RECORD在第一个代码的数据块中,而在第二个代码中它在堆栈上。 - Arnaud Bouchez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接