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

#include "base/macros.h"
#include "base/profiler/win32_stack_frame_unwinder.h"

#include <windows.h>
#include <utility>

namespace base {

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

const HMODULE ModuleHandleTraits::kNonNullModuleForTesting =
    reinterpret_cast<HMODULE>(static_cast<uintptr_t>(-1));

// static
bool ModuleHandleTraits::CloseHandle(HMODULE handle) {
  if (handle == kNonNullModuleForTesting)
    return true;

  return ::FreeLibrary(handle) != 0;
}

// static
bool ModuleHandleTraits::IsHandleValid(HMODULE handle) {
  return handle != nullptr;
}

// static
HMODULE ModuleHandleTraits::NullHandle() {
  return nullptr;
}

namespace {

// Implements the UnwindFunctions interface for the corresponding Win32
// functions.
class Win32UnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
public:
  Win32UnwindFunctions();
  ~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;

  ScopedModuleHandle GetModuleForProgramCounter(
      DWORD64 program_counter) override;

private:
  DISALLOW_COPY_AND_ASSIGN(Win32UnwindFunctions);
};

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

PRUNTIME_FUNCTION Win32UnwindFunctions::LookupFunctionEntry(
    DWORD64 program_counter,
    PDWORD64 image_base) {
#ifdef _WIN64
  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) {
#ifdef _WIN64
  void* handler_data;
  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
}

ScopedModuleHandle Win32UnwindFunctions::GetModuleForProgramCounter(
    DWORD64 program_counter) {
  HMODULE module_handle = nullptr;
  // GetModuleHandleEx() increments the module reference count, which is then
  // managed and ultimately decremented by ScopedModuleHandle.
  if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                           reinterpret_cast<LPCTSTR>(program_counter),
                           &module_handle)) {
    const DWORD error = ::GetLastError();
    DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error));
  }
  return ScopedModuleHandle(module_handle);
}

}  // namespace

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

Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {}
Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}

Win32StackFrameUnwinder::Win32StackFrameUnwinder()
    : Win32StackFrameUnwinder(make_scoped_ptr(new Win32UnwindFunctions)) {
}

Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}

bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
                                        ScopedModuleHandle* module) {
#ifdef _WIN64
  ScopedModuleHandle frame_module =
      unwind_functions_->GetModuleForProgramCounter(context->Rip);
  if (!frame_module.IsValid()) {
    // There's no loaded module containing the instruction pointer. This can be
    // due to executing code that is not in a module. In particular,
    // runtime-generated code associated with third-party injected DLLs
    // typically is not in a module. It can also be due to the the module having
    // been unloaded since we recorded the stack.  In the latter case the
    // function unwind information was part of the unloaded module, so it's not
    // possible to unwind further.
    //
    // If a module was found, it's still theoretically possible for the detected
    // module module to be different than the one that was loaded when the stack
    // was copied (i.e. if the module was unloaded and a different module loaded
    // in overlapping memory). This likely would cause a crash, but has not been
    // observed in practice.
    return false;
  }

  ULONG64 image_base;
  // Try to look up unwind metadata for the current function.
  PRUNTIME_FUNCTION runtime_function =
      unwind_functions_->LookupFunctionEntry(context->Rip, &image_base);

  if (runtime_function) {
    unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function,
                                     context);
    at_top_frame_ = false;
  } else {
    if (at_top_frame_) {
      at_top_frame_ = false;

      // This is a leaf function (i.e. a function that neither calls a function,
      // nor allocates any stack space itself) so the return address is at RSP.
      context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp);
      context->Rsp += 8;
    } else {
      // 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.
      //
      // Note that dodgy third-party generated code that otherwise would enter
      // this path should be caught by the module check above, since the code
      // typically is located outside of a module.
      return false;
    }
  }

  module->Set(frame_module.Take());
  return true;
#else
  NOTREACHED();
  return false;
#endif
}

Win32StackFrameUnwinder::Win32StackFrameUnwinder(
    scoped_ptr<UnwindFunctions> unwind_functions)
    : at_top_frame_(true),
      unwind_functions_(std::move(unwind_functions)) {}

}  // namespace base
