//===-- NativeProcessDarwin.cpp ---------------------------------*- C++ -*-===//
//
// 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 "NativeProcessDarwin.h"

// C includes
#include <mach/mach_init.h>
#include <mach/mach_traps.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/types.h>

// C++ includes
// LLDB includes
#include "lldb/Host/PseudoTerminal.h"
#include "lldb/Target/ProcessLaunchInfo.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/State.h"
#include "lldb/Utility/StreamString.h"

#include "CFBundle.h"
#include "CFString.h"
#include "DarwinProcessLauncher.h"

#include "MachException.h"

#include "llvm/Support/FileSystem.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_darwin;
using namespace lldb_private::darwin_process_launcher;

// Hidden Impl

namespace {
struct hack_task_dyld_info {
  mach_vm_address_t all_image_info_addr;
  mach_vm_size_t all_image_info_size;
};
}

// Public Static Methods

Status NativeProcessProtocol::Launch(
    ProcessLaunchInfo &launch_info,
    NativeProcessProtocol::NativeDelegate &native_delegate, MainLoop &mainloop,
    NativeProcessProtocolSP &native_process_sp) {
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  Status error;

  // Verify the working directory is valid if one was specified.
  FileSpec working_dir(launch_info.GetWorkingDirectory());
  if (working_dir) {
    FileInstance::Instance().Resolve(working_dir);
    if (!FileSystem::Instance().IsDirectory(working_dir)) {
      error.SetErrorStringWithFormat("No such file or directory: %s",
                                   working_dir.GetCString());
      return error;
    }
  }

  // Launch the inferior.
  int pty_master_fd = -1;
  LaunchFlavor launch_flavor = LaunchFlavor::Default;

  error = LaunchInferior(launch_info, &pty_master_fd, &launch_flavor);

  // Handle launch failure.
  if (!error.Success()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s() failed to launch process: "
                  "%s",
                  __FUNCTION__, error.AsCString());
    return error;
  }

  // Handle failure to return a pid.
  if (launch_info.GetProcessID() == LLDB_INVALID_PROCESS_ID) {
    if (log)
      log->Printf("NativeProcessDarwin::%s() launch succeeded but no "
                  "pid was returned!  Aborting.",
                  __FUNCTION__);
    return error;
  }

  // Create the Darwin native process impl.
  std::shared_ptr<NativeProcessDarwin> np_darwin_sp(
      new NativeProcessDarwin(launch_info.GetProcessID(), pty_master_fd));
  if (!np_darwin_sp->RegisterNativeDelegate(native_delegate)) {
    native_process_sp.reset();
    error.SetErrorStringWithFormat("failed to register the native delegate");
    return error;
  }

  // Finalize the processing needed to debug the launched process with a
  // NativeProcessDarwin instance.
  error = np_darwin_sp->FinalizeLaunch(launch_flavor, mainloop);
  if (!error.Success()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s() aborting, failed to finalize"
                  " the launching of the process: %s",
                  __FUNCTION__, error.AsCString());
    return error;
  }

  // Return the process and process id to the caller through the launch args.
  native_process_sp = np_darwin_sp;
  return error;
}

Status NativeProcessProtocol::Attach(
    lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate,
    MainLoop &mainloop, NativeProcessProtocolSP &native_process_sp) {
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
  if (log)
    log->Printf("NativeProcessDarwin::%s(pid = %" PRIi64 ")", __FUNCTION__,
                pid);

  // Retrieve the architecture for the running process.
  ArchSpec process_arch;
  Status error = ResolveProcessArchitecture(pid, process_arch);
  if (!error.Success())
    return error;

  // TODO get attach to return this value.
  const int pty_master_fd = -1;
  std::shared_ptr<NativeProcessDarwin> native_process_darwin_sp(
      new NativeProcessDarwin(pid, pty_master_fd));

  if (!native_process_darwin_sp->RegisterNativeDelegate(native_delegate)) {
    error.SetErrorStringWithFormat("failed to register the native "
                                   "delegate");
    return error;
  }

  native_process_darwin_sp->AttachToInferior(mainloop, pid, error);
  if (!error.Success())
    return error;

  native_process_sp = native_process_darwin_sp;
  return error;
}

// ctor/dtor

NativeProcessDarwin::NativeProcessDarwin(lldb::pid_t pid, int pty_master_fd)
    : NativeProcessProtocol(pid), m_task(TASK_NULL), m_did_exec(false),
      m_cpu_type(0), m_exception_port(MACH_PORT_NULL), m_exc_port_info(),
      m_exception_thread(nullptr), m_exception_messages_mutex(),
      m_sent_interrupt_signo(0), m_auto_resume_signo(0), m_thread_list(),
      m_thread_actions(), m_waitpid_pipe(), m_waitpid_thread(nullptr),
      m_waitpid_reader_handle() {
  // TODO add this to the NativeProcessProtocol constructor.
  m_terminal_fd = pty_master_fd;
}

NativeProcessDarwin::~NativeProcessDarwin() {}

// Instance methods

