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;
}