/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "CrashHandler.h"

#include "SkTypes.h"

#include <stdlib.h>

// Disable SetupCrashHandler() unless SK_CRASH_HANDLER is defined.
#ifndef SK_CRASH_HANDLER
    void SetupCrashHandler() { }

#elif defined(GOOGLE3)
    #include "base/process_state.h"
    void SetupCrashHandler() { InstallSignalHandlers(); }

#else

    #if defined(SK_BUILD_FOR_MAC)

        // We only use local unwinding, so we can define this to select a faster implementation.
        #define UNW_LOCAL_ONLY
        #include <libunwind.h>
        #include <cxxabi.h>

        static void handler(int sig) {
            unw_context_t context;
            unw_getcontext(&context);

            unw_cursor_t cursor;
            unw_init_local(&cursor, &context);

            SkDebugf("\nSignal %d:\n", sig);
            while (unw_step(&cursor) > 0) {
                static const size_t kMax = 256;
                char mangled[kMax], demangled[kMax];
                unw_word_t offset;
                unw_get_proc_name(&cursor, mangled, kMax, &offset);

                int ok;
                size_t len = kMax;
                abi::__cxa_demangle(mangled, demangled, &len, &ok);

                SkDebugf("%s (+0x%zx)\n", ok == 0 ? demangled : mangled, (size_t)offset);
            }
            SkDebugf("\n");

            // Exit NOW.  Don't notify other threads, don't call anything registered with atexit().
            _Exit(sig);
        }

    #elif defined(SK_BUILD_FOR_UNIX)

        // We'd use libunwind here too, but it's a pain to get installed for
        // both 32 and 64 bit on bots.  Doesn't matter much: catchsegv is best anyway.
        #include <execinfo.h>

        static void handler(int sig) {
            static const int kMax = 64;
            void* stack[kMax];
            const int count = backtrace(stack, kMax);

            SkDebugf("\nSignal %d [%s]:\n", sig, strsignal(sig));
            backtrace_symbols_fd(stack, count, 2/*stderr*/);

            // Exit NOW.  Don't notify other threads, don't call anything registered with atexit().
            _Exit(sig);
        }

    #endif

    #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_UNIX)
        #include <signal.h>

        void SetupCrashHandler() {
            static const int kSignals[] = {
                SIGABRT,
                SIGBUS,
                SIGFPE,
                SIGILL,
                SIGSEGV,
            };

            for (size_t i = 0; i < sizeof(kSignals) / sizeof(kSignals[0]); i++) {
                // Register our signal handler unless something's already done so (e.g. catchsegv).
                void (*prev)(int) = signal(kSignals[i], handler);
                if (prev != SIG_DFL) {
                    signal(kSignals[i], prev);
                }
            }
        }

    #elif defined(SK_CRASH_HANDLER) && defined(SK_BUILD_FOR_WIN)

        #include <DbgHelp.h>

        static const struct {
            const char* name;
            const DWORD code;
        } kExceptions[] = {
        #define _(E) {#E, E}
            _(EXCEPTION_ACCESS_VIOLATION),
            _(EXCEPTION_BREAKPOINT),
            _(EXCEPTION_INT_DIVIDE_BY_ZERO),
            _(EXCEPTION_STACK_OVERFLOW),
            // TODO: more?
        #undef _
        };

        static LONG WINAPI handler(EXCEPTION_POINTERS* e) {
            const DWORD code = e->ExceptionRecord->ExceptionCode;
            SkDebugf("\nCaught exception %u", code);
            for (size_t i = 0; i < SK_ARRAY_COUNT(kExceptions); i++) {
                if (kExceptions[i].code == code) {
                    SkDebugf(" %s", kExceptions[i].name);
                }
            }
            SkDebugf("\n");

            // We need to run SymInitialize before doing any of the stack walking below.
            HANDLE hProcess = GetCurrentProcess();
            SymInitialize(hProcess, 0, true);

            STACKFRAME64 frame;
            sk_bzero(&frame, sizeof(frame));
            // Start frame off from the frame that triggered the exception.
            CONTEXT* c = e->ContextRecord;
            frame.AddrPC.Mode      = AddrModeFlat;
            frame.AddrStack.Mode   = AddrModeFlat;
            frame.AddrFrame.Mode   = AddrModeFlat;
        #if defined(_X86_)
            frame.AddrPC.Offset    = c->Eip;
            frame.AddrStack.Offset = c->Esp;
            frame.AddrFrame.Offset = c->Ebp;
            const DWORD machineType = IMAGE_FILE_MACHINE_I386;
        #elif defined(_AMD64_)
            frame.AddrPC.Offset    = c->Rip;
            frame.AddrStack.Offset = c->Rsp;
            frame.AddrFrame.Offset = c->Rbp;
            const DWORD machineType = IMAGE_FILE_MACHINE_AMD64;
        #endif

            while (StackWalk64(machineType,
                               GetCurrentProcess(),
                               GetCurrentThread(),
                               &frame,
                               c,
                               nullptr,
                               SymFunctionTableAccess64,
                               SymGetModuleBase64,
                               nullptr)) {
                // Buffer to store symbol name in.
                static const int kMaxNameLength = 1024;
                uint8_t buffer[sizeof(IMAGEHLP_SYMBOL64) + kMaxNameLength];
                sk_bzero(buffer, sizeof(buffer));

                // We have to place IMAGEHLP_SYMBOL64 at the front, and fill in
                // how much space it can use.
                IMAGEHLP_SYMBOL64* symbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(&buffer);
                symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
                symbol->MaxNameLength = kMaxNameLength - 1;

                // Translate the current PC into a symbol and byte offset from the symbol.
                DWORD64 offset;
                SymGetSymFromAddr64(hProcess, frame.AddrPC.Offset, &offset, symbol);

                SkDebugf("%s +%x\n", symbol->Name, offset);
            }

            // Exit NOW.  Don't notify other threads, don't call anything registered with atexit().
            _exit(1);

            // The compiler wants us to return something.  This is what we'd do
            // if we didn't _exit().
            return EXCEPTION_EXECUTE_HANDLER;
        }

        void SetupCrashHandler() {
            SetUnhandledExceptionFilter(handler);
        }

    #else  // We asked for SK_CRASH_HANDLER, but it's not Mac, Linux, or Windows.  Sorry!

        void SetupCrashHandler() { }

    #endif
#endif // SK_CRASH_HANDLER