10

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.

flowcharts

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

  1. right after the prologue, a try is initiated;
  2. all code ends up at the finally block M, which
  3. then always exists the code at a single fixed point.
10

Delphi implements try/except/finally by using Win32 Structured Exception Handlers (SEH). The basics of SEH are explained in the classic article by Matt Pietrek, so I'll skip to the details relevant to Delphi only.

1. try entry

Entry to a try block, or a block which protects automatic variables that need to be destructed on exit (such as strings) looks like the following:

xor     eax, eax
push    ebp
push    offset SEH_HANDLER
push    dword ptr fs:[eax]
mov     fs:[eax], esp

This is a typical way of setting up a SEH frame. After it's run, top of the stack will look like this:

       +-----------+
ESP+00 |    next   | <- fs:[0] points here
       +-----------+
ESP+04 |  handler  |
       +-----------+
ESP+08 | saved_ebp |
       +-----------+

The pointer to this structure will be passed to the SEH handler.

2. try exit

At the end of the try block, the SEH frame is torn down:

    xor     eax, eax
    pop     edx               ; pop 'next' into edx
    pop     ecx               ; pop handler
    pop     ecx               ; pop saved_ebp
    mov     fs:[eax], edx     ; move 'next' into fs:[0]

If there is a finally handler or automatic destructors, then it continues like this:

    push    offset AFTER_TRY  ; make it so the 'ret' will jump to AFTER_TRY
FINALLY_HANDLER:
    <destruct automatic variables created in the try block>
    <finally handler body>
    ret                       ; jumps to AFTER_TRY

Otherwise there is a simple jump:

    jmp AFTER_TRY

3. finally handler

In case the program use finally statement, or in case of the try..finally added by the compiler to guard automatic variables, the SEH handler looks like this:

SEH_HANDLER:
    jmp     _HandleFinally
    jmp     FINALLY_HANDLER

4. except handler

If the program uses an except handler to catch all exceptions, the code looks a little different:

SEH_HANDLER:
    jmp     _HandleAnyException
    <handler code>
    call    _DoneExcept

5. except on handlers

If the program uses except on... to match the exception(s) being caught, the compiler generates a table of one or more possible exception classes with corresponding handlers:

SEH_HANDLER:
    jmp     _HandleOnException
    dd <numExceptions>
    dd offset ExceptionClass1
    dd offset OnException1_handler
    dd offset ExceptionClass2
    dd offset OnException2_handler
    <...>

OnException1_handler:
    <handler code>
    call    _DoneExcept

OnException2_handler:
    <handler code>
    call    _DoneExcept

There may be some variations, but I think I covered most of it.

The source code of _HandleFinally, _HandleAnyException, _HandleOnException, _DoneExcept and a few other exceptions-related functions can be found in system.pas in the VCL sources.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.