// Copyright 2014 The Crashpad Authors
//
// 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 "minidump/minidump_context_writer.h"

#include <windows.h>
#include <dbghelp.h>
#include <stdint.h>
#include <string.h>

#include "base/compiler_specific.h"
#include "base/logging.h"
#include "build/build_config.h"
#include "snapshot/cpu_context.h"
#include "util/file/file_writer.h"
#include "util/stdlib/aligned_allocator.h"

namespace crashpad {

namespace {

// Sanity-check complex structures to ensure interoperability.
static_assert(sizeof(MinidumpContextX86) == 716, "MinidumpContextX86 size");
static_assert(sizeof(MinidumpContextAMD64) == 1232,
              "MinidumpContextAMD64 size");

// These structures can also be checked against definitions in the Windows SDK.
#if BUILDFLAG(IS_WIN)
#if defined(ARCH_CPU_X86_FAMILY)
static_assert(sizeof(MinidumpContextX86) == sizeof(WOW64_CONTEXT),
              "WOW64_CONTEXT size");
#if defined(ARCH_CPU_X86)
static_assert(sizeof(MinidumpContextX86) == sizeof(CONTEXT), "CONTEXT size");
#elif defined(ARCH_CPU_X86_64)
static_assert(sizeof(MinidumpContextAMD64) == sizeof(CONTEXT), "CONTEXT size");
#endif
#endif  // ARCH_CPU_X86_FAMILY
#endif  // BUILDFLAG(IS_WIN)

}  // namespace

MinidumpContextWriter::~MinidumpContextWriter() {
}

// static
std::unique_ptr<MinidumpContextWriter>
MinidumpContextWriter::CreateFromSnapshot(const CPUContext* context_snapshot) {
  std::unique_ptr<MinidumpContextWriter> context;

  switch (context_snapshot->architecture) {
    case kCPUArchitectureX86: {
      MinidumpContextX86Writer* context_x86 = new MinidumpContextX86Writer();
      context.reset(context_x86);
      context_x86->InitializeFromSnapshot(context_snapshot->x86);
      break;
    }

    case kCPUArchitectureX86_64: {
      MinidumpContextAMD64Writer* context_amd64 =
          new MinidumpContextAMD64Writer();
      context.reset(context_amd64);
      context_amd64->InitializeFromSnapshot(context_snapshot->x86_64);
      break;
    }

    case kCPUArchitectureARM: {
      context = std::make_unique<MinidumpContextARMWriter>();
      reinterpret_cast<MinidumpContextARMWriter*>(context.get())
          ->InitializeFromSnapshot(context_snapshot->arm);
      break;
    }

    case kCPUArchitectureARM64: {
      context = std::make_unique<MinidumpContextARM64Writer>();
      reinterpret_cast<MinidumpContextARM64Writer*>(context.get())
          ->InitializeFromSnapshot(context_snapshot->arm64);
      break;
    }

    case kCPUArchitectureMIPSEL: {
      context = std::make_unique<MinidumpContextMIPSWriter>();
      reinterpret_cast<MinidumpContextMIPSWriter*>(context.get())
          ->InitializeFromSnapshot(context_snapshot->mipsel);
      break;
    }

    case kCPUArchitectureMIPS64EL: {
      context = std::make_unique<MinidumpContextMIPS64Writer>();
      reinterpret_cast<MinidumpContextMIPS64Writer*>(context.get())
          ->InitializeFromSnapshot(context_snapshot->mips64);
      break;
    }

    default: {
      LOG(ERROR) << "unknown context architecture "
                 << context_snapshot->architecture;
      break;
    }
  }

  return context;
}

size_t MinidumpContextWriter::SizeOfObject() {
  DCHECK_GE(state(), kStateFrozen);

  return ContextSize();
}

size_t MinidumpContextWriter::FreezeAndGetSizeOfObject() {
  Freeze();
  return SizeOfObject();
}

MinidumpContextX86Writer::MinidumpContextX86Writer()
    : MinidumpContextWriter(), context_() {
  context_.context_flags = kMinidumpContextX86;
}

MinidumpContextX86Writer::~MinidumpContextX86Writer() {
}

void MinidumpContextX86Writer::InitializeFromSnapshot(
    const CPUContextX86* context_snapshot) {
  DCHECK_EQ(state(), kStateMutable);
  DCHECK_EQ(context_.context_flags, kMinidumpContextX86);

  context_.context_flags = kMinidumpContextX86All;

  context_.dr0 = context_snapshot->dr0;
  context_.dr1 = context_snapshot->dr1;
  context_.dr2 = context_snapshot->dr2;
  context_.dr3 = context_snapshot->dr3;
  context_.dr6 = context_snapshot->dr6;
  context_.dr7 = context_snapshot->dr7;

  // The contents of context_.fsave effectively alias everything in
  // context_.fxsave that’s related to x87 FPU state. context_.fsave doesn’t
  // carry state specific to SSE (or later), such as mxcsr and the xmm
  // registers.
  CPUContextX86::FxsaveToFsave(context_snapshot->fxsave, &context_.fsave);

  context_.gs = context_snapshot->gs;
  context_.fs = context_snapshot->fs;
  context_.es = context_snapshot->es;
  context_.ds = context_snapshot->ds;
  context_.edi = context_snapshot->edi;
  context_.esi = context_snapshot->esi;
  context_.ebx = context_snapshot->ebx;
  context_.edx = context_snapshot->edx;
  context_.ecx = context_snapshot->ecx;
  context_.eax = context_snapshot->eax;
  context_.ebp = context_snapshot->ebp;
  context_.eip = context_snapshot->eip;
  context_.cs = context_snapshot->cs;
  context_.eflags = context_snapshot->eflags;
  context_.esp = context_snapshot->esp;
  context_.ss = context_snapshot->ss;

  // This is effectively a memcpy() of a big structure.
  context_.fxsave = context_snapshot->fxsave;
}

bool MinidumpContextX86Writer::WriteObject(FileWriterInterface* file_writer) {
  DCHECK_EQ(state(), kStateWritable);

  return file_writer->Write(&context_, sizeof(context_));
}

size_t MinidumpContextX86Writer::ContextSize() const {
  DCHECK_GE(state(), kStateFrozen);

  return sizeof(context_);
}

static_assert(alignof(MinidumpContextAMD64) >= 16,
              "MinidumpContextAMD64 alignment");
static_assert(alignof(MinidumpContextAMD64Writer) >=
                  alignof(MinidumpContextAMD64),
              "MinidumpContextAMD64Writer alignment");

MinidumpContextAMD64Writer::MinidumpContextAMD64Writer()
    : MinidumpContextWriter(), context_() {
  context_.context_flags = kMinidumpContextAMD64;
}

MinidumpContextAMD64Writer::~MinidumpContextAMD64Writer() {
}

// static
void* MinidumpContextAMD64Writer::operator new(size_t size) {
  // MinidumpContextAMD64 requests an alignment of 16, which can be larger than
  // what standard new provides. This may trigger MSVC warning C4316. As a
  // workaround to this language deficiency, provide a custom allocation
  // function to allocate a block meeting the alignment requirement.
  return AlignedAllocate(alignof(MinidumpContextAMD64Writer), size);
}

// static
void MinidumpContextAMD64Writer::operator delete(void* pointer) {
  return AlignedFree(pointer);
}

void MinidumpContextAMD64Writer::InitializeFromSnapshot(
    const CPUContextX86_64* context_snapshot) {
  DCHECK_EQ(state(), kStateMutable);
  DCHECK_EQ(context_.context_flags, kMinidumpContextAMD64);

  if (context_snapshot->xstate.enabled_features != 0) {
    // Extended context.
    context_.context_flags =
        kMinidumpContextAMD64All | kMinidumpContextAMD64Xstate;
  } else {
    // Fixed size context - no xsave components.
    context_.context_flags = kMinidumpContextAMD64All;
  }

  context_.mx_csr = context_snapshot->fxsave.mxcsr;
  context_.cs = context_snapshot->cs;
  context_.fs = context_snapshot->fs;
  context_.gs = context_snapshot->gs;
  // The top 32 bits of rflags are reserved/unused.
  context_.eflags = static_cast<uint32_t>(context_snapshot->rflags);
  context_.dr0 = context_snapshot->dr0;
  context_.dr1 = context_snapshot->dr1;
  context_.dr2 = context_snapshot->dr2;
  context_.dr3 = context_snapshot->dr3;
  context_.dr6 = context_snapshot->dr6;
  context_.dr7 = context_snapshot->dr7;
  context_.rax = context_snapshot->rax;
  context_.rcx = context_snapshot->rcx;
  context_.rdx = context_snapshot->rdx;
  context_.rbx = context_snapshot->rbx;
  context_.rsp = context_snapshot->rsp;
  context_.rbp = context_snapshot->rbp;
  context_.rsi = context_snapshot->rsi;
  context_.rdi = context_snapshot->rdi;
  context_.r8 = context_snapshot->r8;
  context_.r9 = context_snapshot->r9;
  context_.r10 = context_snapshot->r10;
  context_.r11 = context_snapshot->r11;
  context_.r12 = context_snapshot->r12;
  context_.r13 = context_snapshot->r13;
  context_.r14 = context_snapshot->r14;
  context_.r15 = context_snapshot->r15;
  context_.rip = context_snapshot->rip;

  // This is effectively a memcpy() of a big structure.
  context_.fxsave = context_snapshot->fxsave;

  // If XSave features are being recorded store in xsave_entries in xcomp_bv
  // order. We will not see features we do not support as we provide flags
  // to the OS when first obtaining a snapshot.
  if (context_snapshot->xstate.enabled_features & XSTATE_MASK_CET_U) {
    auto cet_u = std::make_unique<MinidumpXSaveAMD64CetU>();
    cet_u->InitializeFromSnapshot(context_snapshot);
    xsave_entries_.push_back(std::move(cet_u));
  }
}

size_t MinidumpContextAMD64Writer::Alignment() {
  DCHECK_GE(state(), kStateFrozen);

  // Match the alignment of MinidumpContextAMD64.
  return 16;
}

bool MinidumpContextAMD64Writer::WriteObject(FileWriterInterface* file_writer) {
  DCHECK_EQ(state(), kStateWritable);
  // Note: all sizes here come from our constants, not from untrustworthy data.
  std::vector<unsigned char> data(ContextSize());
  unsigned char* const buf = data.data();

  // CONTEXT always comes first.
  DCHECK_LE(sizeof(context_), data.size());
  memcpy(buf, &context_, sizeof(context_));

  if (xsave_entries_.size() > 0) {
    MinidumpContextExHeader context_ex = {{0, 0}, {0, 0}, {0, 0}};
    MinidumpXSaveAreaHeader xsave_header = {0, 0, {}};

    // CONTEXT_EX goes directly after the CONTEXT. |offset| is relative to
    // &CONTEXT_EX.
    context_ex.all.offset = -static_cast<int32_t>(sizeof(context_));
    context_ex.all.size = static_cast<uint32_t>(ContextSize());
    context_ex.legacy.offset = context_ex.all.offset;
    context_ex.legacy.size = sizeof(context_);
    // Then... there is a gap.
    //
    // In the compacted format the XSave area header goes just before
    // the first xsave entry.  It has a total size given by the header
    // + (padded) sizes of all the entries.
    context_ex.xstate.offset = static_cast<int32_t>(
        kMinidumpAMD64XSaveOffset - sizeof(MinidumpXSaveAreaHeader) -
        sizeof(context_));
    context_ex.xstate.size =
        static_cast<uint32_t>(sizeof(MinidumpXSaveAreaHeader) + ContextSize() -
                              kMinidumpAMD64XSaveOffset);

    // Store CONTEXT_EX now it is complete.
    DCHECK_LE(sizeof(context_) + sizeof(context_ex), data.size());
    memcpy(&buf[sizeof(context_)], &context_ex, sizeof(context_ex));

    // Calculate flags for xsave header & write entries (they will be
    // *after* the xsave header).
    size_t cursor = kMinidumpAMD64XSaveOffset;
    for (auto const& entry : xsave_entries_) {
      xsave_header.mask |= 1ull << entry->XCompBVBit();
      DCHECK_LE(cursor + entry->Size(), data.size());
      entry->Copy(&buf[cursor]);
      cursor += entry->Size();
    }

    xsave_header.compaction_mask =
        xsave_header.mask | XSTATE_COMPACTION_ENABLE_MASK;

    // Store xsave header at its calculated offset. It is before the entries
    // above, but we need to add the |mask| bits before writing it.
    DCHECK_LE(
        context_ex.xstate.offset + sizeof(context_) + sizeof(xsave_header),
        data.size());
    memcpy(&buf[context_ex.xstate.offset + sizeof(context_)],
           &xsave_header,
           sizeof(xsave_header));
  }

  if (!file_writer->Write(data.data(), data.size()))
    return false;

  return true;
}

size_t MinidumpContextAMD64Writer::ContextSize() const {
  DCHECK_GE(state(), kStateFrozen);
  if (xsave_entries_.size() == 0) {
    return sizeof(context_);
  } else {
    DCHECK_EQ(context_.context_flags,
              kMinidumpContextAMD64All | kMinidumpContextAMD64Xstate);
    DCHECK(xsave_entries_.size() != 0);
    size_t size = kMinidumpAMD64XSaveOffset;
    for (auto& entry : xsave_entries_) {
      size += entry->Size();
    }
    return size;
  }
}

bool MinidumpXSaveAMD64CetU::InitializeFromSnapshot(
    const CPUContextX86_64* context_snapshot) {
  DCHECK_EQ(context_snapshot->xstate.cet_u.cetmsr, 1ull);
  cet_u_.cetmsr = context_snapshot->xstate.cet_u.cetmsr;
  cet_u_.ssp = context_snapshot->xstate.cet_u.ssp;
  return true;
}

bool MinidumpXSaveAMD64CetU::Copy(void* dst) const {
  memcpy(dst, &cet_u_, sizeof(cet_u_));
  return true;
}

MinidumpContextARMWriter::MinidumpContextARMWriter()
    : MinidumpContextWriter(), context_() {
  context_.context_flags = kMinidumpContextARM;
}

MinidumpContextARMWriter::~MinidumpContextARMWriter() = default;

void MinidumpContextARMWriter::InitializeFromSnapshot(
    const CPUContextARM* context_snapshot) {
  DCHECK_EQ(state(), kStateMutable);
  DCHECK_EQ(context_.context_flags, kMinidumpContextARM);

  context_.context_flags = kMinidumpContextARMAll;

  static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs),
                "GPRS size mismatch");
  memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs));
  context_.fp = context_snapshot->fp;
  context_.ip = context_snapshot->ip;
  context_.sp = context_snapshot->sp;
  context_.lr = context_snapshot->lr;
  context_.pc = context_snapshot->pc;
  context_.cpsr = context_snapshot->cpsr;

  context_.fpscr = context_snapshot->vfp_regs.fpscr;
  static_assert(sizeof(context_.vfp) == sizeof(context_snapshot->vfp_regs.vfp),
                "VFP size mismatch");
  memcpy(context_.vfp, context_snapshot->vfp_regs.vfp, sizeof(context_.vfp));

  memset(context_.extra, 0, sizeof(context_.extra));
}

