// Copyright 2017 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 "snapshot/linux/exception_snapshot_linux.h"

#include <signal.h>

#include "base/logging.h"
#include "snapshot/linux/capture_memory_delegate_linux.h"
#include "snapshot/linux/cpu_context_linux.h"
#include "snapshot/linux/process_reader_linux.h"
#include "snapshot/linux/signal_context.h"
#include "util/linux/traits.h"
#include "util/misc/reinterpret_bytes.h"
#include "util/numeric/safe_assignment.h"
#include "util/posix/signals.h"

namespace crashpad {
namespace internal {

ExceptionSnapshotLinux::ExceptionSnapshotLinux()
    : ExceptionSnapshot(),
      context_union_(),
      context_(),
      codes_(),
      thread_id_(0),
      exception_address_(0),
      signal_number_(0),
      signal_code_(0),
      initialized_() {}

ExceptionSnapshotLinux::~ExceptionSnapshotLinux() {}

#if defined(ARCH_CPU_X86_FAMILY)

template <>
bool ExceptionSnapshotLinux::ReadContext<ContextTraits32>(
    ProcessReaderLinux* reader,
    LinuxVMAddress context_address) {
  UContext<ContextTraits32> ucontext;
  if (!reader->Memory()->Read(context_address, sizeof(ucontext), &ucontext)) {
    LOG(ERROR) << "Couldn't read ucontext";
    return false;
  }

  context_.architecture = kCPUArchitectureX86;
  context_.x86 = &context_union_.x86;

  if (!ucontext.mcontext.fpptr) {
    InitializeCPUContextX86_NoFloatingPoint(ucontext.mcontext.gprs,
                                            context_.x86);
    return true;
  }

  SignalFloatContext32 fprs;
  if (!reader->Memory()->Read(ucontext.mcontext.fpptr, sizeof(fprs), &fprs)) {
    LOG(ERROR) << "Couldn't read float context";
    return false;
  }

  if (fprs.magic == X86_FXSR_MAGIC) {
    InitializeCPUContextX86_NoFloatingPoint(ucontext.mcontext.gprs,
                                            context_.x86);
    if (!reader->Memory()->Read(
            ucontext.mcontext.fpptr + offsetof(SignalFloatContext32, fxsave),
            sizeof(CPUContextX86::Fxsave),
            &context_.x86->fxsave)) {
      LOG(ERROR) << "Couldn't read fxsave";
      return false;
    }
  } else if (fprs.magic == 0xffff) {
    InitializeCPUContextX86(ucontext.mcontext.gprs, fprs, context_.x86);
  } else {
    LOG(ERROR) << "unexpected magic 0x" << std::hex << fprs.magic;
    return false;
  }

  return true;
}

template <>
bool ExceptionSnapshotLinux::ReadContext<ContextTraits64>(
    ProcessReaderLinux* reader,
    LinuxVMAddress context_address) {
  UContext<ContextTraits64> ucontext;
  if (!reader->Memory()->Read(context_address, sizeof(ucontext), &ucontext)) {
    LOG(ERROR) << "Couldn't read ucontext";
    return false;
  }

  context_.architecture = kCPUArchitectureX86_64;
  context_.x86_64 = &context_union_.x86_64;

  if (!ucontext.mcontext.fpptr) {
    InitializeCPUContextX86_64_NoFloatingPoint(ucontext.mcontext.gprs,
                                               context_.x86_64);
    return true;
  }

  SignalFloatContext64 fprs;
  if (!reader->Memory()->Read(ucontext.mcontext.fpptr, sizeof(fprs), &fprs)) {
    LOG(ERROR) << "Couldn't read float context";
    return false;
  }

  InitializeCPUContextX86_64(ucontext.mcontext.gprs, fprs, context_.x86_64);
  return true;
}

#elif defined(ARCH_CPU_ARM_FAMILY)

template <>
bool ExceptionSnapshotLinux::ReadContext<ContextTraits32>(
    ProcessReaderLinux* reader,
    LinuxVMAddress context_address) {
  context_.architecture = kCPUArchitectureARM;
  context_.arm = &context_union_.arm;

  CPUContextARM* dest_context = context_.arm;
  const ProcessMemory* memory = reader->Memory();

  LinuxVMAddress gprs_address =
      context_address + offsetof(UContext<ContextTraits32>, mcontext32) +
      offsetof(ContextTraits32::MContext32, gprs);

  SignalThreadContext32 thread_context;
  if (!memory->Read(gprs_address, sizeof(thread_context), &thread_context)) {
    LOG(ERROR) << "Couldn't read gprs";
    return false;
  }
  InitializeCPUContextARM_NoFloatingPoint(thread_context, dest_context);

  LinuxVMAddress reserved_address =
      context_address + offsetof(UContext<ContextTraits32>, reserved);
  if ((reserved_address & 7) != 0) {
    LOG(ERROR) << "invalid alignment 0x" << std::hex << reserved_address;
    return false;
  }

  constexpr VMSize kMaxContextSpace = 1024;

  ProcessMemoryRange range;
  if (!range.Initialize(memory, false, reserved_address, kMaxContextSpace)) {
    return false;
  }

  do {
    CoprocessorContextHead head;
    if (!range.Read(reserved_address, sizeof(head), &head)) {
      LOG(ERROR) << "missing context terminator";
      return false;
    }
    reserved_address += sizeof(head);

    switch (head.magic) {
      case VFP_MAGIC:
        if (head.size != sizeof(SignalVFPContext) + sizeof(head)) {
          LOG(ERROR) << "unexpected vfp context size " << head.size;
          return false;
        }
        static_assert(
            sizeof(SignalVFPContext::vfp) == sizeof(dest_context->vfp_regs),
            "vfp context size mismatch");
        if (!range.Read(reserved_address + offsetof(SignalVFPContext, vfp),
                        sizeof(dest_context->vfp_regs),
                        &dest_context->vfp_regs)) {
          LOG(ERROR) << "Couldn't read vfp";
          return false;
        }
        dest_context->have_vfp_regs = true;
        return true;

      case CRUNCH_MAGIC:
      case IWMMXT_MAGIC:
      case DUMMY_MAGIC:
        reserved_address += head.size - sizeof(head);
        continue;

      case 0:
        return true;

      default:
        LOG(ERROR) << "invalid magic number 0x" << std::hex << head.magic;
        return false;
    }
  } while (true);
}

template <>
bool ExceptionSnapshotLinux::ReadContext<ContextTraits64>(
    ProcessReaderLinux* reader,
    LinuxVMAddress context_address) {
  context_.architecture = kCPUArchitectureARM64;
  context_.arm64 = &context_union_.arm64;

  CPUContextARM64* dest_context = context_.arm64;
  const ProcessMemory* memory = reader->Memory();

  LinuxVMAddress gprs_address =
      context_address + offsetof(UContext<ContextTraits64>, mcontext64) +
      offsetof(ContextTraits64::MContext64, gprs);

  ThreadContext::t64_t thread_context;
  if (!memory->Read(gprs_address, sizeof(thread_context), &thread_context)) {
    LOG(ERROR) << "Couldn't read gprs";
    return false;
  }
  InitializeCPUContextARM64_NoFloatingPoint(thread_context, dest_context);

  LinuxVMAddress reserved_address =
      context_address + offsetof(UContext<ContextTraits64>, reserved);
  if ((reserved_address & 15) != 0) {
    LOG(ERROR) << "invalid alignment 0x" << std::hex << reserved_address;
    return false;
  }

  constexpr VMSize kMaxContextSpace = 4096;

  ProcessMemoryRange range;
  if (!range.Initialize(memory, true, reserved_address, kMaxContextSpace)) {
    return false;
  }

  do {
    CoprocessorContextHead head;
    if (!range.Read(reserved_address, sizeof(head), &head)) {
      LOG(ERROR) << "missing context terminator";
      return false;
    }
    reserved_address += sizeof(head);

    switch (head.magic) {
      case FPSIMD_MAGIC:
        if (head.size != sizeof(SignalFPSIMDContext) + sizeof(head)) {
          LOG(ERROR) << "unexpected fpsimd context size " << head.size;
          return false;
        }
        SignalFPSIMDContext fpsimd;
        if (!range.Read(reserved_address, sizeof(fpsimd), &fpsimd)) {
          LOG(ERROR) << "Couldn't read fpsimd " << head.size;
          return false;
        }
        InitializeCPUContextARM64_OnlyFPSIMD(fpsimd, dest_context);
        return true;

      case ESR_MAGIC:
      case EXTRA_MAGIC:
        reserved_address += head.size - sizeof(head);
        continue;

      case 0:
        LOG(WARNING) << "fpsimd not found";
        return true;

      default:
        LOG(ERROR) << "invalid magic number 0x" << std::hex << head.magic;
        return false;
    }
  } while (true);
}

#elif defined(ARCH_CPU_MIPS_FAMILY)

template <typename Traits>
static bool ReadContext(ProcessReaderLinux* reader,
                        LinuxVMAddress context_address,
                        typename Traits::CPUContext* dest_context) {
  const ProcessMemory* memory = reader->Memory();

  LinuxVMAddress gregs_address = context_address +
                                 offsetof(UContext<Traits>, mcontext) +
                                 offsetof(typename Traits::MContext, gregs);

  typename Traits::SignalThreadContext thread_context;
  if (!memory->Read(gregs_address, sizeof(thread_context), &thread_context)) {
    LOG(ERROR) << "Couldn't read gregs";
    return false;
  }

  LinuxVMAddress fpregs_address = context_address +
                                  offsetof(UContext<Traits>, mcontext) +
                                  offsetof(typename Traits::MContext, fpregs);

  typename Traits::SignalFloatContext fp_context;
  if (!memory->Read(fpregs_address, sizeof(fp_context), &fp_context)) {
    LOG(ERROR) << "Couldn't read fpregs";
    return false;
  }

  InitializeCPUContextMIPS<Traits>(thread_context, fp_context, dest_context);

  return true;
}

template <>
bool ExceptionSnapshotLinux::ReadContext<ContextTraits32>(
    ProcessReaderLinux* reader,
    LinuxVMAddress context_address) {
  context_.architecture = kCPUArchitectureMIPSEL;
  context_.mipsel = &context_union_.mipsel;

  return internal::ReadContext<ContextTraits32>(
      reader, context_address, context_.mipsel);
}

template <>
bool ExceptionSnapshotLinux::ReadContext<ContextTraits64>(
    ProcessReaderLinux* reader,
    LinuxVMAddress context_address) {
  context_.architecture = kCPUArchitectureMIPS64EL;
  context_.mips64 = &context_union_.mips64;

  return internal::ReadContext<ContextTraits64>(
      reader, context_address, context_.mips64);
}

#endif  // ARCH_CPU_X86_FAMILY

bool ExceptionSnapshotLinux::Initialize(
    ProcessReaderLinux* process_reader,
    LinuxVMAddress siginfo_address,
    LinuxVMAddress context_address,
    pid_t thread_id,
    uint32_t* gather_indirectly_referenced_memory_cap) {
  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);

