You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows 10下Delphi生成器首次异常处理耗时过长问题排查

Delphi自定义生成器首次异常处理耗时过长问题(Windows 10特有)

问题背景

我基于Delphi实现了一个生成器,核心逻辑参考俄文技术文章的代码,用于实现非抢占式多任务——每个运行的任务拥有独立栈,通过自定义机制在任务栈与主程序栈之间切换。

我对原代码做了小幅修改:

  • 将执行逻辑改为类方法形式
  • 支持任务内部抛出并处理异常

功能整体正常,但在Windows 10系统中出现特殊现象:首个任务创建后,其内部触发的第一次异常从抛出到处理耗时数百毫秒;同一任务的后续异常、以及后续新建任务的所有异常,处理耗时均为0毫秒。禁用杀毒软件、关闭DEP(数据执行保护)均无法改善此现象,且该问题仅出现在Windows 10,WinXP与Win7下表现完全正常。

复现代码

生成器单元代码(DCa.Generators.pas)

unit DCa.Generators;

interface

type
  TNativeUIntArray = array [0 .. 65535] of Cardinal;
  PNativeUIntArray = ^TNativeUIntArray;
  TGenerator = class;
  TGeneratorProcedure = procedure( Generator: TGenerator ) of object; stdcall;

  TGenerator = class
    SP: Cardinal; 
    Stack: PNativeUIntArray; 
    constructor Create( Proc: TGeneratorProcedure; StackSize: Integer );
    destructor Destroy; override;
    procedure Enter;
    class procedure Return;
  end;

implementation

uses Windows, SysUtils;

function GetFlags: cardinal;
asm
  pushfd
  pop EAX
end;

constructor TGenerator.Create( Proc: TGeneratorProcedure; StackSize: Integer );
var StackLen: Integer;
begin
  Stack := VirtualAlloc( nil, StackSize, MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE );
  StackLen := StackSize div SizeOf( Cardinal );
  // MoveNext
  Stack^[StackLen - 1] := 0; // _EXCEPTION_REGISTRATION.handler
  Stack^[StackLen - 2] := 0; // _EXCEPTION_REGISTRATION.prev
  Stack^[StackLen - 3] := Cardinal(Self);      // Self used by "Return" procedure
  Stack^[StackLen - 4] := Cardinal(Self);      // Self for "Proc" (parameter Generator: TGenerator)
  Stack^[StackLen - 5] := Cardinal(TMethod(Proc).Data);   // Pointer to the object that the "Proc" procedure belongs to
  Stack^[StackLen - 6] := Cardinal(@TGenerator.Return);   // When the procedure "Proc" completes, go to the "Return" procedure
  Stack^[StackLen - 7] := Cardinal(TMethod(Proc).Code);   // @Proc (EIP/RIP)
  Stack^[StackLen - 8] := GetFlags;            // save flags (EFLAGS/RFLAGS)
  Stack^[StackLen - 9] := Cardinal(@Stack^[StackLen - 2]); // FS[0] - need for exception handling
  Stack^[StackLen - 10] := Cardinal(@Stack^[StackLen]);    // FS[4]
  Stack^[StackLen - 11] := Cardinal(@Stack^[0]);           // FS[8]
  SP := Cardinal( @Stack^[StackLen - 15] ); // additional place (4 items) to push/pop EBX,EBP,ESI,EDI
  Enter(); // switch main program stack -> new created stack for "Proc" procedure
end;

destructor TGenerator.Destroy;
begin
  VirtualFree( Stack, 0, MEM_RELEASE );
end;

procedure SwitchContext;
asm
  // ECX should be =@TGenerator
  pushfd               // Save EFLAGS
  mov  eax, fs:[$00]   // Save Top ExceptionHandler
  push eax
  mov  eax, fs:[$04]   // Save Stack Top and Bottom 
  push eax
  mov  eax, fs:[$08]
  push eax
  push EBX             // Регистры общего назначения
  push EBP             // Регистры EAX,ECX,EDX сохранять
  push ESI             // не нужно, поскольку их не запрещено
  push EDI             // изменять
// Change stack pointer
  xchg ESP, dword ptr [ECX].TGenerator.&SP
// load registers from new stack
  pop EDI
  pop ESI
  pop EBP
  pop EBX
  pop eax
  mov fs:[$08], eax
  pop eax
  mov fs:[$04], eax
  pop eax
  mov fs:[$00], eax
  popfd               // load EFLAGS
end;

procedure TGenerator.Enter;
asm
  mov ECX, EAX       // ECX=Self (@TGenerator)
  jmp SwitchContext  // switch
end;

class procedure TGenerator.Return;
asm
  pop ECX            // ECX=Self (@TGenerator)
  jmp SwitchContext                        
end;

end.

主程序代码(Project1.dpr)

program Project1;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils, DCa.Generators;

type TDummy = class
  class procedure Test(Generator: TGenerator); stdcall;
end;

class procedure TDummy.Test(Generator: TGenerator);
var
  i: Integer;
  tick: Cardinal;
begin
  for i := 1 to 3 do
    try
      Tick := GetTickCount();
      raise Exception.Create('MyExc');
    except
      Writeln('except delay = ', GetTickCount() - tick);
    end;
end;

var i: Integer;
begin
  for i := 1 to 3 do begin
    TGenerator.Create(TDummy.Test, 65536);
    readln;
  end;
end.

典型输出

except delay = 328
except delay = 0
except delay = 0

except delay = 0
except delay = 0
except delay = 0

except delay = 0
except delay = 0
except delay = 0

疑问

请问该问题的原因是什么?如何缩短首次异常处理的耗时?(已跟踪代码,发现ntdll.NtRaiseException调用耗时过长,但原因不明。)


内容的提问来源于stack exchange,提问作者Dmitro25

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 19:32:33