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
相关产品推荐
相关产品推荐

