int main()

in lldb/tools/debugserver/source/debugserver.cpp [902:1700]


int main(int argc, char *argv[]) {
  // If debugserver is launched with DYLD_INSERT_LIBRARIES, unset it so we
  // don't spawn child processes with this enabled.
  unsetenv("DYLD_INSERT_LIBRARIES");

  const char *argv_sub_zero =
      argv[0]; // save a copy of argv[0] for error reporting post-launch

#if defined(__APPLE__)
  pthread_setname_np("main 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);
  }

  ::proc_set_wakemon_params(
      getpid(), 500,
      0); // Allow up to 500 wakeups/sec to avoid EXC_RESOURCE for normal use.
#endif
#endif

  g_isatty = ::isatty(STDIN_FILENO);

  //  ::printf ("uid=%u euid=%u gid=%u egid=%u\n",
  //            getuid(),
  //            geteuid(),
  //            getgid(),
  //            getegid());

  //    signal (SIGINT, signal_handler);
  signal(SIGPIPE, signal_handler);
  signal(SIGHUP, signal_handler);

  // We're always sitting in waitpid or kevent waiting on our target process'
  // death,
  // we don't need no stinking SIGCHLD's...

  sigset_t sigset;
  sigemptyset(&sigset);
  sigaddset(&sigset, SIGCHLD);
  sigprocmask(SIG_BLOCK, &sigset, NULL);

  g_remoteSP = std::make_shared<RNBRemote>();

  RNBRemote *remote = g_remoteSP.get();
  if (remote == NULL) {
    RNBLogSTDERR("error: failed to create a remote connection class\n");
    return -1;
  }

  RNBContext &ctx = remote->Context();

  int i;
  int attach_pid = INVALID_NUB_PROCESS;

  FILE *log_file = NULL;
  uint32_t log_flags = 0;
  // Parse our options
  int ch;
  int long_option_index = 0;
  int debug = 0;
  std::string compile_options;
  std::string waitfor_pid_name; // Wait for a process that starts with this name
  std::string attach_pid_name;
  std::string arch_name;
  std::string working_dir; // The new working directory to use for the inferior
  std::string unix_socket_name; // If we need to handshake with our parent
                                // process, an option will be passed down that
                                // specifies a unix socket name to use
  std::string named_pipe_path;  // If we need to handshake with our parent
                                // process, an option will be passed down that
                                // specifies a named pipe to use
  useconds_t waitfor_interval = 1000; // Time in usecs between process lists
                                      // polls when waiting for a process by
                                      // name, default 1 msec.
  useconds_t waitfor_duration =
      0; // Time in seconds to wait for a process by name, 0 means wait forever.
  bool no_stdio = false;
  bool reverse_connect = false; // Set to true by an option to indicate we
                                // should reverse connect to the host:port
                                // supplied as the first debugserver argument

#if !defined(DNBLOG_ENABLED)
  compile_options += "(no-logging) ";
#endif

  RNBRunLoopMode start_mode = eRNBRunLoopModeExit;

  char short_options[512];
  uint32_t short_options_idx = 0;

  // Handle the two case that don't have short options in g_long_options
  short_options[short_options_idx++] = 'k';
  short_options[short_options_idx++] = 't';

  for (i = 0; g_long_options[i].name != NULL; ++i) {
    if (isalpha(g_long_options[i].val)) {
      short_options[short_options_idx++] = g_long_options[i].val;
      switch (g_long_options[i].has_arg) {
      default:
      case no_argument:
        break;

      case optional_argument:
        short_options[short_options_idx++] = ':';
        short_options[short_options_idx++] = ':';
        break;
      case required_argument:
        short_options[short_options_idx++] = ':';
        break;
      }
    }
  }
  // NULL terminate the short option string.
  short_options[short_options_idx++] = '\0';

#if __GLIBC__
  optind = 0;
#else
  optreset = 1;
  optind = 1;
#endif

  bool forward_env = false;
  while ((ch = getopt_long_only(argc, argv, short_options, g_long_options,
                                &long_option_index)) != -1) {
    DNBLogDebug("option: ch == %c (0x%2.2x) --%s%c%s\n", ch, (uint8_t)ch,
                g_long_options[long_option_index].name,
                g_long_options[long_option_index].has_arg ? '=' : ' ',
                optarg ? optarg : "");
    switch (ch) {
    case 0: // Any optional that auto set themselves will return 0
      break;

    case 'A':
      if (optarg && optarg[0])
        arch_name.assign(optarg);
      break;

    case 'a':
      if (optarg && optarg[0]) {
        if (isdigit(optarg[0])) {
          char *end = NULL;
          attach_pid = static_cast<int>(strtoul(optarg, &end, 0));
          if (end == NULL || *end != '\0') {
            RNBLogSTDERR("error: invalid pid option '%s'\n", optarg);
            exit(4);
          }
        } else {
          attach_pid_name = optarg;
        }
        start_mode = eRNBRunLoopModeInferiorAttaching;
      }
      break;

    // --waitfor=NAME
    case 'w':
      if (optarg && optarg[0]) {
        waitfor_pid_name = optarg;
        start_mode = eRNBRunLoopModeInferiorAttaching;
      }
      break;

    // --waitfor-interval=USEC
    case 'i':
      if (optarg && optarg[0]) {
        char *end = NULL;
        waitfor_interval = static_cast<useconds_t>(strtoul(optarg, &end, 0));
        if (end == NULL || *end != '\0') {
          RNBLogSTDERR("error: invalid waitfor-interval option value '%s'.\n",
                       optarg);
          exit(6);
        }
      }
      break;

    // --waitfor-duration=SEC
    case 'd':
      if (optarg && optarg[0]) {
        char *end = NULL;
        waitfor_duration = static_cast<useconds_t>(strtoul(optarg, &end, 0));
        if (end == NULL || *end != '\0') {
          RNBLogSTDERR("error: invalid waitfor-duration option value '%s'.\n",
                       optarg);
          exit(7);
        }
      }
      break;

    case 'K':
      g_detach_on_error = false;
      break;
    case 'W':
      if (optarg && optarg[0])
        working_dir.assign(optarg);
      break;

    case 'x':
      if (optarg && optarg[0]) {
        if (strcasecmp(optarg, "auto") == 0)
          g_launch_flavor = eLaunchFlavorDefault;
        else if (strcasestr(optarg, "posix") == optarg) {
          DNBLog(
              "[LaunchAttach] launch flavor is posix_spawn via cmdline option");
          g_launch_flavor = eLaunchFlavorPosixSpawn;
        } else if (strcasestr(optarg, "fork") == optarg)
          g_launch_flavor = eLaunchFlavorForkExec;
#ifdef WITH_SPRINGBOARD
        else if (strcasestr(optarg, "spring") == optarg) {
          DNBLog(
              "[LaunchAttach] launch flavor is SpringBoard via cmdline option");
          g_launch_flavor = eLaunchFlavorSpringBoard;
        }
#endif
#ifdef WITH_BKS
        else if (strcasestr(optarg, "backboard") == optarg) {
          DNBLog("[LaunchAttach] launch flavor is BKS via cmdline option");
          g_launch_flavor = eLaunchFlavorBKS;
        }
#endif
#ifdef WITH_FBS
        else if (strcasestr(optarg, "frontboard") == optarg) {
          DNBLog("[LaunchAttach] launch flavor is FBS via cmdline option");
          g_launch_flavor = eLaunchFlavorFBS;
        }
#endif

        else {
          RNBLogSTDERR("error: invalid TYPE for the --launch=TYPE (-x TYPE) "
                       "option: '%s'\n",
                       optarg);
          RNBLogSTDERR("Valid values TYPE are:\n");
          RNBLogSTDERR(
              "  auto       Auto-detect the best launch method to use.\n");
          RNBLogSTDERR(
              "  posix      Launch the executable using posix_spawn.\n");
          RNBLogSTDERR(
              "  fork       Launch the executable using fork and exec.\n");
#ifdef WITH_SPRINGBOARD
          RNBLogSTDERR(
              "  spring     Launch the executable through Springboard.\n");
#endif
#ifdef WITH_BKS
          RNBLogSTDERR("  backboard  Launch the executable through BackBoard "
                       "Services.\n");
#endif
#ifdef WITH_FBS
          RNBLogSTDERR("  frontboard  Launch the executable through FrontBoard "
                       "Services.\n");
#endif
          exit(5);
        }
      }
      break;

    case 'l': // Set Log File
      if (optarg && optarg[0]) {
        if (strcasecmp(optarg, "stdout") == 0)
          log_file = stdout;
        else if (strcasecmp(optarg, "stderr") == 0)
          log_file = stderr;
        else {
          log_file = fopen(optarg, "w");
          if (log_file != NULL)
            setlinebuf(log_file);
        }

        if (log_file == NULL) {
          const char *errno_str = strerror(errno);
          RNBLogSTDERR(
              "Failed to open log file '%s' for writing: errno = %i (%s)",
              optarg, errno, errno_str ? errno_str : "unknown error");
        }
      }
      break;

    case 'f': // Log Flags
      if (optarg && optarg[0])
        log_flags = static_cast<uint32_t>(strtoul(optarg, NULL, 0));
      break;

    case 'g':
      debug = 1;
      DNBLogSetDebug(debug);
      break;

    case 't':
      g_applist_opt = 1;
      break;

    case 'k':
      g_lockdown_opt = 1;
      break;

    case 'r':
      // Do nothing, native regs is the default these days
      break;

    case 'R':
      reverse_connect = true;
      break;
    case 'v':
      DNBLogSetVerbose(1);
      break;

    case 'V':
      show_version_and_exit(0);
      break;

    case 's':
      ctx.GetSTDIN().assign(optarg);
      ctx.GetSTDOUT().assign(optarg);
      ctx.GetSTDERR().assign(optarg);
      break;

    case 'I':
      ctx.GetSTDIN().assign(optarg);
      break;

    case 'O':
      ctx.GetSTDOUT().assign(optarg);
      break;

    case 'E':
      ctx.GetSTDERR().assign(optarg);
      break;

    case 'n':
      no_stdio = true;
      break;

    case 'S':
      // Put debugserver into a new session. Terminals group processes
      // into sessions and when a special terminal key sequences
      // (like control+c) are typed they can cause signals to go out to
      // all processes in a session. Using this --setsid (-S) option
      // will cause debugserver to run in its own sessions and be free
      // from such issues.
      //
      // This is useful when debugserver is spawned from a command
      // line application that uses debugserver to do the debugging,
      // yet that application doesn't want debugserver receiving the
      // signals sent to the session (i.e. dying when anyone hits ^C).
      setsid();
      break;
    case 'D':
      g_disable_aslr = 1;
      break;

    case 'p':
      start_mode = eRNBRunLoopModePlatformMode;
      break;

    case 'u':
      unix_socket_name.assign(optarg);
      break;

    case 'P':
      named_pipe_path.assign(optarg);
      break;

    case 'e':
      // Pass a single specified environment variable down to the process that
      // gets launched
      remote->Context().PushEnvironment(optarg);
      break;

    case 'F':
      forward_env = true;
      break;

    case 'U':
      ctx.SetUnmaskSignals(true);
      break;

    case '2':
      // File descriptor passed to this process during fork/exec and is already
      // open and ready for communication.
      communication_fd = atoi(optarg);
      break;
    }
  }

  if (arch_name.empty()) {
#if defined(__arm__)
    arch_name.assign("arm");
#endif
  } else {
    DNBSetArchitecture(arch_name.c_str());
  }

  //    if (arch_name.empty())
  //    {
  //        fprintf(stderr, "error: no architecture was specified\n");
  //        exit (8);
  //    }
  // Skip any options we consumed with getopt_long_only
  argc -= optind;
  argv += optind;

  if (!working_dir.empty()) {
    if (remote->Context().SetWorkingDirectory(working_dir.c_str()) == false) {
      RNBLogSTDERR("error: working directory doesn't exist '%s'.\n",
                   working_dir.c_str());
      exit(8);
    }
  }

  remote->Context().SetDetachOnError(g_detach_on_error);

  remote->Initialize();

  // It is ok for us to set NULL as the logfile (this will disable any logging)

  if (log_file != NULL) {
    DNBLogSetLogCallback(FileLogCallback, log_file);
    // If our log file was set, yet we have no log flags, log everything!
    if (log_flags == 0)
      log_flags = LOG_ALL | LOG_RNB_ALL;

    DNBLogSetLogMask(log_flags);
  } else {
    // Enable DNB logging

    // if os_log() support is available, log through that.
    auto log_callback = OsLogger::GetLogFunction();
    if (log_callback) {
      DNBLogSetLogCallback(log_callback, nullptr);
      DNBLog("debugserver will use os_log for internal logging.");
    } else {
      // Fall back to ASL support.
      DNBLogSetLogCallback(ASLLogCallback, NULL);
      DNBLog("debugserver will use ASL for internal logging.");
    }
    DNBLogSetLogMask(log_flags);
  }

  if (DNBLogEnabled()) {
    for (i = 0; i < argc; i++)
      DNBLogDebug("argv[%i] = %s", i, argv[i]);
  }

  // as long as we're dropping remotenub in as a replacement for gdbserver,
  // explicitly note that this is not gdbserver.

  const char *in_translation = "";
  if (DNBDebugserverIsTranslated())
    in_translation = " (running under translation)";
  RNBLogSTDOUT("%s-%s %sfor %s%s.\n", DEBUGSERVER_PROGRAM_NAME,
               DEBUGSERVER_VERSION_STR, compile_options.c_str(), RNB_ARCH,
               in_translation);

  std::string host;
  int port = INT32_MAX;
  char str[PATH_MAX];
  str[0] = '\0';

  if (g_lockdown_opt == 0 && g_applist_opt == 0 && communication_fd == -1) {
    // Make sure we at least have port
    if (argc < 1) {
      show_usage_and_exit(1);
    }
    // accept 'localhost:' prefix on port number
    std::string host_specifier = argv[0];
    auto colon_location = host_specifier.rfind(':');
    if (colon_location != std::string::npos) {
      host = host_specifier.substr(0, colon_location);
      std::string port_str =
          host_specifier.substr(colon_location + 1, std::string::npos);
      char *end_ptr;
      port = strtoul(port_str.c_str(), &end_ptr, 0);
      if (end_ptr < port_str.c_str() + port_str.size())
        show_usage_and_exit(2);
      if (host.front() == '[' && host.back() == ']')
        host = host.substr(1, host.size() - 2);
      DNBLogDebug("host = '%s'  port = %i", host.c_str(), port);
    } else {
      // No hostname means "localhost"
      int items_scanned = ::sscanf(argv[0], "%i", &port);
      if (items_scanned == 1) {
        host = "127.0.0.1";
        DNBLogDebug("host = '%s'  port = %i", host.c_str(), port);
      } else if (argv[0][0] == '/') {
        port = INT32_MAX;
        strlcpy(str, argv[0], sizeof(str));
      } else {
        show_usage_and_exit(2);
      }
    }

    // We just used the 'host:port' or the '/path/file' arg...
    argc--;
    argv++;
  }

  //  If we know we're waiting to attach, we don't need any of this other info.
  if (start_mode != eRNBRunLoopModeInferiorAttaching &&
      start_mode != eRNBRunLoopModePlatformMode) {
    if (argc == 0 || g_lockdown_opt) {
      if (g_lockdown_opt != 0) {
        // Work around for SIGPIPE crashes due to posix_spawn issue.
        // We have to close STDOUT and STDERR, else the first time we
        // try and do any, we get SIGPIPE and die as posix_spawn is
        // doing bad things with our file descriptors at the moment.
        int null = open("/dev/null", O_RDWR);
        dup2(null, STDOUT_FILENO);
        dup2(null, STDERR_FILENO);
      } else if (g_applist_opt != 0) {
        DNBLog("debugserver running in --applist mode");
        // List all applications we are able to see
        std::string applist_plist;
        int err = ListApplications(applist_plist, false, false);
        if (err == 0) {
          fputs(applist_plist.c_str(), stdout);
        } else {
          RNBLogSTDERR("error: ListApplications returned error %i\n", err);
        }
        // Exit with appropriate error if we were asked to list the applications
        // with no other args were given (and we weren't trying to do this over
        // lockdown)
        return err;
      }

      DNBLogDebug("Get args from remote protocol...");
      start_mode = eRNBRunLoopModeGetStartModeFromRemoteProtocol;
    } else {
      start_mode = eRNBRunLoopModeInferiorLaunching;
      // Fill in the argv array in the context from the rest of our args.
      // Skip the name of this executable and the port number
      for (int i = 0; i < argc; i++) {
        DNBLogDebug("inferior_argv[%i] = '%s'", i, argv[i]);
        ctx.PushArgument(argv[i]);
      }
    }
  }

  if (start_mode == eRNBRunLoopModeExit)
    return -1;

  if (forward_env || start_mode == eRNBRunLoopModeInferiorLaunching) {
    // Pass the current environment down to the process that gets launched
    // This happens automatically in the "launching" mode. For the rest, we
    // only do that if the user explicitly requested this via --forward-env
    // argument.
    char **host_env = *_NSGetEnviron();
    char *env_entry;
    size_t i;
    for (i = 0; (env_entry = host_env[i]) != NULL; ++i)
      remote->Context().PushEnvironmentIfNeeded(env_entry);
  }

  RNBRunLoopMode mode = start_mode;
  char err_str[1024] = {'\0'};

  while (mode != eRNBRunLoopModeExit) {
    switch (mode) {
    case eRNBRunLoopModeGetStartModeFromRemoteProtocol:
#ifdef WITH_LOCKDOWN
      if (g_lockdown_opt) {
        if (!remote->Comm().IsConnected()) {
          if (remote->Comm().ConnectToService() != rnb_success) {
            RNBLogSTDERR(
                "Failed to get connection from a remote gdb process.\n");
            mode = eRNBRunLoopModeExit;
          } else if (g_applist_opt != 0) {
            // List all applications we are able to see
            DNBLog("debugserver running in applist mode under lockdown");
            std::string applist_plist;
            if (ListApplications(applist_plist, false, false) == 0) {
              DNBLogDebug("Task list: %s", applist_plist.c_str());

              remote->Comm().Write(applist_plist.c_str(), applist_plist.size());
              // Issue a read that will never yield any data until the other
              // side
              // closes the socket so this process doesn't just exit and cause
              // the
              // socket to close prematurely on the other end and cause data
              // loss.
              std::string buf;
              remote->Comm().Read(buf);
            }
            remote->Comm().Disconnect(false);
            mode = eRNBRunLoopModeExit;
            break;
          } else {
            // Start watching for remote packets
            remote->StartReadRemoteDataThread();
          }
        }
      } else
#endif
          if (port != INT32_MAX) {
        if (!ConnectRemote(remote, host.c_str(), port, reverse_connect,
                           named_pipe_path.c_str(), unix_socket_name.c_str()))
          mode = eRNBRunLoopModeExit;
      } else if (str[0] == '/') {
        if (remote->Comm().OpenFile(str))
          mode = eRNBRunLoopModeExit;
      } else if (communication_fd >= 0) {
        // We were passed a file descriptor to use during fork/exec that is
        // already open
        // in our process, so lets just use it!
        if (remote->Comm().useFD(communication_fd))
          mode = eRNBRunLoopModeExit;
        else
          remote->StartReadRemoteDataThread();
      }

      if (mode != eRNBRunLoopModeExit) {
        RNBLogSTDOUT("Got a connection, waiting for process information for "
                     "launching or attaching.\n");

        mode = RNBRunLoopGetStartModeFromRemote(remote);
      }
      break;

    case eRNBRunLoopModeInferiorAttaching:
      if (!waitfor_pid_name.empty()) {
        // Set our end wait time if we are using a waitfor-duration
        // option that may have been specified
        struct timespec attach_timeout_abstime, *timeout_ptr = NULL;
        if (waitfor_duration != 0) {
          DNBTimer::OffsetTimeOfDay(&attach_timeout_abstime, waitfor_duration,
                                    0);
          timeout_ptr = &attach_timeout_abstime;
        }
        nub_launch_flavor_t launch_flavor = g_launch_flavor;
        if (launch_flavor == eLaunchFlavorDefault)
          launch_flavor = default_launch_flavor(waitfor_pid_name.c_str());

        ctx.SetLaunchFlavor(launch_flavor);
        bool ignore_existing = false;
        RNBLogSTDOUT("Waiting to attach to process %s...\n",
                     waitfor_pid_name.c_str());
        nub_process_t pid = DNBProcessAttachWait(
            &ctx, waitfor_pid_name.c_str(), ignore_existing, timeout_ptr,
            waitfor_interval, err_str, sizeof(err_str));
        g_pid = pid;

        if (pid == INVALID_NUB_PROCESS) {
          ctx.LaunchStatus().SetError(-1, DNBError::Generic);
          if (err_str[0])
            ctx.LaunchStatus().SetErrorString(err_str);
          RNBLogSTDERR("error: failed to attach to process named: \"%s\" %s\n",
                       waitfor_pid_name.c_str(), err_str);
          mode = eRNBRunLoopModeExit;
        } else {
          ctx.SetProcessID(pid);
          mode = eRNBRunLoopModeInferiorExecuting;
        }
      } else if (attach_pid != INVALID_NUB_PROCESS) {

        RNBLogSTDOUT("Attaching to process %i...\n", attach_pid);
        nub_process_t attached_pid;
        mode = RNBRunLoopLaunchAttaching(remote, attach_pid, attached_pid);
        if (mode != eRNBRunLoopModeInferiorExecuting) {
          const char *error_str = remote->Context().LaunchStatus().AsString();
          RNBLogSTDERR("error: failed to attach process %i: %s\n", attach_pid,
                       error_str ? error_str : "unknown error.");
          mode = eRNBRunLoopModeExit;
        }
      } else if (!attach_pid_name.empty()) {
        struct timespec attach_timeout_abstime, *timeout_ptr = NULL;
        if (waitfor_duration != 0) {
          DNBTimer::OffsetTimeOfDay(&attach_timeout_abstime, waitfor_duration,
                                    0);
          timeout_ptr = &attach_timeout_abstime;
        }

        RNBLogSTDOUT("Attaching to process %s...\n", attach_pid_name.c_str());
        nub_process_t pid = DNBProcessAttachByName(
            attach_pid_name.c_str(), timeout_ptr, ctx.GetUnmaskSignals(),
            err_str, sizeof(err_str));
        g_pid = pid;
        if (pid == INVALID_NUB_PROCESS) {
          ctx.LaunchStatus().SetError(-1, DNBError::Generic);
          if (err_str[0])
            ctx.LaunchStatus().SetErrorString(err_str);
          RNBLogSTDERR("error: failed to attach to process named: \"%s\" %s\n",
                       waitfor_pid_name.c_str(), err_str);
          mode = eRNBRunLoopModeExit;
        } else {
          ctx.SetProcessID(pid);
          mode = eRNBRunLoopModeInferiorExecuting;
        }

      } else {
        RNBLogSTDERR(
            "error: asked to attach with empty name and invalid PID.\n");
        mode = eRNBRunLoopModeExit;
      }

      if (mode != eRNBRunLoopModeExit) {
        if (port != INT32_MAX) {
          if (!ConnectRemote(remote, host.c_str(), port, reverse_connect,
                             named_pipe_path.c_str(), unix_socket_name.c_str()))
            mode = eRNBRunLoopModeExit;
        } else if (str[0] == '/') {
          if (remote->Comm().OpenFile(str))
            mode = eRNBRunLoopModeExit;
        } else if (communication_fd >= 0) {
          // We were passed a file descriptor to use during fork/exec that is
          // already open
          // in our process, so lets just use it!
          if (remote->Comm().useFD(communication_fd))
            mode = eRNBRunLoopModeExit;
          else
            remote->StartReadRemoteDataThread();
        }

        if (mode != eRNBRunLoopModeExit)
          RNBLogSTDOUT("Waiting for debugger instructions for process %d.\n",
                       attach_pid);
      }
      break;

    case eRNBRunLoopModeInferiorLaunching: {
      mode = RNBRunLoopLaunchInferior(remote, ctx.GetSTDINPath(),
                                      ctx.GetSTDOUTPath(), ctx.GetSTDERRPath(),
                                      no_stdio);

      if (mode == eRNBRunLoopModeInferiorExecuting) {
        if (port != INT32_MAX) {
          if (!ConnectRemote(remote, host.c_str(), port, reverse_connect,
                             named_pipe_path.c_str(), unix_socket_name.c_str()))
            mode = eRNBRunLoopModeExit;
        } else if (str[0] == '/') {
          if (remote->Comm().OpenFile(str))
            mode = eRNBRunLoopModeExit;
        } else if (communication_fd >= 0) {
          // We were passed a file descriptor to use during fork/exec that is
          // already open
          // in our process, so lets just use it!
          if (remote->Comm().useFD(communication_fd))
            mode = eRNBRunLoopModeExit;
          else
            remote->StartReadRemoteDataThread();
        }

        if (mode != eRNBRunLoopModeExit) {
          const char *proc_name = "<unknown>";
          if (ctx.ArgumentCount() > 0)
            proc_name = ctx.ArgumentAtIndex(0);
          DNBLog("[LaunchAttach] Successfully launched %s (pid = %d).\n",
                 proc_name, ctx.ProcessID());
          RNBLogSTDOUT("Got a connection, launched process %s (pid = %d).\n",
                       proc_name, ctx.ProcessID());
        }
      } else {
        const char *error_str = remote->Context().LaunchStatus().AsString();
        RNBLogSTDERR("error: failed to launch process %s: %s\n", argv_sub_zero,
                     error_str ? error_str : "unknown error.");
      }
    } break;

    case eRNBRunLoopModeInferiorExecuting:
      mode = RNBRunLoopInferiorExecuting(remote);
      break;

    case eRNBRunLoopModePlatformMode:
      if (port != INT32_MAX) {
        if (!ConnectRemote(remote, host.c_str(), port, reverse_connect,
                           named_pipe_path.c_str(), unix_socket_name.c_str()))
          mode = eRNBRunLoopModeExit;
      } else if (str[0] == '/') {
        if (remote->Comm().OpenFile(str))
          mode = eRNBRunLoopModeExit;
      } else if (communication_fd >= 0) {
        // We were passed a file descriptor to use during fork/exec that is
        // already open
        // in our process, so lets just use it!
        if (remote->Comm().useFD(communication_fd))
          mode = eRNBRunLoopModeExit;
        else
          remote->StartReadRemoteDataThread();
      }

      if (mode != eRNBRunLoopModeExit)
        mode = RNBRunLoopPlatform(remote);
      break;

    default:
      mode = eRNBRunLoopModeExit;
      break;
    case eRNBRunLoopModeExit:
      break;
    }
  }

  remote->StopReadRemoteDataThread();
  remote->Context().SetProcessID(INVALID_NUB_PROCESS);
  RNBLogSTDOUT("Exiting.\n");

  return 0;
}