Status NativeProcessDarwin::FinalizeLaunch(LaunchFlavor launch_flavor,
                                           MainLoop &main_loop) {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  error = StartExceptionThread();
  if (!error.Success()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): failure starting the "
                  "mach exception port monitor thread: %s",
                  __FUNCTION__, error.AsCString());

    // Terminate the inferior process.  There's nothing meaningful we can do if
    // we can't receive signals and exceptions.  Since we launched the process,
    // it's fair game for us to kill it.
    ::ptrace(PT_KILL, m_pid, 0, 0);
    SetState(eStateExited);

    return error;
  }

  StartSTDIOThread();

  if (launch_flavor == LaunchFlavor::PosixSpawn) {
    SetState(eStateAttaching);
    errno = 0;
    int err = ::ptrace(PT_ATTACHEXC, m_pid, 0, 0);
    if (err == 0) {
      // m_flags |= eMachProcessFlagsAttached;
      if (log)
        log->Printf("NativeProcessDarwin::%s(): successfully spawned "
                    "process with pid %" PRIu64,
                    __FUNCTION__, m_pid);
    } else {
      error.SetErrorToErrno();
      SetState(eStateExited);
      if (log)
        log->Printf("NativeProcessDarwin::%s(): error: failed to "
                    "attach to spawned pid %" PRIu64 " (error=%d (%s))",
                    __FUNCTION__, m_pid, (int)error.GetError(),
                    error.AsCString());
      return error;
    }
  }

  if (log)
    log->Printf("NativeProcessDarwin::%s(): new pid is %" PRIu64 "...",
                __FUNCTION__, m_pid);

  // Spawn a thread to reap our child inferior process...
  error = StartWaitpidThread(main_loop);
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): failed to start waitpid() "
                  "thread: %s",
                  __FUNCTION__, error.AsCString());
    kill(SIGKILL, static_cast<::pid_t>(m_pid));
    return error;
  }

  if (TaskPortForProcessID(error) == TASK_NULL) {
    // We failed to get the task for our process ID which is bad. Kill our
    // process; otherwise, it will be stopped at the entry point and get
    // reparented to someone else and never go away.
    if (log)
      log->Printf("NativeProcessDarwin::%s(): could not get task port "
                  "for process, sending SIGKILL and exiting: %s",
                  __FUNCTION__, error.AsCString());
    kill(SIGKILL, static_cast<::pid_t>(m_pid));
    return error;
  }

  // Indicate that we're stopped, as we always launch suspended.
  SetState(eStateStopped);

  // Success.
  return error;
}

Status NativeProcessDarwin::SaveExceptionPortInfo() {
  return m_exc_port_info.Save(m_task);
}

bool NativeProcessDarwin::ProcessUsingSpringBoard() const {
  // TODO implement flags
  // return (m_flags & eMachProcessFlagsUsingSBS) != 0;
  return false;
}

bool NativeProcessDarwin::ProcessUsingBackBoard() const {
  // TODO implement flags
  // return (m_flags & eMachProcessFlagsUsingBKS) != 0;
  return false;
}

// Called by the exception thread when an exception has been received from our
// process. The exception message is completely filled and the exception data
// has already been copied.
void NativeProcessDarwin::ExceptionMessageReceived(
    const MachException::Message &message) {
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));

  std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex);
  if (m_exception_messages.empty()) {
    // Suspend the task the moment we receive our first exception message.
    SuspendTask();
  }

  // Use a locker to automatically unlock our mutex in case of exceptions Add
  // the exception to our internal exception stack
  m_exception_messages.push_back(message);

  if (log)
    log->Printf("NativeProcessDarwin::%s(): new queued message count: %lu",
                __FUNCTION__, m_exception_messages.size());
}

void *NativeProcessDarwin::ExceptionThread(void *arg) {
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));
  if (!arg) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): cannot run mach exception "
                  "thread, mandatory process arg was null",
                  __FUNCTION__);
    return nullptr;
  }

  return reinterpret_cast<NativeProcessDarwin *>(arg)->DoExceptionThread();
}

void *NativeProcessDarwin::DoExceptionThread() {
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));

  if (log)
    log->Printf("NativeProcessDarwin::%s(arg=%p) starting thread...",
                __FUNCTION__, this);

  pthread_setname_np("exception monitoring thread");

  // Ensure we don't get CPU starved.
  MaybeRaiseThreadPriority();

  // We keep a count of the number of consecutive exceptions received so we
  // know to grab all exceptions without a timeout. We do this to get a bunch
  // of related exceptions on our exception port so we can process then
  // together. When we have multiple threads, we can get an exception per
  // thread and they will come in consecutively. The main loop in this thread
  // can stop periodically if needed to service things related to this process.
  //
  // [did we lose some words here?]
  //
  // flag set in the options, so we will wait forever for an exception on
  // 0 our exception port. After we get one exception, we then will use the
  // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
  // exceptions for our process. After we have received the last pending
  // exception, we will get a timeout which enables us to then notify our main
  // thread that we have an exception bundle available. We then wait for the
  // main thread to tell this exception thread to start trying to get
  // exceptions messages again and we start again with a mach_msg read with
  // infinite timeout.
  //
  // We choose to park a thread on this, rather than polling, because the
  // polling is expensive.  On devices, we need to minimize overhead caused by
  // the process monitor.
  uint32_t num_exceptions_received = 0;
  Status error;
  task_t task = m_task;
  mach_msg_timeout_t periodic_timeout = 0;

#if defined(WITH_SPRINGBOARD) && !defined(WITH_BKS)
  mach_msg_timeout_t watchdog_elapsed = 0;
  mach_msg_timeout_t watchdog_timeout = 60 * 1000;
  ::pid_t pid = (::pid_t)process->GetID();
  CFReleaser<SBSWatchdogAssertionRef> watchdog;

  if (process->ProcessUsingSpringBoard()) {
    // Request a renewal for every 60 seconds if we attached using SpringBoard.
    watchdog.reset(::SBSWatchdogAssertionCreateForPID(nullptr, pid, 60));
    if (log)
      log->Printf("::SBSWatchdogAssertionCreateForPID(NULL, %4.4x, 60) "
                  "=> %p",
                  pid, watchdog.get());

    if (watchdog.get()) {
      ::SBSWatchdogAssertionRenew(watchdog.get());

      CFTimeInterval watchdogRenewalInterval =
          ::SBSWatchdogAssertionGetRenewalInterval(watchdog.get());
      if (log)
        log->Printf("::SBSWatchdogAssertionGetRenewalInterval(%p) => "
                    "%g seconds",
                    watchdog.get(), watchdogRenewalInterval);
      if (watchdogRenewalInterval > 0.0) {
        watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000;
        if (watchdog_timeout > 3000) {
          // Give us a second to renew our timeout.
          watchdog_timeout -= 1000;
        } else if (watchdog_timeout > 1000) {
          // Give us a quarter of a second to renew our timeout.
          watchdog_timeout -= 250;
        }
      }
    }
    if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout)
      periodic_timeout = watchdog_timeout;
  }