bool MinidumpContextARMWriter::WriteObject(FileWriterInterface* file_writer) {
  DCHECK_EQ(state(), kStateWritable);
  return file_writer->Write(&context_, sizeof(context_));
}

size_t MinidumpContextARMWriter::ContextSize() const {
  DCHECK_GE(state(), kStateFrozen);
  return sizeof(context_);
}

MinidumpContextARM64Writer::MinidumpContextARM64Writer()
    : MinidumpContextWriter(), context_() {
  context_.context_flags = kMinidumpContextARM64;
}

MinidumpContextARM64Writer::~MinidumpContextARM64Writer() = default;

void MinidumpContextARM64Writer::InitializeFromSnapshot(
    const CPUContextARM64* context_snapshot) {
  DCHECK_EQ(state(), kStateMutable);
  DCHECK_EQ(context_.context_flags, kMinidumpContextARM64);

  context_.context_flags = kMinidumpContextARM64Full;

  static_assert(
      sizeof(context_.regs) == sizeof(context_snapshot->regs) -
                                   2 * sizeof(context_snapshot->regs[0]),
      "GPRs size mismatch");
  memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs));
  context_.fp = context_snapshot->regs[29];
  context_.lr = context_snapshot->regs[30];
  context_.sp = context_snapshot->sp;
  context_.pc = context_snapshot->pc;
  context_.cpsr = context_snapshot->spsr;

  static_assert(sizeof(context_.fpsimd) == sizeof(context_snapshot->fpsimd),
                "FPSIMD size mismatch");
  memcpy(context_.fpsimd, context_snapshot->fpsimd, sizeof(context_.fpsimd));
  context_.fpcr = context_snapshot->fpcr;
  context_.fpsr = context_snapshot->fpsr;

  memset(context_.bcr, 0, sizeof(context_.bcr));
  memset(context_.bvr, 0, sizeof(context_.bvr));
  memset(context_.wcr, 0, sizeof(context_.wcr));
  memset(context_.wvr, 0, sizeof(context_.wvr));
}

