// Copyright 2014 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/mac/exception_snapshot_mac.h"

#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "snapshot/mac/cpu_context_mac.h"
#include "snapshot/mac/process_reader_mac.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_types.h"
#include "util/mach/symbolic_constants_mach.h"
#include "util/numeric/safe_assignment.h"

namespace crashpad {
namespace internal {

ExceptionSnapshotMac::ExceptionSnapshotMac()
    : ExceptionSnapshot(),
      context_union_(),
      context_(),
      codes_(),
      thread_id_(0),
      exception_address_(0),
      exception_(0),
      exception_code_0_(0),
      initialized_() {
}

ExceptionSnapshotMac::~ExceptionSnapshotMac() {
}

bool ExceptionSnapshotMac::Initialize(ProcessReaderMac* process_reader,
                                      exception_behavior_t behavior,
                                      thread_t exception_thread,
                                      exception_type_t exception,
                                      const mach_exception_data_type_t* code,
                                      mach_msg_type_number_t code_count,
                                      thread_state_flavor_t flavor,
                                      ConstThreadState state,
                                      mach_msg_type_number_t state_count) {
  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);

  codes_.push_back(exception);
  for (mach_msg_type_number_t code_index = 0;
       code_index < code_count;
       ++code_index) {
    codes_.push_back(code[code_index]);
  }

  exception_ = exception;
  mach_exception_code_t exception_code_0 = code[0];

  if (exception_ == EXC_CRASH) {
    exception_ = ExcCrashRecoverOriginalException(
        exception_code_0, &exception_code_0, nullptr);

    if (!ExcCrashCouldContainException(exception_)) {
      LOG(WARNING) << base::StringPrintf(
          "exception %s invalid in EXC_CRASH",
          ExceptionToString(exception_, kUseFullName | kUnknownIsNumeric)
              .c_str());
    }
  }

  // The operations that follow put exception_code_0 (a mach_exception_code_t,
  // a typedef for int64_t) into exception_code_0_ (a uint32_t). The range
  // checks and bit shifts involved need the same signedness on both sides to
  // work properly.
  const uint64_t unsigned_exception_code_0 = exception_code_0;

  // ExceptionInfo() returns code[0] as a 32-bit value, but exception_code_0 is
  // a 64-bit value. The best treatment for this inconsistency depends on the
  // exception type.
  if (exception_ == EXC_RESOURCE || exception_ == EXC_GUARD) {
    // All 64 bits of code[0] are significant for these exceptions. See
    // <mach/exc_resource.h> for EXC_RESOURCE and 10.10
    // xnu-2782.1.97/bsd/kern/kern_guarded.c fd_guard_ast() for EXC_GUARD.
    // code[0] is structured similarly for these two exceptions.
    //
    // EXC_RESOURCE: see <kern/exc_resource.h>. The resource type and “flavor”
    // together define the resource and are in the highest bits. The resource
    // limit is in the lowest bits.
    //
    // EXC_GUARD: see 10.10 xnu-2782.1.97/osfmk/ipc/mach_port.c
    // mach_port_guard_exception() and xnu-2782.1.97/bsd/kern/kern_guarded.c
    // fd_guard_ast(). The guard type (GUARD_TYPE_MACH_PORT or GUARD_TYPE_FD)
    // and “flavor” (from the mach_port_guard_exception_codes or
    // guard_exception_codes enums) are in the highest bits. The violating Mach
    // port name or file descriptor number is in the lowest bits.

    // If MACH_EXCEPTION_CODES is not set in |behavior|, code[0] will only carry
    // 32 significant bits, and the interesting high bits will have been
    // truncated.
    if (!ExceptionBehaviorHasMachExceptionCodes(behavior)) {
      LOG(WARNING) << base::StringPrintf(
          "behavior %s invalid for exception %s",
          ExceptionBehaviorToString(
              behavior, kUseFullName | kUnknownIsNumeric | kUseOr).c_str(),
          ExceptionToString(exception_, kUseFullName | kUnknownIsNumeric)
              .c_str());
    }

    // Include the more-significant information from the high bits of code[0] in
    // the value to be returned by ExceptionInfo(). The full value of codes[0]
    // including the less-significant lower bits is still available via Codes().
    exception_code_0_ = unsigned_exception_code_0 >> 32;
  } else {
    // For other exceptions, code[0]’s values never exceed 32 bits.
    if (!base::IsValueInRangeForNumericType<decltype(exception_code_0_)>(
            unsigned_exception_code_0)) {
      LOG(WARNING) << base::StringPrintf("exception_code_0 0x%llx out of range",
                                         unsigned_exception_code_0);
    }
    exception_code_0_ = unsigned_exception_code_0;
  }

