/*
 * Support functions for Wine exception handling
 *
 * Copyright (c) 1999, 2010 Alexandre Julliard
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"
#include <stdarg.h>
#include "winternl.h"
#include "wine/exception.h"

#if defined(__x86_64__) && defined(__ASM_GLOBAL_FUNC)
extern void __wine_unwind_trampoline(void);
/* we need an extra call to make sure the stack is correctly aligned */
__ASM_GLOBAL_FUNC( __wine_unwind_trampoline, "callq *%rax" );
#endif

/* wrapper for RtlUnwind since it clobbers registers on Windows */
void __wine_rtl_unwind( EXCEPTION_REGISTRATION_RECORD* frame, EXCEPTION_RECORD *record,
                        void (*target)(void) )
{
#if defined(__GNUC__) && defined(__i386__)
    int dummy1, dummy2, dummy3, dummy4;
    __asm__ __volatile__("pushl %%ebp\n\t"
                         "pushl %%ebx\n\t"
                         "pushl $0\n\t"
                         "pushl %3\n\t"
                         "pushl %2\n\t"
                         "pushl %1\n\t"
                         "call *%0\n\t"
                         "popl %%ebx\n\t"
                         "popl %%ebp"
                         : "=a" (dummy1), "=S" (dummy2), "=D" (dummy3), "=c" (dummy4)
                         : "0" (RtlUnwind), "1" (frame), "2" (target), "3" (record)
                         : "edx", "memory" );
#elif defined(__x86_64__) && defined(__ASM_GLOBAL_FUNC)
    RtlUnwind( frame, __wine_unwind_trampoline, record, target );
#else
    RtlUnwind( frame, target, record, 0 );
#endif
    for (;;) target();
}

static void DECLSPEC_NORETURN unwind_target(void)
{
    __WINE_FRAME *wine_frame = (__WINE_FRAME *)__wine_get_frame();
    __wine_pop_frame( &wine_frame->frame );
    siglongjmp( wine_frame->jmp, 1 );
}

static void DECLSPEC_NORETURN unwind_frame( EXCEPTION_RECORD *record,
                                            EXCEPTION_REGISTRATION_RECORD *frame )
{
    __WINE_FRAME *wine_frame = (__WINE_FRAME *)frame;

    /* hack to make GetExceptionCode() work in handler */
    wine_frame->ExceptionCode   = record->ExceptionCode;
    wine_frame->ExceptionRecord = wine_frame;

    __wine_rtl_unwind( frame, record, unwind_target );
}

DWORD __wine_exception_handler( EXCEPTION_RECORD *record,
                                EXCEPTION_REGISTRATION_RECORD *frame,
                                CONTEXT *context,
                                EXCEPTION_REGISTRATION_RECORD **pdispatcher )
{
    __WINE_FRAME *wine_frame = (__WINE_FRAME *)frame;
    EXCEPTION_POINTERS ptrs;

    if (record->ExceptionFlags & (EH_UNWINDING | EH_EXIT_UNWIND | EH_NESTED_CALL))
        return ExceptionContinueSearch;

    ptrs.ExceptionRecord = record;
    ptrs.ContextRecord = context;
    switch(wine_frame->u.filter( &ptrs ))
    {
    case EXCEPTION_CONTINUE_SEARCH:
        return ExceptionContinueSearch;
    case EXCEPTION_CONTINUE_EXECUTION:
        return ExceptionContinueExecution;
    case EXCEPTION_EXECUTE_HANDLER:
        break;
    }
    unwind_frame( record, frame );
}

DWORD __wine_exception_handler_page_fault( EXCEPTION_RECORD *record,
                                           EXCEPTION_REGISTRATION_RECORD *frame,
                                           CONTEXT *context,
                                           EXCEPTION_REGISTRATION_RECORD **pdispatcher )
{
    if (record->ExceptionFlags & (EH_UNWINDING | EH_EXIT_UNWIND | EH_NESTED_CALL))
        return ExceptionContinueSearch;
    if (record->ExceptionCode != STATUS_ACCESS_VIOLATION)
        return ExceptionContinueSearch;
    unwind_frame( record, frame );
}

DWORD __wine_exception_handler_all( EXCEPTION_RECORD *record,
                                    EXCEPTION_REGISTRATION_RECORD *frame,
                                    CONTEXT *context,
                                    EXCEPTION_REGISTRATION_RECORD **pdispatcher )
{
    if (record->ExceptionFlags & (EH_UNWINDING | EH_EXIT_UNWIND | EH_NESTED_CALL))
        return ExceptionContinueSearch;
    unwind_frame( record, frame );
}

DWORD __wine_finally_handler( EXCEPTION_RECORD *record,
                              EXCEPTION_REGISTRATION_RECORD *frame,
                              CONTEXT *context,
                              EXCEPTION_REGISTRATION_RECORD **pdispatcher )
{
    if (record->ExceptionFlags & (EH_UNWINDING | EH_EXIT_UNWIND))
    {
        __WINE_FRAME *wine_frame = (__WINE_FRAME *)frame;
        wine_frame->u.finally_func( FALSE );
    }
    return ExceptionContinueSearch;
}