//===-- sanitizer_unwind_win.cpp ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
/// Sanitizer unwind Windows specific functions.
//
//===----------------------------------------------------------------------===//

#include "sanitizer_platform.h"
#if SANITIZER_WINDOWS

#define WIN32_LEAN_AND_MEAN
#define NOGDI
#include <windows.h>

#include "sanitizer_dbghelp.h"  // for StackWalk64
#include "sanitizer_stacktrace.h"
#include "sanitizer_symbolizer.h"  // for InitializeDbgHelpIfNeeded

using namespace __sanitizer;

#if !SANITIZER_GO
void BufferedStackTrace::UnwindSlow(uptr pc, u32 max_depth) {
  CHECK_GE(max_depth, 2);
  // FIXME: CaptureStackBackTrace might be too slow for us.
  // FIXME: Compare with StackWalk64.
  // FIXME: Look at LLVMUnhandledExceptionFilter in Signals.inc
  size = CaptureStackBackTrace(1, Min(max_depth, kStackTraceMax),
    (void **)&trace_buffer[0], 0);
  if (size == 0)
    return;

  // Skip the RTL frames by searching for the PC in the stacktrace.
  uptr pc_location = LocatePcInTrace(pc);
  PopStackFrames(pc_location);

  // Replace the first frame with the PC because the frame in the
  // stacktrace might be incorrect.
  trace_buffer[0] = pc;
}

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wframe-larger-than="
#endif
void BufferedStackTrace::UnwindSlow(uptr pc, void *context, u32 max_depth) {
  CHECK(context);
  CHECK_GE(max_depth, 2);
  CONTEXT ctx = *(CONTEXT *)context;
  STACKFRAME64 stack_frame;
  memset(&stack_frame, 0, sizeof(stack_frame));

  InitializeDbgHelpIfNeeded();

  size = 0;
#    if SANITIZER_WINDOWS64
#      if SANITIZER_ARM64
  int machine_type = IMAGE_FILE_MACHINE_ARM64;
  stack_frame.AddrPC.Offset = ctx.Pc;
  stack_frame.AddrFrame.Offset = ctx.Fp;
  stack_frame.AddrStack.Offset = ctx.Sp;
#      else
  int machine_type = IMAGE_FILE_MACHINE_AMD64;
  stack_frame.AddrPC.Offset = ctx.Rip;
  stack_frame.AddrFrame.Offset = ctx.Rbp;
  stack_frame.AddrStack.Offset = ctx.Rsp;
#      endif
#    else
#      if SANITIZER_ARM
  int machine_type = IMAGE_FILE_MACHINE_ARM;
  stack_frame.AddrPC.Offset = ctx.Pc;
  stack_frame.AddrFrame.Offset = ctx.R11;
  stack_frame.AddrStack.Offset = ctx.Sp;
#      elif SANITIZER_MIPS32
  int machine_type = IMAGE_FILE_MACHINE_R4000;
  stack_frame.AddrPC.Offset = ctx.Fir;
  stack_frame.AddrFrame.Offset = ctx.IntS8;
  stack_frame.AddrStack.Offset = ctx.IntSp;
#      else
  int machine_type = IMAGE_FILE_MACHINE_I386;
  stack_frame.AddrPC.Offset = ctx.Eip;
  stack_frame.AddrFrame.Offset = ctx.Ebp;
  stack_frame.AddrStack.Offset = ctx.Esp;
#      endif
#    endif
  stack_frame.AddrPC.Mode = AddrModeFlat;
  stack_frame.AddrFrame.Mode = AddrModeFlat;
  stack_frame.AddrStack.Mode = AddrModeFlat;
  while (StackWalk64(machine_type, GetCurrentProcess(), GetCurrentThread(),
                     &stack_frame, &ctx, NULL, SymFunctionTableAccess64,
                     SymGetModuleBase64, NULL) &&
         size < Min(max_depth, kStackTraceMax)) {
    trace_buffer[size++] = (uptr)stack_frame.AddrPC.Offset;
  }
}
#    ifdef __clang__
#      pragma clang diagnostic pop
#    endif
#  endif  // #if !SANITIZER_GO

#endif  // SANITIZER_WINDOWS
