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

#ifndef CRASHPAD_SNAPSHOT_MAC_PROCESS_READER_MAC_H_
#define CRASHPAD_SNAPSHOT_MAC_PROCESS_READER_MAC_H_

#include <Availability.h>
#include <mach/mach.h>
#include <stdint.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>

#include <memory>
#include <string>
#include <vector>

#include "build/build_config.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/posix/process_info.h"
#include "util/process/process_memory_mac.h"

#if defined(ARCH_CPU_32_BIT) ||  \
    (!defined(ARCH_CPU_ARM64) && \
     __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_15)
// There’s no 32-bit x86 environment on macOS 10.15 or later, and there’s no
// 32-bit ARM environment for macOS at all.
#define CRASHPAD_MAC_32_BIT_SUPPORT 1
#endif  // ARCH_CPU_32_BIT || (!ARCH_CPU_ARM64 && DT < 10.15)

namespace crashpad {

class MachOImageReader;

//! \brief Accesses information about another process, identified by a Mach
//!     task.
class ProcessReaderMac {
 public:
  //! \brief Contains information about a thread that belongs to a task
  //!     (process).
  struct Thread {
#if defined(ARCH_CPU_X86_FAMILY)
    union ThreadContext {
      x86_thread_state64_t t64;
      x86_thread_state32_t t32;
    };
    union FloatContext {
      x86_float_state64_t f64;
      x86_float_state32_t f32;
    };
    union DebugContext {
      x86_debug_state64_t d64;
      x86_debug_state32_t d32;
    };
#elif defined(ARCH_CPU_ARM64)
    using ThreadContext = arm_thread_state64_t;
    using FloatContext = arm_neon_state64_t;
    using DebugContext = arm_debug_state64_t;
#endif

    Thread();
    ~Thread() {}

    ThreadContext thread_context;
    FloatContext float_context;
    DebugContext debug_context;
    uint64_t id;
    mach_vm_address_t stack_region_address;
    mach_vm_size_t stack_region_size;
    mach_vm_address_t thread_specific_data_address;
    thread_t port;
    int suspend_count;
    int priority;
  };

  //! \brief Contains information about a module loaded into a process.
  struct Module {
    Module();
    ~Module();

    //! \brief The pathname used to load the module from disk.
    std::string name;

    //! \brief An image reader for the module.
    //!
    //! The lifetime of this MachOImageReader is scoped to the lifetime of the
    //! ProcessReaderMac that created it.
    //!
    //! This field may be `nullptr` if a reader could not be created for the
    //! module.
    const MachOImageReader* reader;

    //! \brief The module’s timestamp.
    //!
    //! This field will be `0` if its value cannot be determined. It can only be
    //! determined for images that are loaded by dyld, so it will be `0` for the
    //! main executable and for dyld itself.
    time_t timestamp;
  };

  ProcessReaderMac();

  ProcessReaderMac(const ProcessReaderMac&) = delete;
  ProcessReaderMac& operator=(const ProcessReaderMac&) = delete;

  ~ProcessReaderMac();

  //! \brief Initializes this object. This method must be called before any
  //!     other.
  //!
  //! \param[in] task A send right to the target task’s task port. This object
  //!     does not take ownership of the send right.
  //!
  //! \return `true` on success, indicating that this object will respond
  //!     validly to further method calls. `false` on failure. On failure, no
  //!     further method calls should be made.
  bool Initialize(task_t task);

  //! \return `true` if the target task is a 64-bit process.
#if defined(CRASHPAD_MAC_32_BIT_SUPPORT) || DOXYGEN
  bool Is64Bit() const { return is_64_bit_; }
#else  // CRASHPAD_MAC_32_BIT_SUPPORT
  bool Is64Bit() const { return true; }
#endif  // CRASHPAD_MAC_32_BIT_SUPPORT

  //! \return The target task’s process ID.
  pid_t ProcessID() const { return process_info_.ProcessID(); }

  //! \return The target task’s parent process ID.
  pid_t ParentProcessID() const { return process_info_.ParentProcessID(); }

  //! \brief Determines the target process’ start time.
  //!
  //! \param[out] start_time The time that the process started.
  void StartTime(timeval* start_time) const;

  //! \brief Determines the target process’ execution time.
  //!
  //! \param[out] user_time The amount of time the process has executed code in
  //!     user mode.
  //! \param[out] system_time The amount of time the process has executed code
  //!     in system mode.
  //!
  //! \return `true` on success, `false` on failure, with a warning logged. On
  //!     failure, \a user_time and \a system_time will be set to represent no
  //!     time spent executing code in user or system mode.
  bool CPUTimes(timeval* user_time, timeval* system_time) const;

  //! \return Accesses the memory of the target task.
  const ProcessMemoryMac* Memory() const { return &process_memory_; }