#endif // #if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS)

#ifdef WITH_BKS
  CFReleaser<BKSWatchdogAssertionRef> watchdog;
  if (process->ProcessUsingBackBoard()) {
    ::pid_t pid = process->GetID();
    CFAllocatorRef alloc = kCFAllocatorDefault;
    watchdog.reset(::BKSWatchdogAssertionCreateForPID(alloc, pid));
  }
#endif // #ifdef WITH_BKS

  // Do we want to use a weak pointer to the NativeProcessDarwin here, in which
  // case we can guarantee we don't whack the process monitor if we race
  // between this thread and the main one on shutdown?
  while (IsExceptionPortValid()) {
    ::pthread_testcancel();

    MachException::Message exception_message;

    if (num_exceptions_received > 0) {
      // We don't want a timeout here, just receive as many exceptions as we
      // can since we already have one.  We want to get all currently available
      // exceptions for this task at once.
      error = exception_message.Receive(
          GetExceptionPort(),
          MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0);
    } else if (periodic_timeout > 0) {
      // We need to stop periodically in this loop, so try and get a mach
      // message with a valid timeout (ms).
      error = exception_message.Receive(GetExceptionPort(),
                                        MACH_RCV_MSG | MACH_RCV_INTERRUPT |
                                            MACH_RCV_TIMEOUT,
                                        periodic_timeout);
    } else {
      // We don't need to parse all current exceptions or stop periodically,
      // just wait for an exception forever.
      error = exception_message.Receive(GetExceptionPort(),
                                        MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0);
    }

    if (error.Success()) {
      // We successfully received an exception.
      if (exception_message.CatchExceptionRaise(task)) {
        ++num_exceptions_received;
        ExceptionMessageReceived(exception_message);
      }
    } else {
      if (error.GetError() == MACH_RCV_INTERRUPTED) {
        // We were interrupted.

        // If we have no task port we should exit this thread, as it implies
        // the inferior went down.
        if (!IsExceptionPortValid()) {
          if (log)
            log->Printf("NativeProcessDarwin::%s(): the inferior "
                        "exception port is no longer valid, "
                        "canceling exception thread...",
                        __FUNCTION__);
          // Should we be setting a process state here?
          break;
        }

        // Make sure the inferior task is still valid.
        if (IsTaskValid()) {
          // Task is still ok.
          if (log)
            log->Printf("NativeProcessDarwin::%s(): interrupted, but "
                        "the inferior task iss till valid, "
                        "continuing...",
                        __FUNCTION__);
          continue;
        } else {
          // The inferior task is no longer valid.  Time to exit as the process
          // has gone away.
          if (log)
            log->Printf("NativeProcessDarwin::%s(): the inferior task "
                        "has exited, and so will we...",
                        __FUNCTION__);
          // Does this race at all with our waitpid()?
          SetState(eStateExited);
          break;
        }
      } else if (error.GetError() == MACH_RCV_TIMED_OUT) {
        // We timed out when waiting for exceptions.

        if (num_exceptions_received > 0) {
          // We were receiving all current exceptions with a timeout of zero.
          // It is time to go back to our normal looping mode.
          num_exceptions_received = 0;

          // Notify our main thread we have a complete exception message bundle
          // available.  Get the possibly updated task port back from the
          // process in case we exec'ed and our task port changed.
          task = ExceptionMessageBundleComplete();

          // In case we use a timeout value when getting exceptions, make sure
          // our task is still valid.
          if (IsTaskValid(task)) {
            // Task is still ok.
            if (log)
              log->Printf("NativeProcessDarwin::%s(): got a timeout, "
                          "continuing...",
                          __FUNCTION__);
            continue;
          } else {
            // The inferior task is no longer valid.  Time to exit as the
            // process has gone away.
            if (log)
              log->Printf("NativeProcessDarwin::%s(): the inferior "
                          "task has exited, and so will we...",
                          __FUNCTION__);
            // Does this race at all with our waitpid()?
            SetState(eStateExited);
            break;
          }
        }

#if defined(WITH_SPRINGBOARD) && !defined(WITH_BKS)
        if (watchdog.get()) {
          watchdog_elapsed += periodic_timeout;
          if (watchdog_elapsed >= watchdog_timeout) {
            if (log)
              log->Printf("SBSWatchdogAssertionRenew(%p)", watchdog.get());
            ::SBSWatchdogAssertionRenew(watchdog.get());
            watchdog_elapsed = 0;
          }
        }
#endif
      } else {
        if (log)
          log->Printf("NativeProcessDarwin::%s(): continuing after "
                      "receiving an unexpected error: %u (%s)",
                      __FUNCTION__, error.GetError(), error.AsCString());
        // TODO: notify of error?
      }
    }
  }

#if defined(WITH_SPRINGBOARD) && !defined(WITH_BKS)
  if (watchdog.get()) {
    // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel
    // when we
    // all are up and running on systems that support it. The SBS framework has
    // a #define that will forward SBSWatchdogAssertionRelease to
    // SBSWatchdogAssertionCancel for now so it should still build either way.
    DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)",
                     watchdog.get());
    ::SBSWatchdogAssertionRelease(watchdog.get());
  }
#endif // #if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS)

  if (log)
    log->Printf("NativeProcessDarwin::%s(%p): thread exiting...", __FUNCTION__,
                this);
  return nullptr;
}

