I'm having trouble reverse-engineering a specific Delphi Pascal .exe (old vsn., pre-1995 so probably v.3). From the system calls I understand this is probably a try..except..finally
block, but I am at a loss finding the 'normal' route through the code, and what the except
and (possibly) finally
blocks are.
The assembly looks like this:
782CFC 33 C0 xor eax, eax
782CFE 55 push ebp
782CFF 68 (782E37) push _FINALLY_A_0_782E37
782D04 64 FF 30 push dword ptr fs:[eax]
782D07 64 89 20 mov dword ptr fs:[eax], esp
_try_0_782D0A:
782D0A 8B D3 mov edx, ebx
782D0C 8B C6 mov eax, esi
782D0E E8 D1 F3 FF FF call ...unrelated...
782D13 8D 56 1C lea edx, [esi+1Ch]
.. lots of regular code here ..
.. ending with ..
782E17 8B 18 mov ebx, dword ptr [eax]
782E19 FF 53 20 call dword ptr [ebx+20h]
finally_1_782E1C:
782E1C 33 C0 xor eax, eax
782E1E 5A pop edx
782E1F 59 pop ecx
782E20 59 pop ecx
782E21 64 89 10 mov dword ptr fs:[eax], edx
782E24 68 (782E3E) push _end_1_782E3E
@block_L:
782E29 8D 45 F4 lea eax, [ebp + local_0C]
782E2C BA 02 00 00 00 mov edx, 2
782E31 E8 12 E3 F7 FF call System.@LStrArrayClr
782E36 C3 retn
_FINALLY_A_0_782E37:
782E37 E9 B4 E2 F7 FF jmp System.@HandleFinally
_FINALLY_B_0_782E3C:
782E3C EB EB jmp @block_L
; -------
_end_1_782E3E:
782E3E 5F pop edi
782E3F 5E pop esi
782E40 5B pop ebx
782E41 8B E5 mov esp, ebp
782E43 5D pop ebp
782E44 C3 retn
-- this is output from my own disassembler, but I don't think there are errors in it. The labels have been auto-named, but I still cannot follow the 'logic' (if any) from one block to the next. In particular, the bottom half, right before the function epilogue, confuses me.
Are these fragments enough to reconstruct the original try
..finally
blocks?
After reading Igor's answer: yes they are. Consider these flowcharts: left, original before special handling of try/finally blocks, right, afterwards.
In the original flowchart, I considered every jump from one basic block to another as a link, and the code flow stops at every retn
. if
(E-(F)-K) and if-else
(G-H/I-J) structures can clearly be discerned. However, pushing return addresses and the other 'tricks' of exception handling, defeat this, as can be seen by the dangling blocks N and O -- they 'enter' from nowhere --, and a separate block 'M' which comes and goes from nowhere.
At the right, I separated the initialization of the exception block from the main code (adding a new block B), and concatenated the finalize structure into one single new block (M), which ultimately jumps to an AFTER_TRY (which happened to be the last Exit block). Now it's clear that
- right after the prologue, a
try
is initiated; - all code ends up at the
finally
block M, which - then always exists the code at a single fixed point.