//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "ExceptionRecord.h"
#include "lldb/Host/windows/windows.h"
#include "lldb/lldb-forward.h"
#include "lldb/lldb-types.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"

#include <dbghelp.h>
#include <memory>
#include <vector>

using namespace lldb_private;

ExceptionRecord::ExceptionRecord(const EXCEPTION_RECORD &record,
                                 lldb::tid_t thread_id) {
  // Notes about the `record.ExceptionRecord` field:
  // In the past, some code tried to parse the nested exception with it, but
  // in practice, that code just causes Access Violation. I suspect
  // `ExceptionRecord` here actually points to the address space of the
  // debuggee process. However, I did not manage to find any official or
  // unofficial reference that clarifies this point. If anyone would like to
  // reimplement this, please also keep in mind to check how this behaves when
  // debugging a WOW64 process. I suspect you may have to use the explicit
  // `EXCEPTION_RECORD32` and `EXCEPTION_RECORD64` structs.
  m_code = record.ExceptionCode;
  m_continuable = (record.ExceptionFlags == 0);
  m_exception_addr = reinterpret_cast<lldb::addr_t>(record.ExceptionAddress);
  m_thread_id = thread_id;
  m_arguments.assign(record.ExceptionInformation,
                     record.ExceptionInformation + record.NumberParameters);
}

lldb_private::ExceptionRecord::ExceptionRecord(const MINIDUMP_EXCEPTION &record,
                                               lldb::tid_t thread_id)
    : m_code(record.ExceptionCode), m_continuable(record.ExceptionFlags == 0),
      m_exception_addr(static_cast<lldb::addr_t>(record.ExceptionAddress)),
      m_thread_id(thread_id),
      m_arguments(record.ExceptionInformation,
                  record.ExceptionInformation + record.NumberParameters) {}

void lldb_private::ExceptionRecord::Dump(llvm::raw_ostream &stream) const {
  // Decode additional exception information for specific exception types based
  // on
  // https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_exception_record
  // Mirrors ProcessWindows::DumpAdditionalExceptionInformation so that
  // lldb-server stop descriptions match the in-process debugger.

  const int addr_min_width = 2 + 8; // "0x" + 4 address bytes

  const llvm::ArrayRef<uint64_t> args = GetExceptionArguments();
  switch (GetExceptionValue()) {
  case EXCEPTION_ACCESS_VIOLATION: {
    if (args.size() < 2)
      break;

    stream << ": ";
    const int access_violation_code = args[0];
    const lldb::addr_t access_violation_address = args[1];
    switch (access_violation_code) {
    case 0:
      stream << "Access violation reading";
      break;
    case 1:
      stream << "Access violation writing";
      break;
    case 8:
      stream << "User-mode data execution prevention (DEP) violation at";
      break;
    default:
      stream << "Unknown access violation (code " << access_violation_code
             << ") at";
      break;
    }
    stream << " location "
           << llvm::format_hex(access_violation_address, addr_min_width);
    break;
  }
  case EXCEPTION_IN_PAGE_ERROR: {
    if (args.size() < 3)
      break;

    stream << ": ";
    const int page_load_error_code = args[0];
    const lldb::addr_t page_load_error_address = args[1];
    const DWORD underlying_code = args[2];
    switch (page_load_error_code) {
    case 0:
      stream << "In page error reading";
      break;
    case 1:
      stream << "In page error writing";
      break;
    case 8:
      stream << "User-mode data execution prevention (DEP) violation at";
      break;
    default:
      stream << "Unknown page loading error (code " << page_load_error_code
             << ") at";
      break;
    }
    stream << " location "
           << llvm::format_hex(page_load_error_address, addr_min_width)
           << " (status code " << llvm::format_hex(underlying_code, 8) << ")";
    break;
  }
  }
}