Status NativeProcessDarwin::StartExceptionThread() {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
  if (log)
    log->Printf("NativeProcessDarwin::%s() called", __FUNCTION__);

  // Make sure we've looked up the inferior port.
  TaskPortForProcessID(error);

  // Ensure the inferior task is valid.
  if (!IsTaskValid()) {
    error.SetErrorStringWithFormat("cannot start exception thread: "
                                   "task 0x%4.4x is not valid",
                                   m_task);
    return error;
  }

  // Get the mach port for the process monitor.
  mach_port_t task_self = mach_task_self();

  // Allocate an exception port that we will use to track our child process
  auto mach_err = ::mach_port_allocate(task_self, MACH_PORT_RIGHT_RECEIVE,
                                       &m_exception_port);
  error.SetError(mach_err, eErrorTypeMachKernel);
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): mach_port_allocate("
                  "task_self=0x%4.4x, MACH_PORT_RIGHT_RECEIVE, "
                  "&m_exception_port) failed: %u (%s)",
                  __FUNCTION__, task_self, error.GetError(), error.AsCString());
    return error;
  }

  // Add the ability to send messages on the new exception port
  mach_err = ::mach_port_insert_right(
      task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND);
  error.SetError(mach_err, eErrorTypeMachKernel);
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): mach_port_insert_right("
                  "task_self=0x%4.4x, m_exception_port=0x%4.4x, "
                  "m_exception_port=0x%4.4x, MACH_MSG_TYPE_MAKE_SEND) "
                  "failed: %u (%s)",
                  __FUNCTION__, task_self, m_exception_port, m_exception_port,
                  error.GetError(), error.AsCString());
    return error;
  }

  // Save the original state of the exception ports for our child process.
  error = SaveExceptionPortInfo();
  if (error.Fail() || (m_exc_port_info.mask == 0)) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): SaveExceptionPortInfo() "
                  "failed, cannot install exception handler: %s",
                  __FUNCTION__, error.AsCString());
    return error;
  }

  // Set the ability to get all exceptions on this port.
  mach_err = ::task_set_exception_ports(
      m_task, m_exc_port_info.mask, m_exception_port,
      EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
  error.SetError(mach_err, eErrorTypeMachKernel);
  if (error.Fail()) {
    if (log)
      log->Printf("::task_set_exception_ports (task = 0x%4.4x, "
                  "exception_mask = 0x%8.8x, new_port = 0x%4.4x, "
                  "behavior = 0x%8.8x, new_flavor = 0x%8.8x) failed: "
                  "%u (%s)",
                  m_task, m_exc_port_info.mask, m_exception_port,
                  (EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES), THREAD_STATE_NONE,
                  error.GetError(), error.AsCString());
    return error;
  }

  // Create the exception thread.
  auto pthread_err =
      ::pthread_create(&m_exception_thread, nullptr, ExceptionThread, this);
  error.SetError(pthread_err, eErrorTypePOSIX);
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): failed to create Mach "
                  "exception-handling thread: %u (%s)",
                  __FUNCTION__, error.GetError(), error.AsCString());
  }

  return error;
}

lldb::addr_t
NativeProcessDarwin::GetDYLDAllImageInfosAddress(Status &error) const {
  error.Clear();

  struct hack_task_dyld_info dyld_info;
  mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
  // Make sure that COUNT isn't bigger than our hacked up struct
  // hack_task_dyld_info.  If it is, then make COUNT smaller to match.
  if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t))) {
    count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t));
  }

  TaskPortForProcessID(error);
  if (error.Fail())
    return LLDB_INVALID_ADDRESS;

  auto mach_err =
      ::task_info(m_task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
  error.SetError(mach_err, eErrorTypeMachKernel);
  if (error.Success()) {
    // We now have the address of the all image infos structure.
    return dyld_info.all_image_info_addr;
  }

  // We don't have it.
  return LLDB_INVALID_ADDRESS;
}

uint32_t NativeProcessDarwin::GetCPUTypeForLocalProcess(::pid_t pid) {
  int mib[CTL_MAXNAME] = {
      0,
  };
  size_t len = CTL_MAXNAME;

  if (::sysctlnametomib("sysctl.proc_cputype", mib, &len))
    return 0;

  mib[len] = pid;
  len++;

  cpu_type_t cpu;
  size_t cpu_len = sizeof(cpu);
  if (::sysctl(mib, static_cast<u_int>(len), &cpu, &cpu_len, 0, 0))
    cpu = 0;
  return cpu;
}

uint32_t NativeProcessDarwin::GetCPUType() const {
  if (m_cpu_type == 0 && m_pid != 0)
    m_cpu_type = GetCPUTypeForLocalProcess(m_pid);
  return m_cpu_type;
}