  //! \return The threads that are in the task (process). The first element (at
  //!     index `0`) corresponds to the main thread.
  const std::vector<Thread>& Threads();

  //! \return The modules loaded in the process. The first element (at index
  //!     `0`) corresponds to the main executable, and the final element
  //!     corresponds to the dynamic loader, dyld.
  const std::vector<Module>& Modules();

  //! \brief Determines the location of the `dyld_all_image_infos` structure in
  //!     the process’ address space.
  //!
  //! This function is an internal implementation detail of Modules(), and
  //! should not normally be used directly. It is exposed solely for use by test
  //! code.
  //!
  //! \param[out] all_image_info_size The size of the `dyld_all_image_infos`
  //!     structure. Optional, may be `nullptr` if not required.
  //!
  //! \return The address of the `dyld_all_image_infos` structure in the
  //!     process’ address space, with \a all_image_info_size set appropriately.
  //!     On failure, returns `0` with a message logged.
  mach_vm_address_t DyldAllImageInfo(mach_vm_size_t* all_image_info_size);

  task_t task_;  // weak

 private:
  //! Performs lazy initialization of the \a threads_ vector on behalf of
  //! Threads().
  void InitializeThreads();

  //! Performs lazy initialization of the \a modules_ vector on behalf of
  //! Modules().
  void InitializeModules();

  //! \brief Calculates the base address and size of the region used as a
  //!     thread’s stack.
  //!
  //! The region returned by this method may be formed by merging multiple
  //! adjacent regions in a process’ memory map if appropriate. The base address
  //! of the returned region may be lower than the \a stack_pointer passed in
  //! when the ABI mandates a red zone below the stack pointer.
  //!
  //! \param[in] stack_pointer The stack pointer, referring to the top (lowest
  //!     address) of a thread’s stack.
  //! \param[out] stack_region_size The size of the memory region used as the
  //!     thread’s stack.
  //!
  //! \return The base address (lowest address) of the memory region used as the
  //!     thread’s stack.
  mach_vm_address_t CalculateStackRegion(mach_vm_address_t stack_pointer,
                                         mach_vm_size_t* stack_region_size);

  //! \brief Adjusts the region for the red zone, if the ABI requires one.
  //!
  //! This method performs red zone calculation for CalculateStackRegion(). Its
  //! parameters are local variables used within that method, and may be
  //! modified as needed.
  //!
  //! Where a red zone is required, the region of memory captured for a thread’s
  //! stack will be extended to include the red zone below the stack pointer,
  //! provided that such memory is mapped, readable, and has the correct user
  //! tag value. If these conditions cannot be met fully, as much of the red
  //! zone will be captured as is possible while meeting these conditions.
  //!
  //! \param[in,out] start_address The base address of the region to begin
  //!     capturing stack memory from. On entry, \a start_address is the stack
  //!     pointer. On return, \a start_address may be decreased to encompass a
  //!     red zone.
  //! \param[in,out] region_base The base address of the region that contains
  //!     stack memory. This is distinct from \a start_address in that \a
  //!     region_base will be page-aligned. On entry, \a region_base is the
  //!     base address of a region that contains \a start_address. On return,
  //!     if \a start_address is decremented and is outside of the region
  //!     originally described by \a region_base, \a region_base will also be
  //!     decremented appropriately.
  //! \param[in,out] region_size The size of the region that contains stack
  //!     memory. This region begins at \a region_base. On return, if \a
  //!     region_base is decremented, \a region_size will be incremented
  //!     appropriately.
  //! \param[in] user_tag The Mach VM system’s user tag for the region described
  //!     by the initial values of \a region_base and \a region_size. The red
  //!     zone will only be allowed to extend out of the region described by
  //!     these initial values if the user tag is appropriate for stack memory
  //!     and the expanded region has the same user tag value.
  void LocateRedZone(mach_vm_address_t* start_address,
                     mach_vm_address_t* region_base,
                     mach_vm_address_t* region_size,
                     unsigned int user_tag);

  ProcessInfo process_info_;
  std::vector<Thread> threads_;  // owns send rights
  std::vector<Module> modules_;
  std::vector<std::unique_ptr<MachOImageReader>> module_readers_;
  ProcessMemoryMac process_memory_;
  InitializationStateDcheck initialized_;

#if defined(CRASHPAD_MAC_32_BIT_SUPPORT)
  // This shadows a method of process_info_, but it’s accessed so frequently
  // that it’s given a first-class field to save a call and a few bit operations
  // on each access.
  bool is_64_bit_;
#endif  // CRASHPAD_MAC_32_BIT_SUPPORT

  bool initialized_threads_;
  bool initialized_modules_;
};

}  // namespace crashpad

#endif  // CRASHPAD_SNAPSHOT_MAC_PROCESS_READER_MAC_H_