bool MinidumpContextARM64Writer::WriteObject(FileWriterInterface* file_writer) {
  DCHECK_EQ(state(), kStateWritable);
  return file_writer->Write(&context_, sizeof(context_));
}

size_t MinidumpContextARM64Writer::ContextSize() const {
  DCHECK_GE(state(), kStateFrozen);
  return sizeof(context_);
}

MinidumpContextMIPSWriter::MinidumpContextMIPSWriter()
    : MinidumpContextWriter(), context_() {
  context_.context_flags = kMinidumpContextMIPS;
}

MinidumpContextMIPSWriter::~MinidumpContextMIPSWriter() = default;

void MinidumpContextMIPSWriter::InitializeFromSnapshot(
    const CPUContextMIPS* context_snapshot) {
  DCHECK_EQ(state(), kStateMutable);
  DCHECK_EQ(context_.context_flags, kMinidumpContextMIPS);

  context_.context_flags = kMinidumpContextMIPSAll;

  static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs),
                "GPRs size mismatch");
  memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs));
  context_.mdhi = context_snapshot->mdhi;
  context_.mdlo = context_snapshot->mdlo;
  context_.epc = context_snapshot->cp0_epc;
  context_.badvaddr = context_snapshot->cp0_badvaddr;
  context_.status = context_snapshot->cp0_status;
  context_.cause = context_snapshot->cp0_cause;

  static_assert(sizeof(context_.fpregs) == sizeof(context_snapshot->fpregs),
                "FPRs size mismatch");
  memcpy(&context_.fpregs, &context_snapshot->fpregs, sizeof(context_.fpregs));
  context_.fpcsr = context_snapshot->fpcsr;
  context_.fir = context_snapshot->fir;

  for (size_t index = 0; index < 3; ++index) {
    context_.hi[index] = context_snapshot->hi[index];
    context_.lo[index] = context_snapshot->lo[index];
  }
  context_.dsp_control = context_snapshot->dsp_control;
}