task_t NativeProcessDarwin::ExceptionMessageBundleComplete() {
  // We have a complete bundle of exceptions for our child process.
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));

  std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex);
  if (log)
    log->Printf("NativeProcessDarwin::%s(): processing %lu exception "
                "messages.",
                __FUNCTION__, m_exception_messages.size());

  if (m_exception_messages.empty()) {
    // Not particularly useful...
    return m_task;
  }

  bool auto_resume = false;
  m_did_exec = false;

  // First check for any SIGTRAP and make sure we didn't exec
  const task_t task = m_task;
  size_t i;
  if (m_pid != 0) {
    bool received_interrupt = false;
    uint32_t num_task_exceptions = 0;
    for (i = 0; i < m_exception_messages.size(); ++i) {
      if (m_exception_messages[i].state.task_port != task) {
        // This is an exception that is not for our inferior, ignore.
        continue;
      }

      // This is an exception for the inferior.
      ++num_task_exceptions;
      const int signo = m_exception_messages[i].state.SoftSignal();
      if (signo == SIGTRAP) {
        // SIGTRAP could mean that we exec'ed. We need to check the
        // dyld all_image_infos.infoArray to see if it is NULL and if so, say
        // that we exec'ed.
        const addr_t aii_addr = GetDYLDAllImageInfosAddress(error);
        if (aii_addr == LLDB_INVALID_ADDRESS)
          break;

        const addr_t info_array_count_addr = aii_addr + 4;
        uint32_t info_array_count = 0;
        size_t bytes_read = 0;
        Status read_error;
        read_error = ReadMemory(info_array_count_addr, // source addr
                                &info_array_count,     // dest addr
                                4,                     // byte count
                                bytes_read);           // #bytes read
        if (read_error.Success() && (bytes_read == 4)) {
          if (info_array_count == 0) {
            // We got the all infos address, and there are zero entries.  We
            // think we exec'd.
            m_did_exec = true;

            // Force the task port to update itself in case the task port
            // changed after exec
            const task_t old_task = m_task;
            const bool force_update = true;
            const task_t new_task = TaskPortForProcessID(error, force_update);
            if (old_task != new_task) {
              if (log)
                log->Printf("exec: inferior task port changed "
                            "from 0x%4.4x to 0x%4.4x",
                            old_task, new_task);
            }
          }
        } else {
          if (log)
            log->Printf("NativeProcessDarwin::%s() warning: "
                        "failed to read all_image_infos."
                        "infoArrayCount from 0x%8.8llx",
                        __FUNCTION__, info_array_count_addr);
        }
      } else if ((m_sent_interrupt_signo != 0) &&
                 (signo == m_sent_interrupt_signo)) {
        // We just received the interrupt that we sent to ourselves.
        received_interrupt = true;
      }
    }

    if (m_did_exec) {
      cpu_type_t process_cpu_type = GetCPUTypeForLocalProcess(m_pid);
      if (m_cpu_type != process_cpu_type) {
        if (log)
          log->Printf("NativeProcessDarwin::%s(): arch changed from "
                      "0x%8.8x to 0x%8.8x",
                      __FUNCTION__, m_cpu_type, process_cpu_type);
        m_cpu_type = process_cpu_type;
        // TODO figure out if we need to do something here.
        // DNBArchProtocol::SetArchitecture (process_cpu_type);
      }
      m_thread_list.Clear();

      // TODO hook up breakpoints.
      // m_breakpoints.DisableAll();
    }

    if (m_sent_interrupt_signo != 0) {
      if (received_interrupt) {
        if (log)
          log->Printf("NativeProcessDarwin::%s(): process "
                      "successfully interrupted with signal %i",
                      __FUNCTION__, m_sent_interrupt_signo);

        // Mark that we received the interrupt signal
        m_sent_interrupt_signo = 0;
        // Now check if we had a case where:
        // 1 - We called NativeProcessDarwin::Interrupt() but we stopped
        //     for another reason.
        // 2 - We called NativeProcessDarwin::Resume() (but still
        //     haven't gotten the interrupt signal).
        // 3 - We are now incorrectly stopped because we are handling
        //     the interrupt signal we missed.
        // 4 - We might need to resume if we stopped only with the
        //     interrupt signal that we never handled.
        if (m_auto_resume_signo != 0) {
          // Only auto_resume if we stopped with _only_ the interrupt signal.
          if (num_task_exceptions == 1) {
            auto_resume = true;
            if (log)
              log->Printf("NativeProcessDarwin::%s(): auto "
                          "resuming due to unhandled interrupt "
                          "signal %i",
                          __FUNCTION__, m_auto_resume_signo);
          }
          m_auto_resume_signo = 0;
        }
      } else {
        if (log)
          log->Printf("NativeProcessDarwin::%s(): didn't get signal "
                      "%i after MachProcess::Interrupt()",
                      __FUNCTION__, m_sent_interrupt_signo);
      }
    }
  }

  // Let all threads recover from stopping and do any clean up based on the
  // previous thread state (if any).
  m_thread_list.ProcessDidStop(*this);

  // Let each thread know of any exceptions
  for (i = 0; i < m_exception_messages.size(); ++i) {
    // Let the thread list forward all exceptions on down to each thread.
    if (m_exception_messages[i].state.task_port == task) {
      // This exception is for our inferior.
      m_thread_list.NotifyException(m_exception_messages[i].state);
    }

    if (log) {
      StreamString stream;
      m_exception_messages[i].Dump(stream);
      stream.Flush();
      log->PutCString(stream.GetString().c_str());
    }
  }

  if (log) {
    StreamString stream;
    m_thread_list.Dump(stream);
    stream.Flush();
    log->PutCString(stream.GetString().c_str());
  }

  bool step_more = false;
  if (m_thread_list.ShouldStop(step_more) && (auto_resume == false)) {
// TODO - need to hook up event system here. !!!!
#if 0
        // Wait for the eEventProcessRunningStateChanged event to be reset
        // before changing state to stopped to avoid race condition with very
        // fast start/stops.
        struct timespec timeout;

        //DNBTimer::OffsetTimeOfDay(&timeout, 0, 250 * 1000);   // Wait for 250 ms
        DNBTimer::OffsetTimeOfDay(&timeout, 1, 0);  // Wait for 250 ms
        m_events.WaitForEventsToReset(eEventProcessRunningStateChanged,
                                      &timeout);
#endif
    SetState(eStateStopped);
  } else {
    // Resume without checking our current state.
    PrivateResume();
  }

  return m_task;
}

void NativeProcessDarwin::StartSTDIOThread() {
  // TODO implement
}

Status NativeProcessDarwin::StartWaitpidThread(MainLoop &main_loop) {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  // Strategy: create a thread that sits on waitpid(), waiting for the inferior
  // process to die, reaping it in the process.  Arrange for the thread to have
  // a pipe file descriptor that it can send a byte over when the waitpid
  // completes.  Have the main loop have a read object for the other side of
  // the pipe, and have the callback for the read do the process termination
  // message sending.

  // Create a single-direction communication channel.
  const bool child_inherits = false;
  error = m_waitpid_pipe.CreateNew(child_inherits);
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): failed to create waitpid "
                  "communication pipe: %s",
                  __FUNCTION__, error.AsCString());
    return error;
  }

  // Hook up the waitpid reader callback.

  // TODO make PipePOSIX derive from IOObject.  This is goofy here.
  const bool transfer_ownership = false;
  auto io_sp = IOObjectSP(
      new File(m_waitpid_pipe.GetReadFileDescriptor(), transfer_ownership));
  m_waitpid_reader_handle = main_loop.RegisterReadObject(
      io_sp, [this](MainLoopBase &) { HandleWaitpidResult(); }, error);

  // Create the thread.
  auto pthread_err =
      ::pthread_create(&m_waitpid_thread, nullptr, WaitpidThread, this);
  error.SetError(pthread_err, eErrorTypePOSIX);
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): failed to create waitpid "
                  "handling thread: %u (%s)",
                  __FUNCTION__, error.GetError(), error.AsCString());
    return error;
  }

  return error;
}

