/* $Id: seh.s 23790 2006-08-29 19:14:18Z greatlrd $
 *
 * COPYRIGHT:         See COPYING in the top level directory
 * PROJECT:           ReactOS MSVCRT Runtime Library
 * PURPOSE:           Runtime library exception support for IA-32
 * FILE:              lib/msvcrt/except/seh.s
 * PROGRAMER:         Casper S. Hornstrup (chorns@users.sourceforge.net)
 * NOTES:             This file is shared with ntoskrnl/rtl/i386/seh.s.
 *                    Please keep them in sync.
 */

#define ExceptionContinueExecution	0
#define ExceptionContinueSearch		1
#define ExceptionNestedException	2
#define ExceptionCollidedUnwind		3

#define EXCEPTION_NONCONTINUABLE	0x01
#define EXCEPTION_UNWINDING			0x02
#define EXCEPTION_EXIT_UNWIND		0x04
#define EXCEPTION_STACK_INVALID		0x08
#define EXCEPTION_NESTED_CALL		0x10
#define EXCEPTION_TARGET_UNWIND		0x20
#define EXCEPTION_COLLIDED_UNWIND	0x40

#define EXCEPTION_UNWIND_MODE \
(  EXCEPTION_UNWINDING \
 | EXCEPTION_EXIT_UNWIND \
 | EXCEPTION_TARGET_UNWIND \
 | EXCEPTION_COLLIDED_UNWIND)

#define EREC_CODE		0x00
#define EREC_FLAGS		0x04
#define EREC_RECORD		0x08
#define EREC_ADDRESS	0x0C
#define EREC_NUMPARAMS	0x10
#define EREC_INFO		0x14

#define TRYLEVEL_NONE    -1
#define TRYLEVEL_INVALID -2

#define ER_STANDARDESP	-0x08
#define ER_EPOINTERS	-0x04
#define ER_PREVFRAME	0x00
#define ER_HANDLER		0x04
#define ER_SCOPETABLE	0x08
#define ER_TRYLEVEL		0x0C
#define ER_EBP			0x10

#define ST_TRYLEVEL		0x00
#define ST_FILTER		0x04
#define ST_HANDLER		0x08

#define CONTEXT_EDI		0x9C
#define CONTEXT_EBX		0xA4
#define CONTEXT_EIP		0xB8

.globl __local_unwind2
.globl __except_handler3
.globl __EH_prolog

// EAX = value to print
_do_debug:
	pushal
	pushl	%eax
	call	_MsvcrtDebug@4
	popal
	ret

#define LU2_TRYLEVEL	0x08
#define LU2_REGFRAME	0x04

//
// void
// _local_unwind2(PEXCEPTION_REGISTRATION RegistrationFrame,
//			      LONG TryLevel)
//
// Parameters:
//   [EDX+08h] - PEXCEPTION_REGISTRATION RegistrationFrame
//   [EDX+04h] - LONG TryLevel
// Registers:
//   EBP - EBP of call frame we are unwinding
// Returns:
//   Nothing
// Notes:
//   Run all termination handlers for a call frame from the current
//   try-level up to (but not including) the given stop try-level.
__local_unwind2:
    // Setup our call frame so we can access parameters using EDX
    //pushl    %ebp
    movl     %esp, %edx

    // FIXME: Setup an EXCEPTION_REGISTRATION entry to protect the
    // unwinding in case something goes wrong

.lu2_next_scope:

    // Keep a pointer to the exception registration in EBX
    movl     LU2_REGFRAME(%edx), %ebx

    // If we have reached the end of the chain or we're asked to stop here
    // by the caller then exit
    test     %ebx, %ebx
    je       .lu2_done

    movl     ER_TRYLEVEL(%ebx), %eax
    cmpl     $-1, %eax
    je       .lu2_done

    cmpl     LU2_TRYLEVEL(%edx), %eax
    je       .lu2_done

    // Keep a pointer to the scopetable in ESI
    movl     ER_SCOPETABLE(%ebx), %esi

    // Compute the offset of the entry in the scopetable that describes
    // the scope that is to be unwound. Put the offset in EDI.
    movl	ST_TRYLEVEL(%esi), %edi
    lea     (%edi, %edi, 2), %edi
    shll    $2, %edi
    addl    %esi, %edi

    // If this is not a termination handler then skip it
    cmpl     $0, ST_FILTER(%edi)
    jne      .lu2_next_scope

    // Save the previous try-level in the exception registration structure
    movl     ST_TRYLEVEL(%edi), %eax
    movl     %eax, ER_TRYLEVEL(%ebx)

    // Fetch the address of the termination handler
    movl     ST_HANDLER(%edi), %eax

    // Termination handlers may trash all registers so save the
    // important ones and then call the handler
    pushl    %edx
    call 	 *%eax

	// Get our base pointer back
    popl     %edx

    jmp      .lu2_next_scope