bool MinidumpContextMIPSWriter::WriteObject(FileWriterInterface* file_writer) {
  DCHECK_EQ(state(), kStateWritable);
  return file_writer->Write(&context_, sizeof(context_));
}

size_t MinidumpContextMIPSWriter::ContextSize() const {
  DCHECK_GE(state(), kStateFrozen);
  return sizeof(context_);
}

MinidumpContextMIPS64Writer::MinidumpContextMIPS64Writer()
    : MinidumpContextWriter(), context_() {
  context_.context_flags = kMinidumpContextMIPS64;
}

MinidumpContextMIPS64Writer::~MinidumpContextMIPS64Writer() = default;

void MinidumpContextMIPS64Writer::InitializeFromSnapshot(
    const CPUContextMIPS64* context_snapshot) {
  DCHECK_EQ(state(), kStateMutable);
  DCHECK_EQ(context_.context_flags, kMinidumpContextMIPS64);

  context_.context_flags = kMinidumpContextMIPS64All;

  static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs),
                "GPRs size mismatch");
  memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs));
  context_.mdhi = context_snapshot->mdhi;
  context_.mdlo = context_snapshot->mdlo;
  context_.epc = context_snapshot->cp0_epc;
  context_.badvaddr = context_snapshot->cp0_badvaddr;
  context_.status = context_snapshot->cp0_status;
  context_.cause = context_snapshot->cp0_cause;

  static_assert(sizeof(context_.fpregs) == sizeof(context_snapshot->fpregs),
                "FPRs size mismatch");
  memcpy(context_.fpregs.dregs,
         context_snapshot->fpregs.dregs,
         sizeof(context_.fpregs.dregs));
  context_.fpcsr = context_snapshot->fpcsr;
  context_.fir = context_snapshot->fir;

  for (size_t index = 0; index < 3; ++index) {
    context_.hi[index] = context_snapshot->hi[index];
    context_.lo[index] = context_snapshot->lo[index];
  }
  context_.dsp_control = context_snapshot->dsp_control;
}

bool MinidumpContextMIPS64Writer::WriteObject(
    FileWriterInterface* file_writer) {
  DCHECK_EQ(state(), kStateWritable);
  return file_writer->Write(&context_, sizeof(context_));
}

size_t MinidumpContextMIPS64Writer::ContextSize() const {
  DCHECK_GE(state(), kStateFrozen);
  return sizeof(context_);
}

}  // namespace crashpad