void *NativeProcessDarwin::WaitpidThread(void *arg) {
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
  if (!arg) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): cannot run waitpid "
                  "thread, mandatory process arg was null",
                  __FUNCTION__);
    return nullptr;
  }

  return reinterpret_cast<NativeProcessDarwin *>(arg)->DoWaitpidThread();
}

void NativeProcessDarwin::MaybeRaiseThreadPriority() {
#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)
  struct sched_param thread_param;
  int thread_sched_policy;
  if (pthread_getschedparam(pthread_self(), &thread_sched_policy,
                            &thread_param) == 0) {
    thread_param.sched_priority = 47;
    pthread_setschedparam(pthread_self(), thread_sched_policy, &thread_param);
  }
#endif
}

void *NativeProcessDarwin::DoWaitpidThread() {
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  if (m_pid == LLDB_INVALID_PROCESS_ID) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): inferior process ID is "
                  "not set, cannot waitpid on it",
                  __FUNCTION__);
    return nullptr;
  }

  // Name the thread.
  pthread_setname_np("waitpid thread");

  // Ensure we don't get CPU starved.
  MaybeRaiseThreadPriority();

  Status error;
  int status = -1;

  while (1) {
    // Do a waitpid.
    ::pid_t child_pid = ::waitpid(m_pid, &status, 0);
    if (child_pid < 0)
      error.SetErrorToErrno();
    if (error.Fail()) {
      if (error.GetError() == EINTR) {
        // This is okay, we can keep going.
        if (log)
          log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64
                      ", &status, 0) interrupted, continuing",
                      __FUNCTION__, m_pid);
        continue;
      }

      // This error is not okay, abort.
      if (log)
        log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64
                    ", &status, 0) aborting due to error: %u (%s)",
                    __FUNCTION__, m_pid, error.GetError(), error.AsCString());
      break;
    }

    // Log the successful result.
    if (log)
      log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64
                  ", &status, 0) => %i, status = %i",
                  __FUNCTION__, m_pid, child_pid, status);

    // Handle the result.
    if (WIFSTOPPED(status)) {
      if (log)
        log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64
                    ") received a stop, continuing waitpid() loop",
                    __FUNCTION__, m_pid);
      continue;
    } else // if (WIFEXITED(status) || WIFSIGNALED(status))
    {
      if (log)
        log->Printf("NativeProcessDarwin::%s(pid = %" PRIu64 "): "
                    "waitpid thread is setting exit status for pid = "
                    "%i to %i",
                    __FUNCTION__, m_pid, child_pid, status);

      error = SendInferiorExitStatusToMainLoop(child_pid, status);
      return nullptr;
    }
  }

  // We should never exit as long as our child process is alive.  If we get
  // here, something completely unexpected went wrong and we should exit.
  if (log)
    log->Printf(
        "NativeProcessDarwin::%s(): internal error: waitpid thread "
        "exited out of its main loop in an unexpected way. pid = %" PRIu64
        ". Sending exit status of -1.",
        __FUNCTION__, m_pid);

  error = SendInferiorExitStatusToMainLoop((::pid_t)m_pid, -1);
  return nullptr;
}

Status NativeProcessDarwin::SendInferiorExitStatusToMainLoop(::pid_t pid,
                                                             int status) {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  size_t bytes_written = 0;

  // Send the pid.
  error = m_waitpid_pipe.Write(&pid, sizeof(pid), bytes_written);
  if (error.Fail() || (bytes_written < sizeof(pid))) {
    if (log)
      log->Printf("NativeProcessDarwin::%s() - failed to write "
                  "waitpid exiting pid to the pipe.  Client will not "
                  "hear about inferior exit status!",
                  __FUNCTION__);
    return error;
  }

  // Send the status.
  bytes_written = 0;
  error = m_waitpid_pipe.Write(&status, sizeof(status), bytes_written);
  if (error.Fail() || (bytes_written < sizeof(status))) {
    if (log)
      log->Printf("NativeProcessDarwin::%s() - failed to write "
                  "waitpid exit result to the pipe.  Client will not "
                  "hear about inferior exit status!",
                  __FUNCTION__);
  }
  return error;
}

Status NativeProcessDarwin::HandleWaitpidResult() {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  // Read the pid.
  const bool notify_status = true;

  ::pid_t pid = -1;
  size_t bytes_read = 0;
  error = m_waitpid_pipe.Read(&pid, sizeof(pid), bytes_read);
  if (error.Fail() || (bytes_read < sizeof(pid))) {
    if (log)
      log->Printf("NativeProcessDarwin::%s() - failed to read "
                  "waitpid exiting pid from the pipe.  Will notify "
                  "as if parent process died with exit status -1.",
                  __FUNCTION__);
    SetExitStatus(WaitStatus(WaitStatus::Exit, -1), notify_status);
    return error;
  }

  // Read the status.
  int status = -1;
  error = m_waitpid_pipe.Read(&status, sizeof(status), bytes_read);
  if (error.Fail() || (bytes_read < sizeof(status))) {
    if (log)
      log->Printf("NativeProcessDarwin::%s() - failed to read "
                  "waitpid exit status from the pipe.  Will notify "
                  "as if parent process died with exit status -1.",
                  __FUNCTION__);
    SetExitStatus(WaitStatus(WaitStatus::Exit, -1), notify_status);
    return error;
  }

  // Notify the monitor that our state has changed.
  if (log)
    log->Printf("NativeProcessDarwin::%s(): main loop received waitpid "
                "exit status info: pid=%i (%s), status=%i",
                __FUNCTION__, pid,
                (pid == m_pid) ? "the inferior" : "not the inferior", status);

  SetExitStatus(WaitStatus::Decode(status), notify_status);
  return error;
}

