// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "snapshot/win/process_reader_win.h"

#ifdef CLIENT_STACKTRACES_ENABLED
#include <dbghelp.h>
#endif
#include <string.h>
#include <winternl.h>

#include <memory>

#include "base/numerics/safe_conversions.h"
#include "util/misc/capture_context.h"
#include "util/misc/time.h"
#include "util/win/nt_internals.h"
#include "util/win/ntstatus_logging.h"
#include "util/win/process_structs.h"
#include "util/win/scoped_handle.h"

namespace crashpad {

namespace {

// Gets a pointer to the process information structure after a given one, or
// null when iteration is complete, assuming they've been retrieved in a block
// via NtQuerySystemInformation().
template <class Traits>
process_types::SYSTEM_PROCESS_INFORMATION<Traits>* NextProcess(
    process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process) {
  ULONG offset = process->NextEntryOffset;
  if (offset == 0)
    return nullptr;
  return reinterpret_cast<process_types::SYSTEM_PROCESS_INFORMATION<Traits>*>(
      reinterpret_cast<uint8_t*>(process) + offset);
}

//! \brief Retrieves the SYSTEM_PROCESS_INFORMATION for a given process.
//!
//! The returned pointer points into the memory block stored by \a buffer.
//! Ownership of \a buffer is transferred to the caller.
//!
//! \return Pointer to the process' data, or nullptr if it was not found or on
//!     error. On error, a message will be logged.
template <class Traits>
process_types::SYSTEM_PROCESS_INFORMATION<Traits>* GetProcessInformation(
    HANDLE process_handle,
    std::unique_ptr<uint8_t[]>* buffer) {
  ULONG buffer_size = 16384;
  ULONG actual_size;
  buffer->reset(new uint8_t[buffer_size]);
  NTSTATUS status;
  // This must be in retry loop, as we're racing with process creation on the
  // system to find a buffer large enough to hold all process information.
  for (int tries = 0; tries < 20; ++tries) {
    status = crashpad::NtQuerySystemInformation(
        SystemProcessInformation,
        reinterpret_cast<void*>(buffer->get()),
        buffer_size,
        &actual_size);
    if (status == STATUS_BUFFER_TOO_SMALL ||
        status == STATUS_INFO_LENGTH_MISMATCH) {
      DCHECK_GT(actual_size, buffer_size);

      // Add a little extra to try to avoid an additional loop iteration. We're
      // racing with system-wide process creation between here and the next call
      // to NtQuerySystemInformation().
      buffer_size = actual_size + 4096;

      // Free the old buffer before attempting to allocate a new one.
      buffer->reset();

      buffer->reset(new uint8_t[buffer_size]);
    } else {
      break;
    }
  }

  if (!NT_SUCCESS(status)) {
    NTSTATUS_LOG(ERROR, status) << "NtQuerySystemInformation";
    return nullptr;
  }

  DCHECK_LE(actual_size, buffer_size);

  process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process =
      reinterpret_cast<process_types::SYSTEM_PROCESS_INFORMATION<Traits>*>(
          buffer->get());
  DWORD process_id = GetProcessId(process_handle);
  for (;;) {
    if (process->UniqueProcessId == process_id)
      return process;
    process = NextProcess(process);
    if (!process)
      break;
  }

  LOG(ERROR) << "process " << process_id << " not found";
  return nullptr;
}

template <class Traits>
HANDLE OpenThread(
    const process_types::SYSTEM_THREAD_INFORMATION<Traits>& thread_info) {
  HANDLE handle;
  ACCESS_MASK query_access =
      THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION;
  OBJECT_ATTRIBUTES object_attributes;
  InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr);
  NTSTATUS status = crashpad::NtOpenThread(
      &handle, query_access, &object_attributes, &thread_info.ClientId);
  if (!NT_SUCCESS(status)) {
    NTSTATUS_LOG(ERROR, status) << "NtOpenThread";
    return nullptr;
  }
  return handle;
}

#ifdef CLIENT_STACKTRACES_ENABLED
void DoStackWalk(ProcessReaderWin::Thread* thread,
                 HANDLE process,
                 HANDLE thread_handle,
                 bool is_64_reading_32) {
  if (is_64_reading_32) {
    // TODO: we dont support it right away, maybe in the future
    return;
  }

  STACKFRAME64 stack_frame;
  memset(&stack_frame, 0, sizeof(stack_frame));

  stack_frame.AddrPC.Mode = AddrModeFlat;
  stack_frame.AddrFrame.Mode = AddrModeFlat;
  stack_frame.AddrStack.Mode = AddrModeFlat;

  int machine_type = IMAGE_FILE_MACHINE_I386;
  LPVOID ctx = NULL;
#if defined(ARCH_CPU_X86)
  const CONTEXT* ctx_ = &thread->context.native;
  stack_frame.AddrPC.Offset = ctx_->Eip;
  stack_frame.AddrFrame.Offset = ctx_->Ebp;
  stack_frame.AddrStack.Offset = ctx_->Esp;
  ctx = (LPVOID)ctx_;
#elif defined(ARCH_CPU_X86_64)
  // if (!is_64_reading_32) {
  machine_type = IMAGE_FILE_MACHINE_AMD64;

  const CONTEXT* ctx_ = &thread->context.native;
  stack_frame.AddrPC.Offset = ctx_->Rip;
  stack_frame.AddrFrame.Offset = ctx_->Rbp;
  stack_frame.AddrStack.Offset = ctx_->Rsp;
  ctx = (LPVOID)ctx_;
  // } else {
  //   const WOW64_CONTEXT* ctx_ = &thread->context.wow64;
  //   stack_frame.AddrPC.Offset = ctx_->Eip;
  //   stack_frame.AddrFrame.Offset = ctx_->Ebp;
  //   stack_frame.AddrStack.Offset = ctx_->Esp;
  //   ctx = (LPVOID)ctx_;
  // }

// TODO: we dont support this right away, maybe in the future
//#elif defined(ARCH_CPU_ARM64)
//  machine_type = IMAGE_FILE_MACHINE_ARM64;
#else
#error Unsupported Windows Arch
#endif  // ARCH_CPU_X86

  char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME];
  PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;

  pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  pSymbol->MaxNameLen = MAX_SYM_NAME;

  while (StackWalk64(machine_type,
                     process,
                     thread_handle,
                     &stack_frame,
                     ctx,
                     NULL,
                     SymFunctionTableAccess64,
                     SymGetModuleBase64,
                     NULL)) {
    uint64_t addr = stack_frame.AddrPC.Offset;
    std::string sym("");
    if (SymFromAddr(process, addr, 0, pSymbol)) {
      sym = std::string(pSymbol->Name);
    }
    FrameSnapshot frame(addr, sym);
    thread->frames.push_back(frame);
  }
}
#endif

// It's necessary to suspend the thread to grab CONTEXT. SuspendThread has a
// side-effect of returning the SuspendCount of the thread on success, so we
// fill out these two pieces of semi-unrelated data in the same function.
template <class Traits>
bool FillThreadContextAndSuspendCount(HANDLE process,
                                      HANDLE thread_handle,
                                      ProcessReaderWin::Thread* thread,
                                      ProcessSuspensionState suspension_state,
                                      bool is_64_reading_32) {
#ifndef CLIENT_STACKTRACES_ENABLED
  (void)process;
#endif

  // Don't suspend the thread if it's this thread. This is really only for test
  // binaries, as we won't be walking ourselves, in general.
  bool is_current_thread =
      thread->id ==
      reinterpret_cast<process_types::TEB<Traits>*>(NtCurrentTeb())
          ->ClientId.UniqueThread;

  if (is_current_thread) {
    DCHECK(suspension_state == ProcessSuspensionState::kRunning);
    thread->suspend_count = 0;
    DCHECK(!is_64_reading_32);
    CaptureContext(&thread->context.native);

#ifdef CLIENT_STACKTRACES_ENABLED
    DoStackWalk(thread, process, thread_handle, is_64_reading_32);
#endif
  } else {
    DWORD previous_suspend_count = SuspendThread(thread_handle);
    if (previous_suspend_count == static_cast<DWORD>(-1)) {
      PLOG(ERROR) << "SuspendThread";
      return false;
    }
    if (previous_suspend_count <= 0 &&
        suspension_state == ProcessSuspensionState::kSuspended) {
      LOG(WARNING) << "Thread " << thread->id
                   << " should be suspended, but previous_suspend_count is "
                   << previous_suspend_count;
      thread->suspend_count = 0;
    } else {
      thread->suspend_count =
          previous_suspend_count -
          (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0);
    }

    memset(&thread->context, 0, sizeof(thread->context));
#if defined(ARCH_CPU_32_BITS)
    const bool is_native = true;
#elif defined(ARCH_CPU_64_BITS)
    const bool is_native = !is_64_reading_32;
    if (is_64_reading_32) {
      thread->context.wow64.ContextFlags = CONTEXT_ALL;
      if (!Wow64GetThreadContext(thread_handle, &thread->context.wow64)) {
        PLOG(ERROR) << "Wow64GetThreadContext";
        return false;
      }
    }
#endif
    if (is_native) {
      thread->context.native.ContextFlags = CONTEXT_ALL;
      if (!GetThreadContext(thread_handle, &thread->context.native)) {
        PLOG(ERROR) << "GetThreadContext";
        return false;
      }
    }

#ifdef CLIENT_STACKTRACES_ENABLED
    DoStackWalk(thread, process, thread_handle, is_64_reading_32);
#endif

    if (!ResumeThread(thread_handle)) {
      PLOG(ERROR) << "ResumeThread";
      return false;
    }
  }

  return true;
}

}  // namespace