.lu2_done:

    // FIXME: Tear down the EXCEPTION_REGISTRATION entry setup to protect
    // the unwinding

	//movl	%esi, %esp
    //popl	%ebp
    ret

#define EH3_DISPCONTEXT	0x14
#define EH3_CONTEXT		0x10
#define EH3_REGFRAME	0x0C
#define EH3_ERECORD		0x08

// Parameters:
//   [ESP+14h] - PVOID DispatcherContext
//   [ESP+10h] - PCONTEXT Context
//   [ESP+0Ch] - PEXCEPTION_REGISTRATION RegistrationFrame
//   [ESP+08h] - PEXCEPTION_RECORD ExceptionRecord
// Registers:
//   Unknown
// Returns:
//   EXCEPTION_DISPOSITION - How this handler handled the exception
// Notes:
//   Try to find an exception handler that will handle the exception.
//   Traverse the entries in the scopetable that is associated with the
//   exception registration passed as a parameter to this function.
//   If an exception handler that will handle the exception is found, it
//   is called and this function never returns
__except_handler3:
    // Setup our call frame so we can access parameters using EBP
    pushl    %ebp				// Standard ESP in frame (considered part of EXCEPTION_REGISTRATION)
    movl     %esp, %ebp

    // Don't trust the direction flag to be cleared
    cld

    // Either we're called to handle an exception or we're called to unwind    
    movl	EH3_ERECORD(%ebp), %eax
    testl	$EXCEPTION_UNWIND_MODE, EREC_FLAGS(%eax)
    jnz		.eh3_unwind

    // Keep a pointer to the exception registration in EBX
    movl     EH3_REGFRAME(%ebp), %ebx

    // Build an EXCEPTION_POINTERS structure on the stack and store it's
    // address in the EXCEPTION_REGISTRATION structure
    movl     EH3_CONTEXT(%esp), %eax
    pushl    %ebx						// Registration frame
    pushl    %eax						// Context
    movl     %esp, ER_EPOINTERS(%ebx)	// Pointer to EXCEPTION_REGISTRATION on the stack

    // Keep current try-level in EDI
    movl     ER_TRYLEVEL(%ebx), %edi

    // Keep a pointer to the scopetable in ESI
    movl     ER_SCOPETABLE(%ebx), %esi