task_t NativeProcessDarwin::TaskPortForProcessID(Status &error,
                                                 bool force) const {
  if ((m_task == TASK_NULL) || force) {
    Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
    if (m_pid == LLDB_INVALID_PROCESS_ID) {
      if (log)
        log->Printf("NativeProcessDarwin::%s(): cannot get task due "
                    "to invalid pid",
                    __FUNCTION__);
      return TASK_NULL;
    }

    const uint32_t num_retries = 10;
    const uint32_t usec_interval = 10000;

    mach_port_t task_self = mach_task_self();
    task_t task = TASK_NULL;

    for (uint32_t i = 0; i < num_retries; i++) {
      kern_return_t err = ::task_for_pid(task_self, m_pid, &task);
      if (err == 0) {
        // Succeeded.  Save and return it.
        error.Clear();
        m_task = task;
        log->Printf("NativeProcessDarwin::%s(): ::task_for_pid("
                    "stub_port = 0x%4.4x, pid = %llu, &task) "
                    "succeeded: inferior task port = 0x%4.4x",
                    __FUNCTION__, task_self, m_pid, m_task);
        return m_task;
      } else {
        // Failed to get the task for the inferior process.
        error.SetError(err, eErrorTypeMachKernel);
        if (log) {
          log->Printf("NativeProcessDarwin::%s(): ::task_for_pid("
                      "stub_port = 0x%4.4x, pid = %llu, &task) "
                      "failed, err = 0x%8.8x (%s)",
                      __FUNCTION__, task_self, m_pid, err, error.AsCString());
        }
      }

      // Sleep a bit and try again
      ::usleep(usec_interval);
    }

    // We failed to get the task for the inferior process. Ensure that it is
    // cleared out.
    m_task = TASK_NULL;
  }
  return m_task;
}

void NativeProcessDarwin::AttachToInferior(MainLoop &mainloop, lldb::pid_t pid,
                                           Status &error) {
  error.SetErrorString("TODO: implement");
}

Status NativeProcessDarwin::PrivateResume() {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex);
  m_auto_resume_signo = m_sent_interrupt_signo;

  if (log) {
    if (m_auto_resume_signo)
      log->Printf("NativeProcessDarwin::%s(): task 0x%x resuming (with "
                  "unhandled interrupt signal %i)...",
                  __FUNCTION__, m_task, m_auto_resume_signo);
    else
      log->Printf("NativeProcessDarwin::%s(): task 0x%x resuming...",
                  __FUNCTION__, m_task);
  }

  error = ReplyToAllExceptions();
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): aborting, failed to "
                  "reply to exceptions: %s",
                  __FUNCTION__, error.AsCString());
    return error;
  }
  //    bool stepOverBreakInstruction = step;

  // Let the thread prepare to resume and see if any threads want us to step
  // over a breakpoint instruction (ProcessWillResume will modify the value of
  // stepOverBreakInstruction).
  m_thread_list.ProcessWillResume(*this, m_thread_actions);

  // Set our state accordingly
  if (m_thread_actions.NumActionsWithState(eStateStepping))
    SetState(eStateStepping);
  else
    SetState(eStateRunning);

  // Now resume our task.
  error = ResumeTask();
  return error;
}

Status NativeProcessDarwin::ReplyToAllExceptions() {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));

  TaskPortForProcessID(error);
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): no task port, aborting",
                  __FUNCTION__);
    return error;
  }

  std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex);
  if (m_exception_messages.empty()) {
    // We're done.
    return error;
  }

  size_t index = 0;
  for (auto &message : m_exception_messages) {
    if (log) {
      log->Printf("NativeProcessDarwin::%s(): replying to exception "
                  "%zu...",
                  __FUNCTION__, index++);
    }

    int thread_reply_signal = 0;

    const tid_t tid =
        m_thread_list.GetThreadIDByMachPortNumber(message.state.thread_port);
    const ResumeAction *action = nullptr;
    if (tid != LLDB_INVALID_THREAD_ID)
      action = m_thread_actions.GetActionForThread(tid, false);

    if (action) {
      thread_reply_signal = action->signal;
      if (thread_reply_signal)
        m_thread_actions.SetSignalHandledForThread(tid);
    }

    error = message.Reply(m_pid, m_task, thread_reply_signal);
    if (error.Fail() && log) {
      // We log any error here, but we don't stop the exception response
      // handling.
      log->Printf("NativeProcessDarwin::%s(): failed to reply to "
                  "exception: %s",
                  __FUNCTION__, error.AsCString());
      error.Clear();
    }
  }

  // Erase all exception message as we should have used and replied to them all
  // already.
  m_exception_messages.clear();
  return error;
}

Status NativeProcessDarwin::ResumeTask() {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  TaskPortForProcessID(error);
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): failed to get task port "
                  "for process when attempting to resume: %s",
                  __FUNCTION__, error.AsCString());
    return error;
  }
  if (m_task == TASK_NULL) {
    error.SetErrorString("task port retrieval succeeded but task port is "
                         "null when attempting to resume the task");
    return error;
  }

  if (log)
    log->Printf("NativeProcessDarwin::%s(): requesting resume of task "
                "0x%4.4x",
                __FUNCTION__, m_task);

  // Get the BasicInfo struct to verify that we're suspended before we try to
  // resume the task.
  struct task_basic_info task_info;
  error = GetTaskBasicInfo(m_task, &task_info);
  if (error.Fail()) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): failed to get task "
                  "BasicInfo when attempting to resume: %s",
                  __FUNCTION__, error.AsCString());
    return error;
  }

  // task_resume isn't counted like task_suspend calls are, so if the task is
  // not suspended, don't try and resume it since it is already running
  if (task_info.suspend_count > 0) {
    auto mach_err = ::task_resume(m_task);
    error.SetError(mach_err, eErrorTypeMachKernel);
    if (log) {
      if (error.Success())
        log->Printf("::task_resume(target_task = 0x%4.4x): success", m_task);
      else
        log->Printf("::task_resume(target_task = 0x%4.4x) error: %s", m_task,
                    error.AsCString());
    }
  } else {
    if (log)
      log->Printf("::task_resume(target_task = 0x%4.4x): ignored, "
                  "already running",
                  m_task);
  }

  return error;
}

