Delphi use of FS segment: structured exception handling
Posted by jpluimers on 2021/03/16
A while ago, I had to trace through a lot of code in the CPU pane to track down some memory allocation related issues.
I wondered what the use of the FS segment was about, so via [Archive.is] delphi what is fs segment used for – Google Search, I found that it is related to Win32 Structured Exception handling and therefore not limited to Delphi, through these links:
- [WayBack] Guard page exceptions in Delphi? – Stack Overflow
- [WayBack] Debugging Session: Exception Handlers
- [WayBack] Debugging Session: Exception Frame Types
- [WayBack] cbloom rants: 11-09-11 – Weird shite about Exceptions in Windows
- WayBack: A Crash Course on theDepths of Win32 Structured Exception Handling, MSJ January 1997
- Backups of that:
- [WayBack] A Crash Course on TheDepths of Win32 Structured Exception Handling, MSJ January 1997 | Callback (Computer Programming) | Pointer (Computer Programming)
- [WayBack] A Crash Course on theDepths of Win32 Structured Exception Handling, MSJ January 1997
- Which got me to:
- [WayBack] BYTE* / Resources
- [WayBack] Under the Hood: New Vectored Exception Handling in Windows XP
- [WayBack] Exception Handling: Jeremy Gordon – win32 exception handling
- [WayBack] Under the Hood, MSJ April 1997: Pietrek – stack walking with symbolic traces #1
- [WayBack] Under the Hood, MSJ May 1997: Pietrek – stack walking with symbolic traces #2
- [WayBack] Under the Hood: Improved Error Reporting with DBGHELP 5.1 APIs: Pietrek – stack walking with symbolic traces #3
- [WayBack] Under the Hood, MSJ October 1997: Matt Pietrek – exception generator
- [WayBack] BYTE* / Undocumented 32-bit PEB and TEB Structures
- [WayBack] BYTE* / Undocumented 64-bit PEB and TEB Structures
- [WayBack] Under The Hood — MSJ, May 1996: Matt Pietrek – Thread Information Block (TIB)
- [WayBack] BYTE* / Resources
- Which got me to:
- Backups of that:
- [WayBack] Win32 Exception Handling for assembler programmers by Jeremy Gordon
- [WayBack] windows – what is fs:[register+value] meaning in assembly? – Stack Overflow
- [WayBack] windows – Where ES/GS/FS are pointing to? – Reverse Engineering Stack Exchange
- [WayBack] IsBadXxxPtr should really be called CrashProgramRandomly – The Old New Thing
- Microsoft-specific exception handling mechanisms – Wikipedia: Structured Exception Handling
- Exception handling syntax – Wikipedia: Microsoft specific
- Win32 Thread Information Block – Wikipedia
- Protected mode – Wikipedia
- x86 memory segmentation – Wikipedia: 80386 protected mode
A few disassembly parts to show how the Delphi Win32 compiler uses this for try finally blocks and try except blocks is below. Note that often, there are implicit try finally blocks when having managed method parameters or local variables.
–jeroen
Disassembly examples
Try finally block examples.
Example 1
function TMonitor.Wait(ALock: PMonitor; Timeout: Cardinal): Boolean; var RecursionCount: Integer; WaitingThread: TWaitingThread; begin WaitingThread.Next := nil; WaitingThread.Thread := ALock.CheckOwningThread; // This event should probably be cached someplace. // Probably not on the instance since this is a per-thread-per-instance resource WaitingThread.WaitEvent := MonitorSupport.NewWaitObject; try // Save the current recursion count for later RecursionCount := ALock.FRecursionCount; // Add the current thread to the waiting queue QueueWaiter(WaitingThread); // Set it back to almost released so the next Exit call actually drops the lock ALock.FRecursionCount := 1; // Now complete the exit and signal any waiters ALock.Exit; // Get in line for someone to do a Pulse or PulseAll Result := MonitorSupport.WaitOrSignalObject(nil, WaitingThread.WaitEvent, Timeout) = WAIT_OBJECT_0; // Got to get the lock back and block waiting for it. ALock.Enter(INFINITE); // Remove any dangling waiters from the list RemoveWaiter(WaitingThread); // Lets restore the recursion to return to the proper nesting level ALock.FRecursionCount := RecursionCount; finally MonitorSupport.FreeWaitObject(WaitingThread.WaitEvent); end; end; -------------------------------------------------------------------------------- System.pas.18512: function TMonitor.Wait(ALock: PMonitor; Timeout: Cardinal): Boolean; System.pas.18513: var System.pas.18514: RecursionCount: Integer; System.pas.18515: WaitingThread: TWaitingThread; System.pas.18516: begin 0040A7F4 55 push ebp 0040A7F5 8BEC mov ebp,esp 0040A7F7 83C4E8 add esp,-$18 0040A7FA 53 push ebx 0040A7FB 56 push esi 0040A7FC 57 push edi 0040A7FD 894DFC mov [ebp-$04],ecx 0040A800 8BDA mov ebx,edx 0040A802 8BF0 mov esi,eax System.pas.18517: WaitingThread.Next := nil; 0040A804 33C0 xor eax,eax 0040A806 8945E8 mov [ebp-$18],eax System.pas.18518: WaitingThread.Thread := ALock.CheckOwningThread; 0040A809 8BC3 mov eax,ebx 0040A80B E8B4F9FFFF call TMonitor.CheckOwningThread 0040A810 8945EC mov [ebp-$14],eax System.pas.18519: // This event should probably be cached someplace. System.pas.18520: // Probably not on the instance since this is a per-thread-per-instance resource System.pas.18521: WaitingThread.WaitEvent := MonitorSupport.NewWaitObject; 0040A813 8B3DFCE8D600 mov edi,[$00d6e8fc] 0040A819 FF5708 call dword ptr [edi+$08] 0040A81C 8945F0 mov [ebp-$10],eax System.pas.18522: try 0040A81F 33C0 xor eax,eax 0040A821 55 push ebp 0040A822 6896A84000 push $0040a896 0040A827 64FF30 push dword ptr fs:[eax] 0040A82A 648920 mov fs:[eax],esp System.pas.18524: RecursionCount := ALock.FRecursionCount; 0040A82D 8B4304 mov eax,[ebx+$04] 0040A830 8945F4 mov [ebp-$0c],eax System.pas.18526: QueueWaiter(WaitingThread); 0040A833 8D55E8 lea edx,[ebp-$18] 0040A836 8BC6 mov eax,esi 0040A838 E833FEFFFF call TMonitor.QueueWaiter System.pas.18528: ALock.FRecursionCount := 1; 0040A83D C7430401000000 mov [ebx+$04],$00000001 System.pas.18530: ALock.Exit; 0040A844 8BC3 mov eax,ebx 0040A846 E885FCFFFF call TMonitor.Exit System.pas.18532: Result := MonitorSupport.WaitOrSignalObject(nil, WaitingThread.WaitEvent, Timeout) = WAIT_OBJECT_0; 0040A84B 8B3DFCE8D600 mov edi,[$00d6e8fc] 0040A851 8B4DFC mov ecx,[ebp-$04] 0040A854 8B55F0 mov edx,[ebp-$10] 0040A857 33C0 xor eax,eax 0040A859 FF5710 call dword ptr [edi+$10] 0040A85C 85C0 test eax,eax 0040A85E 0F9445FB setz byte ptr [ebp-$05] System.pas.18534: ALock.Enter(INFINITE); 0040A862 8BC3 mov eax,ebx 0040A864 83CAFF or edx,-$01 0040A867 E8CCFAFFFF call TMonitor.Enter System.pas.18536: RemoveWaiter(WaitingThread); 0040A86C 8D55E8 lea edx,[ebp-$18] 0040A86F 8BC6 mov eax,esi 0040A871 E86AFEFFFF call TMonitor.RemoveWaiter System.pas.18538: ALock.FRecursionCount := RecursionCount; 0040A876 8B45F4 mov eax,[ebp-$0c] 0040A879 894304 mov [ebx+$04],eax 0040A87C 33C0 xor eax,eax 0040A87E 5A pop edx 0040A87F 59 pop ecx 0040A880 59 pop ecx 0040A881 648910 mov fs:[eax],edx 0040A884 689DA84000 push $0040a89d System.pas.18540: MonitorSupport.FreeWaitObject(WaitingThread.WaitEvent); 0040A889 8B1DFCE8D600 mov ebx,[$00d6e8fc] 0040A88F 8B45F0 mov eax,[ebp-$10] 0040A892 FF530C call dword ptr [ebx+$0c] 0040A895 C3 ret 0040A896 E911040000 jmp @HandleFinally 0040A89B EBEC jmp $0040a889 System.pas.18542: end; 0040A89D 0FB645FB movzx eax,[ebp-$05] 0040A8A1 5F pop edi 0040A8A2 5E pop esi 0040A8A3 5B pop ebx 0040A8A4 8BE5 mov esp,ebp 0040A8A6 5D pop ebp 0040A8A7 C3 ret
Example 2
function TMonitor.DequeueWaiter: PWaitingThread; begin FQueueLock.Enter; try Result := FWaitQueue; if (Result = nil) or (Result.Next = Result) then begin FWaitQueue := nil; System.Exit; end else begin Result := FWaitQueue.Next; FWaitQueue.Next := FWaitQueue.Next.Next; end; finally FQueueLock.Exit; end; end; -------------------------------------------------------------------------------- Two TIB exception sets: to address $0040a32a, and to edx, which contained the old one. Tear-down is convoluted, as part is right before the finally bit, part is after the finally end; and part inside the finally bit. That's how the code generator tries to optimise generated code. -------------------------------------------------------------------------------- System.pas.18134: function TMonitor.DequeueWaiter: PWaitingThread; System.pas.18135: begin 0040A2B8 55 push ebp 0040A2B9 8BEC mov ebp,esp 0040A2BB 83C4F8 add esp,-$08 0040A2BE 8945FC mov [ebp-$04],eax System.pas.18136: FQueueLock.Enter; 0040A2C1 8B45FC mov eax,[ebp-$04] 0040A2C4 83C018 add eax,$18 0040A2C7 E8A4FDFFFF call TMonitor.TSpinLock.Enter System.pas.18137: try 0040A2CC 33C9 xor ecx,ecx 0040A2CE 55 push ebp 0040A2CF 682AA34000 push $0040a32a 0040A2D4 64FF31 push dword ptr fs:[ecx] 0040A2D7 648921 mov fs:[ecx],esp System.pas.18138: Result := FWaitQueue; 0040A2DA 8B45FC mov eax,[ebp-$04] 0040A2DD 8B4014 mov eax,[eax+$14] 0040A2E0 8945F8 mov [ebp-$08],eax System.pas.18139: if (Result = nil) or (Result.Next = Result) then 0040A2E3 837DF800 cmp dword ptr [ebp-$08],$00 0040A2E7 740A jz $0040a2f3 0040A2E9 8B45F8 mov eax,[ebp-$08] 0040A2EC 8B00 mov eax,[eax] 0040A2EE 3B45F8 cmp eax,[ebp-$08] 0040A2F1 750F jnz $0040a302 System.pas.18140: begin System.pas.18141: FWaitQueue := nil; 0040A2F3 8B45FC mov eax,[ebp-$04] 0040A2F6 33D2 xor edx,edx 0040A2F8 895014 mov [eax+$14],edx System.pas.18142: System.Exit; 0040A2FB E8440C0000 call @TryFinallyExit 0040A300 EB2F jmp $0040a331 System.pas.18143: end else System.pas.18144: begin System.pas.18145: Result := FWaitQueue.Next; 0040A302 8B45FC mov eax,[ebp-$04] 0040A305 8B4014 mov eax,[eax+$14] 0040A308 8B10 mov edx,[eax] 0040A30A 8955F8 mov [ebp-$08],edx System.pas.18146: FWaitQueue.Next := FWaitQueue.Next.Next; 0040A30D 8B12 mov edx,[edx] 0040A30F 8910 mov [eax],edx 0040A311 33C0 xor eax,eax 0040A313 5A pop edx 0040A314 59 pop ecx 0040A315 59 pop ecx 0040A316 648910 mov fs:[eax],edx 0040A319 6831A34000 push $0040a331 System.pas.18147: end; System.pas.18148: finally System.pas.18149: FQueueLock.Exit; 0040A31E 8B45FC mov eax,[ebp-$04] 0040A321 83C018 add eax,$18 0040A324 E873FDFFFF call TMonitor.TSpinLock.Exit 0040A329 C3 ret 0040A32A E97D090000 jmp @HandleFinally 0040A32F EBED jmp $0040a31e System.pas.18151: end; System.pas.18151: end; 0040A331 8B45F8 mov eax,[ebp-$08] 0040A334 59 pop ecx 0040A335 59 pop ecx 0040A336 5D pop ebp 0040A337 C3 ret
Try except block example.
function _AfterConstruction(const Instance: TObject): TObject; begin try Instance.AfterConstruction; Result := Instance; {$IFDEF AUTOREFCOUNT} AtomicDecrement(Instance.FRefCount); {$ENDIF} except _BeforeDestruction(Instance, 1); raise; end; end; -------------------------------------------------------------------------------- System.pas.17836: function _AfterConstruction(const Instance: TObject): TObject; System.pas.17837: begin 00409F98 55 push ebp 00409F99 8BEC mov ebp,esp 00409F9B 51 push ecx 00409F9C 53 push ebx 00409F9D 56 push esi 00409F9E 57 push edi 00409F9F 8945FC mov [ebp-$04],eax System.pas.17838: try 00409FA2 33D2 xor edx,edx 00409FA4 55 push ebp 00409FA5 68C79F4000 push $00409fc7 00409FAA 64FF32 push dword ptr fs:[edx] 00409FAD 648922 mov fs:[edx],esp System.pas.17839: Instance.AfterConstruction; 00409FB0 8B45FC mov eax,[ebp-$04] 00409FB3 8B10 mov edx,[eax] 00409FB5 FF52E4 call dword ptr [edx-$1c] System.pas.17840: Result := Instance; 00409FB8 8B45FC mov eax,[ebp-$04] 00409FBB 648F0500000000 pop dword ptr fs:[$00000000] 00409FC2 83C408 add esp,$08 00409FC5 EB19 jmp $00409fe0 00409FC7 E92C0A0000 jmp @HandleAnyException System.pas.17841: {$IFDEF AUTOREFCOUNT} System.pas.17842: AtomicDecrement(Instance.FRefCount); System.pas.17843: {$ENDIF} System.pas.17844: except System.pas.17845: _BeforeDestruction(Instance, 1); 00409FCC B201 mov dl,$01 00409FCE 8B45FC mov eax,[ebp-$04] 00409FD1 E812000000 call @BeforeDestruction System.pas.17846: raise; 00409FD6 E8E50E0000 call @RaiseAgain 00409FDB E8340F0000 call @DoneExcept System.pas.17848: end; System.pas.17848: end; 00409FE0 5F pop edi 00409FE1 5E pop esi 00409FE2 5B pop ebx 00409FE3 59 pop ecx 00409FE4 5D pop ebp 00409FE5 C3 ret
Leave a Reply