// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/profiler/win32_stack_frame_unwinder.h"

#include <windows.h>

#include <utility>

#include "base/notreached.h"
#include "build/build_config.h"

namespace base {

// Win32UnwindFunctions -------------------------------------------------------

namespace {

// Implements the UnwindFunctions interface for the corresponding Win32
// functions.
class Win32UnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
 public:
  Win32UnwindFunctions();

  Win32UnwindFunctions(const Win32UnwindFunctions&) = delete;
  Win32UnwindFunctions& operator=(const Win32UnwindFunctions&) = delete;

  ~Win32UnwindFunctions() override;

  PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
                                        PDWORD64 image_base) override;

  void VirtualUnwind(DWORD64 image_base,
                     DWORD64 program_counter,
                     PRUNTIME_FUNCTION runtime_function,
                     CONTEXT* context) override;
};

Win32UnwindFunctions::Win32UnwindFunctions() {}
Win32UnwindFunctions::~Win32UnwindFunctions() {}

PRUNTIME_FUNCTION Win32UnwindFunctions::LookupFunctionEntry(
    DWORD64 program_counter,
    PDWORD64 image_base) {
#if defined(ARCH_CPU_64_BITS)
  return ::RtlLookupFunctionEntry(program_counter, image_base, nullptr);
#else
  NOTREACHED();
  return nullptr;
#endif
}

void Win32UnwindFunctions::VirtualUnwind(DWORD64 image_base,
                                         DWORD64 program_counter,
                                         PRUNTIME_FUNCTION runtime_function,
                                         CONTEXT* context) {
#if defined(ARCH_CPU_64_BITS)
  void* handler_data = nullptr;
  ULONG64 establisher_frame;
  KNONVOLATILE_CONTEXT_POINTERS nvcontext = {};
  ::RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, program_counter,
                     runtime_function, context, &handler_data,
                     &establisher_frame, &nvcontext);
#else
  NOTREACHED();
#endif
}

}  // namespace

// Win32StackFrameUnwinder ----------------------------------------------------

Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() = default;
Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() = default;

Win32StackFrameUnwinder::Win32StackFrameUnwinder()
    : Win32StackFrameUnwinder(std::make_unique<Win32UnwindFunctions>()) {}

Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}

bool Win32StackFrameUnwinder::TryUnwind(
    bool at_top_frame,
    CONTEXT* context,
    // The module parameter, while not directly used, is still passed because it
    // represents an implicit dependency for this function. Having the Module
    // ensures that we have incremented the HMODULE reference count, which is
    // critical to ensuring that the module is not unloaded during the
    // unwinding. Otherwise the module could be unloaded between the
    // LookupFunctionEntry and VirtualUnwind calls, resulting in crashes
    // accessing unwind information from the unloaded module.
    const ModuleCache::Module* module) {
#if defined(ARCH_CPU_64_BITS)
  // Ensure we found a valid module for the program counter.
  DCHECK(module);
  ULONG64 image_base = 0;
  // Try to look up unwind metadata for the current function.
  PRUNTIME_FUNCTION runtime_function =
      unwind_functions_->LookupFunctionEntry(ContextPC(context), &image_base);

  if (runtime_function) {
    DCHECK_EQ(module->GetBaseAddress(), image_base);
    unwind_functions_->VirtualUnwind(image_base, ContextPC(context),
                                     runtime_function, context);
    return true;
  }

  if (at_top_frame) {
    // This is a leaf function (i.e. a function that neither calls a function,
    // nor allocates any stack space itself).
#if defined(ARCH_CPU_X86_64)
    // For X64, return address is at RSP.
    context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp);
    context->Rsp += 8;
#elif defined(ARCH_CPU_ARM64)
    // For leaf function on Windows ARM64, return address is at LR(X30).  Add
    // CONTEXT_UNWOUND_TO_CALL flag to avoid unwind ambiguity for tailcall on
    // ARM64, because padding after tailcall is not guaranteed.
    context->Pc = context->Lr;
    context->ContextFlags |= CONTEXT_UNWOUND_TO_CALL;
#else
#error Unsupported Windows 64-bit Arch
#endif
    return true;
  }

  // In theory we shouldn't get here, as it means we've encountered a function
  // without unwind information below the top of the stack, which is forbidden
  // by the Microsoft x64 calling convention.
  //
  // The one known case in Chrome code that executes this path occurs because
  // of BoringSSL unwind information inconsistent with the actual function
  // code. See https://crbug.com/542919.
  return false;
#else
  NOTREACHED();
  return false;
#endif
}

Win32StackFrameUnwinder::Win32StackFrameUnwinder(
    std::unique_ptr<UnwindFunctions> unwind_functions)
    : unwind_functions_(std::move(unwind_functions)) {}

}  // namespace base