.eh3_next_scope:

    // If we have reached the end of the chain then exit
    cmpl     $-1, %edi
    je       .eh3_search

    // Compute the offset of the entry in the scopetable and store
    // the absolute address in EAX
    lea     (%edi, %edi, 2), %eax
    shll    $2, %eax
    addl    %esi, %eax

    // Fetch the address of the filter routine
    movl     ST_FILTER(%eax), %eax

    // If this is a termination handler then skip it
    cmpl     $0, %eax
    je       .eh3_continue

    // Filter routines may trash all registers so save the important
    // ones before restoring the call frame ebp and calling the handler
    pushl	%ebp
    pushl	%edi				// Stop try-level
    lea		ER_EBP(%ebx), %ebp
    call	*%eax
    popl	%edi				// Stop try-level
    popl	%ebp

    // Reload EBX with registration frame address
    movl	EH3_REGFRAME(%ebp), %ebx

    // Be more flexible here by checking if the return value is less than
    // zero, equal to zero, or larger than zero instead of the defined
    // values:
    //   -1 (EXCEPTION_CONTINUE_EXECUTION)
    //    0 (EXCEPTION_CONTINUE_SEARCH)
    //   +1 (EXCEPTION_EXECUTE_HANDLER)
    orl      %eax, %eax
    jz       .eh3_continue
    js       .eh3_dismiss

    // Filter returned: EXCEPTION_EXECUTE_HANDLER

    // Ask the OS to perform global unwinding.
    pushl	%edi		// Save stop try-level
    pushl	%ebx		// Save registration frame address
    pushl	%ebx		// Registration frame address
    call	__global_unwind2
    popl	%eax		// Remove parameter to __global_unwind2
    popl	%ebx		// Restore registration frame address
    popl	%edi		// Restore stop try-level

    // Change the context structure so _except_finish is called in the
    // correct context since we return ExceptionContinueExecution.
    movl     EH3_CONTEXT(%ebp), %eax
    
    movl     %edi, CONTEXT_EDI(%eax)		// Stop try-level
    movl     %ebx, CONTEXT_EBX(%eax)		// Registration frame address
    movl     $_except_finish, CONTEXT_EIP(%eax)

    movl     $ExceptionContinueExecution, %eax
    jmp      .eh3_return

    // Filter returned: EXCEPTION_CONTINUE_SEARCH
.eh3_continue:

    // Reload ESI because the filter routine may have trashed it
    movl     ER_SCOPETABLE(%ebx), %esi

    // Go one try-level closer to the top
    lea      (%edi, %edi, 2), %edi
    shll     $2, %edi
    addl     %esi, %edi
    movl     ST_TRYLEVEL(%edi), %edi

    jmp      .eh3_next_scope

    // Filter returned: EXCEPTION_CONTINUE_EXECUTION
    // Continue execution like nothing happened
.eh3_dismiss:
    movl     $ExceptionContinueExecution, %eax
    jmp      .eh3_return

    // Tell the OS to search for another handler that will handle the exception
.eh3_search:

    movl     $ExceptionContinueSearch, %eax
    jmp      .eh3_return

    // Perform local unwinding
.eh3_unwind:

    testl    $EXCEPTION_TARGET_UNWIND, EREC_FLAGS(%eax)
    jnz      .eh3_return

	// Save some important registers
    pushl	%ebp

    lea		 ER_EBP(%ebx), %ebp
    pushl    $-1
    pushl    %ebx
    call     __local_unwind2
    addl     $8, %esp

	// Restore some important registers
    popl     %ebp

    movl     $ExceptionContinueSearch, %eax

    // Get me out of here
.eh3_return:

	movl	%ebp, %esp
    popl	%ebp
    ret

// Parameters:
//   None
// Registers:
//   EBX - Pointer to exception registration structure
//   EDI - Stop try-level
// Returns:
//   -
// Notes:
//   -
_except_finish:

    // Setup EBP for the exception handler. By doing this the exception
    // handler can access local variables as normal
    lea		ER_EBP(%ebx), %ebp

	// Save some important registers
    pushl	%ebp
    pushl	%ebx
    pushl	%edi

    // Stop try-level
    pushl	%edi

    // Pointer to exception registration structure
    pushl    %ebx
    call     __local_unwind2
    addl     $8, %esp

	// Restore some important registers
    popl     %edi
    popl     %ebx
    popl     %ebp

    // Keep a pointer to the scopetable in ESI
    movl     ER_SCOPETABLE(%ebx), %esi

    // Compute the offset of the entry in the scopetable and store
    // the absolute address in EDI
    lea     (%edi, %edi, 2), %edi
    shll    $2, %edi
    addl    %esi, %edi

    // Set the current try-level to the previous try-level and call
    // the exception handler
    movl     ST_TRYLEVEL(%edi), %eax
    movl     %eax, ER_TRYLEVEL(%ebx)
    movl     ST_HANDLER(%edi), %eax

    call    *%eax

    // We should never get here
    ret

// Copied from Wine.
__EH_prolog:
    pushl    $-1
    pushl    %eax
    pushl    %fs:0
    movl     %esp, %fs:0
    movl     12(%esp), %eax
    movl     %ebp, 12(%esp)
    leal     12(%esp), %ebp
    pushl    %eax
    ret