ProcessReaderWin::Thread::Thread()
    : context(),
      id(0),
      teb_address(0),
      teb_size(0),
      stack_region_address(0),
      stack_region_size(0),
      suspend_count(0),
      priority_class(0),
      priority(0) {}

ProcessReaderWin::ProcessReaderWin()
    : process_(INVALID_HANDLE_VALUE),
      process_info_(),
      process_memory_(),
      threads_(),
      modules_(),
      suspension_state_(),
      initialized_threads_(false),
      initialized_() {}

ProcessReaderWin::~ProcessReaderWin() {}

bool ProcessReaderWin::Initialize(HANDLE process,
                                  ProcessSuspensionState suspension_state) {
  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);

  process_ = process;
  suspension_state_ = suspension_state;
  if (!process_info_.Initialize(process))
    return false;
  if (!process_memory_.Initialize(process))
    return false;

  INITIALIZATION_STATE_SET_VALID(initialized_);
  return true;
}

bool ProcessReaderWin::StartTime(timeval* start_time) const {
  FILETIME creation, exit, kernel, user;
  if (!GetProcessTimes(process_, &creation, &exit, &kernel, &user)) {
    PLOG(ERROR) << "GetProcessTimes";
    return false;
  }
  *start_time = FiletimeToTimevalEpoch(creation);
  return true;
}

bool ProcessReaderWin::CPUTimes(timeval* user_time,
                                timeval* system_time) const {
  FILETIME creation, exit, kernel, user;
  if (!GetProcessTimes(process_, &creation, &exit, &kernel, &user)) {
    PLOG(ERROR) << "GetProcessTimes";
    return false;
  }
  *user_time = FiletimeToTimevalInterval(user);
  *system_time = FiletimeToTimevalInterval(kernel);
  return true;
}

const std::vector<ProcessReaderWin::Thread>& ProcessReaderWin::Threads() {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

  if (initialized_threads_)
    return threads_;

  initialized_threads_ = true;

#if defined(ARCH_CPU_64_BITS)
  ReadThreadData<process_types::internal::Traits64>(process_info_.IsWow64());
#else
  ReadThreadData<process_types::internal::Traits32>(false);
#endif

  return threads_;
}

const std::vector<ProcessInfo::Module>& ProcessReaderWin::Modules() {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

  if (!process_info_.Modules(&modules_)) {
    LOG(ERROR) << "couldn't retrieve modules";
  }

  return modules_;
}

