debugserver task control and exception thread

llvm-project/lldb/tools/debugserver/source/MacOSX/MachTask.mm · 1064 lines · browse source

LLDB debugserver’s task-port acquisition, task suspension, exception-port save/restore, and exception receive thread. This is the closest upstream implementation comparison for machdb’s launch, attach, stop, continue, and detach lifecycle.

MachTask::Suspend source
---------------------------------------------------------------------- MachTask::Suspend ----------------------------------------------------------------------
kern_return_t MachTask::Suspend() {
  DNBError err;
  task_t task = TaskPort();
  err = ::task_suspend(task);
  if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
    err.LogThreaded("::task_suspend ( target_task = 0x%4.4x )", task);
  return err.Status();
}
MachTask::Resume source
---------------------------------------------------------------------- MachTask::Resume ----------------------------------------------------------------------
kern_return_t MachTask::Resume() {
  struct task_basic_info task_info;
  task_t task = TaskPort();
  if (task == TASK_NULL)
    return KERN_INVALID_ARGUMENT;

  DNBError err;
  err = BasicInfo(task, &task_info);

  if (err.Success()) {
    if (m_do_double_resume && task_info.suspend_count == 2) {
      err = ::task_resume(task);
      if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
        err.LogThreaded("::task_resume double-resume after exec-start-stopped "
                        "( target_task = 0x%4.4x )", task);
    }
    m_do_double_resume = false;
      
    // task_resume isn't counted like task_suspend calls are, 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) {
      err = ::task_resume(task);
      if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
        err.LogThreaded("::task_resume ( target_task = 0x%4.4x )", task);
    }
  }
  return err.Status();
}
MachTask::SaveExceptionPortInfo source
---------------------------------------------------------------------- MachTask::SaveExceptionPortInfo ----------------------------------------------------------------------
kern_return_t MachTask::SaveExceptionPortInfo() {
  return m_exc_port_info.Save(TaskPort());
}
MachTask::RestoreExceptionPortInfo source
---------------------------------------------------------------------- MachTask::RestoreExceptionPortInfo ----------------------------------------------------------------------
kern_return_t MachTask::RestoreExceptionPortInfo() {
  return m_exc_port_info.Restore(TaskPort());
}
MachTask::TaskPortForProcessID source
---------------------------------------------------------------------- MachTask::TaskPortForProcessID ----------------------------------------------------------------------
task_t MachTask::TaskPortForProcessID(DNBError &err, bool force) {
  if (((m_task == TASK_NULL) || force) && m_process != NULL)
    m_task = MachTask::TaskPortForProcessID(m_process->ProcessID(), err);
  return m_task;
}
MachTask::StartExceptionThread source
bool MachTask::StartExceptionThread(
        const RNBContext::IgnoredExceptions &ignored_exceptions, 
        DNBError &err) {
  DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__);

  task_t task = TaskPortForProcessID(err);
  if (MachTask::IsValid(task)) {
    // Got the mach port for the current process
    mach_port_t task_self = mach_task_self();

    // Allocate an exception port that we will use to track our child process
    err = ::mach_port_allocate(task_self, MACH_PORT_RIGHT_RECEIVE,
                               &m_exception_port);
    if (err.Fail())
      return false;

    // Add the ability to send messages on the new exception port
    err = ::mach_port_insert_right(task_self, m_exception_port,
                                   m_exception_port, MACH_MSG_TYPE_MAKE_SEND);
    if (err.Fail())
      return false;

    // Save the original state of the exception ports for our child process
    SaveExceptionPortInfo();

    // We weren't able to save the info for our exception ports, we must stop...
    if (m_exc_port_info.mask == 0) {
      err.SetErrorString("failed to get exception port info");
      return false;
    }
… more in source
MachTask::ShutDownExceptionThread source
void MachTask::ShutDownExceptionThread() {
  DNBError err;
  
  if (!m_exception_thread)
    return;

  err = RestoreExceptionPortInfo();

  // NULL our exception port and let our exception thread exit
  mach_port_t exception_port = m_exception_port;
  m_exception_port = 0;

  err.SetError(::pthread_cancel(m_exception_thread), DNBError::POSIX);
  if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
    err.LogThreaded("::pthread_cancel ( thread = %p )", m_exception_thread);

  err.SetError(::pthread_join(m_exception_thread, NULL), DNBError::POSIX);
  if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
    err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)",
                    m_exception_thread);
                    
  m_exception_thread = nullptr;

  // Deallocate our exception port that we used to track our child process
  mach_port_t task_self = mach_task_self();
  err = ::mach_port_deallocate(task_self, exception_port);
  if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
    err.LogThreaded("::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )",
                    task_self, exception_port);
… more in source
MachTask::ExceptionThread source
void *MachTask::ExceptionThread(void *arg) {
  if (arg == NULL)
    return NULL;

  MachTask *mach_task = (MachTask *)arg;
  MachProcess *mach_proc = mach_task->Process();
  DNBLogThreadedIf(LOG_EXCEPTIONS,
                   "MachTask::%s ( arg = %p ) starting thread...", __FUNCTION__,
                   arg);

#if defined(__APPLE__)
  pthread_setname_np("exception monitoring thread");
#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
#endif

  // 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.
… more in source