bool NativeProcessDarwin::IsTaskValid() const {
  if (m_task == TASK_NULL)
    return false;

  struct task_basic_info task_info;
  return GetTaskBasicInfo(m_task, &task_info).Success();
}

bool NativeProcessDarwin::IsTaskValid(task_t task) const {
  if (task == TASK_NULL)
    return false;

  struct task_basic_info task_info;
  return GetTaskBasicInfo(task, &task_info).Success();
}

mach_port_t NativeProcessDarwin::GetExceptionPort() const {
  return m_exception_port;
}

bool NativeProcessDarwin::IsExceptionPortValid() const {
  return MACH_PORT_VALID(m_exception_port);
}

Status
NativeProcessDarwin::GetTaskBasicInfo(task_t task,
                                      struct task_basic_info *info) const {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  // Validate args.
  if (info == NULL) {
    error.SetErrorStringWithFormat("NativeProcessDarwin::%s(): mandatory "
                                   "info arg is null",
                                   __FUNCTION__);
    return error;
  }

  // Grab the task if we don't already have it.
  if (task == TASK_NULL) {
    error.SetErrorStringWithFormat("NativeProcessDarwin::%s(): given task "
                                   "is invalid",
                                   __FUNCTION__);
  }

  mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
  auto err = ::task_info(m_task, TASK_BASIC_INFO, (task_info_t)info, &count);
  error.SetError(err, eErrorTypeMachKernel);
  if (error.Fail()) {
    if (log)
      log->Printf("::task_info(target_task = 0x%4.4x, "
                  "flavor = TASK_BASIC_INFO, task_info_out => %p, "
                  "task_info_outCnt => %u) failed: %u (%s)",
                  m_task, info, count, error.GetError(), error.AsCString());
    return error;
  }

  Log *verbose_log(
      GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));
  if (verbose_log) {
    float user = (float)info->user_time.seconds +
                 (float)info->user_time.microseconds / 1000000.0f;
    float system = (float)info->user_time.seconds +
                   (float)info->user_time.microseconds / 1000000.0f;
    verbose_log->Printf("task_basic_info = { suspend_count = %i, "
                        "virtual_size = 0x%8.8llx, resident_size = "
                        "0x%8.8llx, user_time = %f, system_time = %f }",
                        info->suspend_count, (uint64_t)info->virtual_size,
                        (uint64_t)info->resident_size, user, system);
  }
  return error;
}

Status NativeProcessDarwin::SuspendTask() {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  if (m_task == TASK_NULL) {
    error.SetErrorString("task port is null, cannot suspend task");
    if (log)
      log->Printf("NativeProcessDarwin::%s() failed: %s", __FUNCTION__,
                  error.AsCString());
    return error;
  }

  auto mach_err = ::task_suspend(m_task);
  error.SetError(mach_err, eErrorTypeMachKernel);
  if (error.Fail() && log)
    log->Printf("::task_suspend(target_task = 0x%4.4x)", m_task);

  return error;
}

Status NativeProcessDarwin::Resume(const ResumeActionList &resume_actions) {
  Status error;
  Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));

  if (log)
    log->Printf("NativeProcessDarwin::%s() called", __FUNCTION__);

  if (CanResume()) {
    m_thread_actions = resume_actions;
    error = PrivateResume();
    return error;
  }

  auto state = GetState();
  if (state == eStateRunning) {
    if (log)
      log->Printf("NativeProcessDarwin::%s(): task 0x%x is already "
                  "running, ignoring...",
                  __FUNCTION__, TaskPortForProcessID(error));
    return error;
  }

  // We can't resume from this state.
  error.SetErrorStringWithFormat("task 0x%x has state %s, can't resume",
                                 TaskPortForProcessID(error),
                                 StateAsCString(state));
  return error;
}

Status NativeProcessDarwin::Halt() {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::Detach() {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::Signal(int signo) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::Interrupt() {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::Kill() {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::GetMemoryRegionInfo(lldb::addr_t load_addr,
                                                MemoryRegionInfo &range_info) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::ReadMemory(lldb::addr_t addr, void *buf,
                                       size_t size, size_t &bytes_read) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::ReadMemoryWithoutTrap(lldb::addr_t addr, void *buf,
                                                  size_t size,
                                                  size_t &bytes_read) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::WriteMemory(lldb::addr_t addr, const void *buf,
                                        size_t size, size_t &bytes_written) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::AllocateMemory(size_t size, uint32_t permissions,
                                           lldb::addr_t &addr) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::DeallocateMemory(lldb::addr_t addr) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

lldb::addr_t NativeProcessDarwin::GetSharedLibraryInfoAddress() {
  return LLDB_INVALID_ADDRESS;
}

size_t NativeProcessDarwin::UpdateThreads() { return 0; }

bool NativeProcessDarwin::GetArchitecture(ArchSpec &arch) const {
  return false;
}

Status NativeProcessDarwin::SetBreakpoint(lldb::addr_t addr, uint32_t size,
                                          bool hardware) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

void NativeProcessDarwin::DoStopIDBumped(uint32_t newBumpId) {}

Status NativeProcessDarwin::GetLoadedModuleFileSpec(const char *module_path,
                                                    FileSpec &file_spec) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

Status NativeProcessDarwin::GetFileLoadAddress(const llvm::StringRef &file_name,
                                               lldb::addr_t &load_addr) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}

// NativeProcessProtocol protected interface
Status NativeProcessDarwin::GetSoftwareBreakpointTrapOpcode(
    size_t trap_opcode_size_hint, size_t &actual_opcode_size,
    const uint8_t *&trap_opcode_bytes) {
  Status error;
  error.SetErrorString("TODO: implement");
  return error;
}