const ProcessInfo& ProcessReaderWin::GetProcessInfo() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return process_info_;
}

void ProcessReaderWin::DecrementThreadSuspendCounts(uint64_t except_thread_id) {
  Threads();
  for (auto& thread : threads_) {
    if (thread.id != except_thread_id) {
      DCHECK_GT(thread.suspend_count, 0u);
      --thread.suspend_count;
    }
  }
}

template <class Traits>
void ProcessReaderWin::ReadThreadData(bool is_64_reading_32) {
  DCHECK(threads_.empty());

  std::unique_ptr<uint8_t[]> buffer;
  process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process_information =
      GetProcessInformation<Traits>(process_, &buffer);
  if (!process_information)
    return;

#ifdef CLIENT_STACKTRACES_ENABLED
  DWORD options = SymGetOptions();
  SymSetOptions(options | SYMOPT_UNDNAME);
  SymInitialize(process_, NULL, TRUE);
#endif

  for (unsigned long i = 0; i < process_information->NumberOfThreads; ++i) {
    const process_types::SYSTEM_THREAD_INFORMATION<Traits>& thread_info =
        process_information->Threads[i];
    ProcessReaderWin::Thread thread;
    thread.id = thread_info.ClientId.UniqueThread;

    ScopedKernelHANDLE thread_handle(OpenThread(thread_info));
    if (!thread_handle.is_valid())
      continue;

    if (!FillThreadContextAndSuspendCount<Traits>(process_,
                                                  thread_handle.get(),
                                                  &thread,
                                                  suspension_state_,
                                                  is_64_reading_32)) {
      continue;
    }

    // TODO(scottmg): I believe we could reverse engineer the PriorityClass from
    // the Priority, BasePriority, and
    // https://msdn.microsoft.com/library/ms685100.aspx. MinidumpThreadWriter
    // doesn't handle it yet in any case, so investigate both of those at the
    // same time if it's useful.
    thread.priority_class = NORMAL_PRIORITY_CLASS;

    thread.priority = thread_info.Priority;

    process_types::THREAD_BASIC_INFORMATION<Traits> thread_basic_info;
    NTSTATUS status = crashpad::NtQueryInformationThread(
        thread_handle.get(),
        static_cast<THREADINFOCLASS>(ThreadBasicInformation),
        &thread_basic_info,
        sizeof(thread_basic_info),
        nullptr);
    if (!NT_SUCCESS(status)) {
      NTSTATUS_LOG(ERROR, status) << "NtQueryInformationThread";
      continue;
    }

    // Read the TIB (Thread Information Block) which is the first element of the
    // TEB, for its stack fields.
    process_types::NT_TIB<Traits> tib;
    thread.teb_address = thread_basic_info.TebBaseAddress;
    thread.teb_size = sizeof(process_types::TEB<Traits>);
    if (process_memory_.Read(thread.teb_address, sizeof(tib), &tib)) {
      WinVMAddress base = 0;
      WinVMAddress limit = 0;
      // If we're reading a WOW64 process, then the TIB we just retrieved is the
      // x64 one. The first word of the x64 TIB points at the x86 TIB. See
      // https://msdn.microsoft.com/library/dn424783.aspx.
      if (is_64_reading_32) {
        process_types::NT_TIB<process_types::internal::Traits32> tib32;
        thread.teb_address = tib.Wow64Teb;
        thread.teb_size =
            sizeof(process_types::TEB<process_types::internal::Traits32>);
        if (process_memory_.Read(thread.teb_address, sizeof(tib32), &tib32)) {
          base = tib32.StackBase;
          limit = tib32.StackLimit;
        }
      } else {
        base = tib.StackBase;
        limit = tib.StackLimit;
      }

      // Note, "backwards" because of direction of stack growth.
      thread.stack_region_address = limit;
      if (limit > base) {
        LOG(ERROR) << "invalid stack range: " << base << " - " << limit;
        thread.stack_region_size = 0;
      } else {
        thread.stack_region_size = base - limit;
      }
    }
    threads_.push_back(thread);
  }
}

}  // namespace crashpad
