// Copyright 2018 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/module_cache.h"

#include <objbase.h>
#include <psapi.h>

#include <string>

#include "base/debug/alias.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/pe_image.h"
#include "base/win/scoped_handle.h"
#include "base/win/win_util.h"

namespace base {

namespace {

// Gets the unique build ID and the corresponding debug path for a module.
// Windows build IDs are created by a concatenation of a GUID and AGE fields
// found in the headers of a module. The GUID is stored in the first 16 bytes
// and the AGE is stored in the last 4 bytes. Returns the empty string if the
// function fails to get the build ID. The debug path (pdb file) can be found
// in the PE file and is the build time path where the debug file was produced.
//
// Example:
// dumpbin chrome.exe /headers | find "Format:"
//   ... Format: RSDS, {16B2A428-1DED-442E-9A36-FCE8CBD29726}, 10, ...
//
// The resulting buildID string of this instance of chrome.exe is
// "16B2A4281DED442E9A36FCE8CBD2972610".
//
// Note that the AGE field is encoded in decimal, not hex.
void GetDebugInfoForModule(HMODULE module_handle,
                           std::string* build_id,
                           FilePath* pdb_name) {
  GUID guid;
  DWORD age;
  LPCSTR pdb_file = nullptr;
  size_t pdb_file_length = 0;
  if (!win::PEImage(module_handle)
           .GetDebugId(&guid, &age, &pdb_file, &pdb_file_length)) {
    return;
  }

  FilePath::StringType pdb_filename;
  if (!UTF8ToWide(pdb_file, pdb_file_length, &pdb_filename))
    return;
  *pdb_name = FilePath(std::move(pdb_filename)).BaseName();

  auto buffer = win::WStringFromGUID(guid);
  RemoveChars(buffer, L"{}-", &buffer);
  buffer.append(NumberToWString(age));
  *build_id = WideToUTF8(buffer);
}

// Returns true if the address is in the address space accessible to
// applications and DLLs, as reported by ::GetSystemInfo.
bool IsValidUserSpaceAddress(uintptr_t address) {
  static LPVOID max_app_addr = 0;
  static LPVOID min_app_addr = 0;
  if (!max_app_addr) {
    SYSTEM_INFO sys_info;
    ::GetSystemInfo(&sys_info);
    max_app_addr = sys_info.lpMaximumApplicationAddress;
    min_app_addr = sys_info.lpMinimumApplicationAddress;
  }
  return reinterpret_cast<LPVOID>(address) >= min_app_addr &&
         reinterpret_cast<LPVOID>(address) <= max_app_addr;
}

// Traits class to adapt GenericScopedHandle for HMODULES.
class ModuleHandleTraits : public win::HandleTraits {
 public:
  using Handle = HMODULE;

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

  static bool CloseHandle(HMODULE handle) { return ::FreeLibrary(handle) != 0; }
  static bool IsHandleValid(HMODULE handle) { return handle != nullptr; }
  static HMODULE NullHandle() { return nullptr; }
};

// HMODULE is not really a handle, and has reference count semantics, so the
// standard VerifierTraits does not apply.
using ScopedModuleHandle =
    win::GenericScopedHandle<ModuleHandleTraits, win::DummyVerifierTraits>;

class WindowsModule : public ModuleCache::Module {
 public:
  WindowsModule(ScopedModuleHandle module_handle,
                const MODULEINFO module_info,
                const std::string& id,
                const FilePath& debug_basename)
      : module_handle_(std::move(module_handle)),
        module_info_(module_info),
        id_(id),
        debug_basename_(debug_basename) {}

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

  // ModuleCache::Module
  uintptr_t GetBaseAddress() const override {
    return reinterpret_cast<uintptr_t>(module_info_.lpBaseOfDll);
  }

  std::string GetId() const override { return id_; }
  FilePath GetDebugBasename() const override { return debug_basename_; }
  size_t GetSize() const override { return module_info_.SizeOfImage; }
  bool IsNative() const override { return true; }

 private:
  ScopedModuleHandle module_handle_;
  const MODULEINFO module_info_;
  std::string id_;
  FilePath debug_basename_;
};

ScopedModuleHandle GetModuleHandleForAddress(uintptr_t address) {
  // Record the address in crash dumps to help understand the source of
  // GetModuleHandleEx crashes on Windows 11 observed in
  // https://crbug.com/1297776.
  debug::Alias(&address);
  if (!IsValidUserSpaceAddress(address))
    return ScopedModuleHandle(nullptr);

  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>(address),
                           &module_handle)) {
    const DWORD error = ::GetLastError();
    DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error));
  }
  return ScopedModuleHandle(module_handle);
}

std::unique_ptr<ModuleCache::Module> CreateModuleForHandle(
    ScopedModuleHandle module_handle) {
  FilePath pdb_name;
  std::string build_id;
  GetDebugInfoForModule(module_handle.get(), &build_id, &pdb_name);

  MODULEINFO module_info;
  if (!::GetModuleInformation(GetCurrentProcessHandle(), module_handle.get(),
                              &module_info, sizeof(module_info))) {
    return nullptr;
  }

  return std::make_unique<WindowsModule>(std::move(module_handle), module_info,
                                         build_id, pdb_name);
}

}  // namespace

// static
std::unique_ptr<const ModuleCache::Module> ModuleCache::CreateModuleForAddress(
    uintptr_t address) {
  ScopedModuleHandle module_handle = GetModuleHandleForAddress(address);
  if (!module_handle.is_valid())
    return nullptr;
  return CreateModuleForHandle(std::move(module_handle));
}

}  // namespace base