  thread_id_ = thread_id;
  const ProcessReaderLinux::Thread* thread = nullptr;
  for (const auto& loop_thread : process_reader->Threads()) {
    if (thread_id == loop_thread.tid) {
      thread = &loop_thread;
      break;
    }
  }
  if (!thread) {
    // This is allowed until {ProcessReaderLinux::InitializeThreads()} is
    // improved to support target threads in the same thread group.
    LOG(WARNING) << "thread ID " << thread_id << " not found in process";
  }

  if (process_reader->Is64Bit()) {
    if (!ReadContext<ContextTraits64>(process_reader, context_address) ||
        !ReadSiginfo<Traits64>(process_reader, siginfo_address)) {
      return false;
    }
  } else {
    if (!ReadContext<ContextTraits32>(process_reader, context_address) ||
        !ReadSiginfo<Traits32>(process_reader, siginfo_address)) {
      return false;
    }
  }

  CaptureMemoryDelegateLinux capture_memory_delegate(
      process_reader,
      thread,
      &extra_memory_,
      gather_indirectly_referenced_memory_cap);
  CaptureMemory::PointedToByContext(context_, &capture_memory_delegate);

  INITIALIZATION_STATE_SET_VALID(initialized_);
  return true;
}

template <typename Traits>
bool ExceptionSnapshotLinux::ReadSiginfo(ProcessReaderLinux* reader,
                                         LinuxVMAddress siginfo_address) {
  Siginfo<Traits> siginfo;
  if (!reader->Memory()->Read(siginfo_address, sizeof(siginfo), &siginfo)) {
    LOG(ERROR) << "Couldn't read siginfo";
    return false;
  }

  signal_number_ = siginfo.signo;
  signal_code_ = siginfo.code;

  uint64_t extra_code;
#define PUSH_CODE(value)                         \
  do {                                           \
    if (!ReinterpretBytes(value, &extra_code)) { \
      LOG(ERROR) << "bad code";                  \
      return false;                              \
    }                                            \
    codes_.push_back(extra_code);                \
  } while (false)

  switch (siginfo.signo) {
    case SIGILL:
    case SIGFPE:
    case SIGSEGV:
    case SIGBUS:
    case SIGTRAP:
      exception_address_ = siginfo.address;
      break;

    case SIGPOLL:  // SIGIO
      PUSH_CODE(siginfo.band);
      PUSH_CODE(siginfo.fd);
      break;

    case SIGSYS:
      exception_address_ = siginfo.call_address;
      PUSH_CODE(siginfo.syscall);
      PUSH_CODE(siginfo.arch);
      break;

    case SIGALRM:
    case SIGVTALRM:
    case SIGPROF:
      PUSH_CODE(siginfo.timerid);
      PUSH_CODE(siginfo.overrun);
      PUSH_CODE(siginfo.sigval.sigval);
      break;

    case SIGABRT:
    case SIGQUIT:
    case SIGXCPU:
    case SIGXFSZ:
    case SIGHUP:
    case SIGINT:
    case SIGPIPE:
    case SIGTERM:
    case SIGUSR1:
    case SIGUSR2:
#if defined(SIGEMT)
    case SIGEMT:
#endif  // SIGEMT
#if defined(SIGPWR)
    case SIGPWR:
#endif  // SIGPWR
#if defined(SIGSTKFLT)
    case SIGSTKFLT:
#endif  // SIGSTKFLT
      PUSH_CODE(siginfo.pid);
      PUSH_CODE(siginfo.uid);
      PUSH_CODE(siginfo.sigval.sigval);
      break;

    case Signals::kSimulatedSigno:
      break;

    default:
      LOG(WARNING) << "Unhandled signal " << siginfo.signo;
  }

  return true;
}

const CPUContext* ExceptionSnapshotLinux::Context() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return &context_;
}

uint64_t ExceptionSnapshotLinux::ThreadID() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return thread_id_;
}

uint32_t ExceptionSnapshotLinux::Exception() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return signal_number_;
}

uint32_t ExceptionSnapshotLinux::ExceptionInfo() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return signal_code_;
}

uint64_t ExceptionSnapshotLinux::ExceptionAddress() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return exception_address_;
}

const std::vector<uint64_t>& ExceptionSnapshotLinux::Codes() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return codes_;
}

std::vector<const MemorySnapshot*> ExceptionSnapshotLinux::ExtraMemory() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  std::vector<const MemorySnapshot*> result;
  result.reserve(extra_memory_.size());
  for (const auto& em : extra_memory_) {
    result.push_back(em.get());
  }
  return result;
}

}  // namespace internal
}  // namespace crashpad