  const ProcessReaderMac::Thread* thread = nullptr;
  for (const ProcessReaderMac::Thread& loop_thread :
       process_reader->Threads()) {
    if (exception_thread == loop_thread.port) {
      thread = &loop_thread;
      break;
    }
  }

  if (!thread) {
    LOG(ERROR) << "exception_thread not found in task";
    return false;
  }

  thread_id_ = thread->id;

  // Normally, for EXC_BAD_ACCESS exceptions, the exception address is present
  // in code[1]. It may or may not be the instruction pointer address (usually
  // it’s not). code[1] may carry the exception address for other exception
  // types too, but it’s not guaranteed. But for all other exception types, the
  // instruction pointer will be the exception address, and in fact will be
  // equal to codes[1] when it’s carrying the exception address. In those cases,
  // just use the instruction pointer directly.
  bool code_1_is_exception_address = exception_ == EXC_BAD_ACCESS;

#if defined(ARCH_CPU_X86_FAMILY)
  if (process_reader->Is64Bit()) {
    context_.architecture = kCPUArchitectureX86_64;
    context_.x86_64 = &context_union_.x86_64;
    InitializeCPUContextX86_64(context_.x86_64,
                               flavor,
                               state,
                               state_count,
                               &thread->thread_context.t64,
                               &thread->float_context.f64,
                               &thread->debug_context.d64);
  } else {
    context_.architecture = kCPUArchitectureX86;
    context_.x86 = &context_union_.x86;
    InitializeCPUContextX86(context_.x86,
                            flavor,
                            state,
                            state_count,
                            &thread->thread_context.t32,
                            &thread->float_context.f32,
                            &thread->debug_context.d32);
  }

  // For x86 and x86_64 EXC_BAD_ACCESS exceptions, some code[0] values indicate
  // that code[1] does not (or may not) carry the exception address:
  // EXC_I386_GPFLT (10.9.5 xnu-2422.115.4/osfmk/i386/trap.c user_trap() for
  // T_GENERAL_PROTECTION) and the oddball (VM_PROT_READ | VM_PROT_EXECUTE)
  // which collides with EXC_I386_BOUNDFLT (10.9.5
  // xnu-2422.115.4/osfmk/i386/fpu.c fpextovrflt()). Other EXC_BAD_ACCESS
  // exceptions come through 10.9.5 xnu-2422.115.4/osfmk/i386/trap.c
  // user_page_fault_continue() and do contain the exception address in code[1].
  if (exception_ == EXC_BAD_ACCESS &&
      (exception_code_0_ == EXC_I386_GPFLT ||
       exception_code_0_ == (VM_PROT_READ | VM_PROT_EXECUTE))) {
    code_1_is_exception_address = false;
  }
#elif defined(ARCH_CPU_ARM64)
  context_.architecture = kCPUArchitectureARM64;
  context_.arm64 = &context_union_.arm64;
  InitializeCPUContextARM64(context_.arm64,
                            flavor,
                            state,
                            state_count,
                            &thread->thread_context,
                            &thread->float_context,
                            &thread->debug_context);
#else
#error Port to your architecture
#endif

  if (code_1_is_exception_address) {
    if (process_reader->Is64Bit() &&
        !ExceptionBehaviorHasMachExceptionCodes(behavior)) {
      // If code[1] is an address from a 64-bit process, the exception must have
      // been received with MACH_EXCEPTION_CODES or the address will have been
      // truncated.
      LOG(WARNING) << base::StringPrintf(
          "behavior %s invalid for exception %s code %d in 64-bit process",
          ExceptionBehaviorToString(
              behavior, kUseFullName | kUnknownIsNumeric | kUseOr).c_str(),
          ExceptionToString(exception_, kUseFullName | kUnknownIsNumeric)
              .c_str(),
          exception_code_0_);
    }
    exception_address_ = code[1];
  } else {
    exception_address_ = context_.InstructionPointer();
  }

  INITIALIZATION_STATE_SET_VALID(initialized_);
  return true;
}

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

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

uint32_t ExceptionSnapshotMac::Exception() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return exception_;
}

uint32_t ExceptionSnapshotMac::ExceptionInfo() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return exception_code_0_;
}

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

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

std::vector<const MemorySnapshot*> ExceptionSnapshotMac::ExtraMemory() const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
  return std::vector<const MemorySnapshot*>();
}

}  // namespace internal
}  // namespace crashpad
