The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 4,262 other subscribers

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:

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 comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.