1*d676074dSJason Molenda /* 2*d676074dSJason Molenda * libdebugserver.cpp 3*d676074dSJason Molenda * debugserver 4*d676074dSJason Molenda * 5*d676074dSJason Molenda * Copyright 2008-2012 Apple Inc. All rights reserved. 6*d676074dSJason Molenda * 7*d676074dSJason Molenda */ 8*d676074dSJason Molenda 9*d676074dSJason Molenda #include <sys/socket.h> 10*d676074dSJason Molenda #include <sys/types.h> 11*d676074dSJason Molenda #include <errno.h> 12*d676074dSJason Molenda #include <getopt.h> 13*d676074dSJason Molenda #include <netinet/in.h> 14*d676074dSJason Molenda #include <sys/select.h> 15*d676074dSJason Molenda #include <sys/sysctl.h> 16*d676074dSJason Molenda 17*d676074dSJason Molenda #include "DNB.h" 18*d676074dSJason Molenda #include "DNBLog.h" 19*d676074dSJason Molenda #include "DNBTimer.h" 20*d676074dSJason Molenda #include "PseudoTerminal.h" 21*d676074dSJason Molenda #include "RNBContext.h" 22*d676074dSJason Molenda #include "RNBServices.h" 23*d676074dSJason Molenda #include "RNBSocket.h" 24*d676074dSJason Molenda #include "RNBRemote.h" 25*d676074dSJason Molenda #include "SysSignal.h" 26*d676074dSJason Molenda 27*d676074dSJason Molenda //---------------------------------------------------------------------- 28*d676074dSJason Molenda // Run loop modes which determine which run loop function will be called 29*d676074dSJason Molenda //---------------------------------------------------------------------- 30*d676074dSJason Molenda typedef enum 31*d676074dSJason Molenda { 32*d676074dSJason Molenda eRNBRunLoopModeInvalid = 0, 33*d676074dSJason Molenda eRNBRunLoopModeGetStartModeFromRemoteProtocol, 34*d676074dSJason Molenda eRNBRunLoopModeInferiorExecuting, 35*d676074dSJason Molenda eRNBRunLoopModeExit 36*d676074dSJason Molenda } RNBRunLoopMode; 37*d676074dSJason Molenda 38*d676074dSJason Molenda 39*d676074dSJason Molenda //---------------------------------------------------------------------- 40*d676074dSJason Molenda // Global Variables 41*d676074dSJason Molenda //---------------------------------------------------------------------- 42*d676074dSJason Molenda RNBRemoteSP g_remoteSP; 43*d676074dSJason Molenda int g_disable_aslr = 0; 44*d676074dSJason Molenda int g_isatty = 0; 45*d676074dSJason Molenda 46*d676074dSJason Molenda #define RNBLogSTDOUT(fmt, ...) do { if (g_isatty) { fprintf(stdout, fmt, ## __VA_ARGS__); } else { _DNBLog(0, fmt, ## __VA_ARGS__); } } while (0) 47*d676074dSJason Molenda #define RNBLogSTDERR(fmt, ...) do { if (g_isatty) { fprintf(stderr, fmt, ## __VA_ARGS__); } else { _DNBLog(0, fmt, ## __VA_ARGS__); } } while (0) 48*d676074dSJason Molenda 49*d676074dSJason Molenda 50*d676074dSJason Molenda //---------------------------------------------------------------------- 51*d676074dSJason Molenda // Get our program path and arguments from the remote connection. 52*d676074dSJason Molenda // We will need to start up the remote connection without a PID, get the 53*d676074dSJason Molenda // arguments, wait for the new process to finish launching and hit its 54*d676074dSJason Molenda // entry point, and then return the run loop mode that should come next. 55*d676074dSJason Molenda //---------------------------------------------------------------------- 56*d676074dSJason Molenda RNBRunLoopMode 57*d676074dSJason Molenda RNBRunLoopGetStartModeFromRemote (RNBRemoteSP &remoteSP) 58*d676074dSJason Molenda { 59*d676074dSJason Molenda std::string packet; 60*d676074dSJason Molenda 61*d676074dSJason Molenda if (remoteSP.get() != NULL) 62*d676074dSJason Molenda { 63*d676074dSJason Molenda RNBRemote* remote = remoteSP.get(); 64*d676074dSJason Molenda RNBContext& ctx = remote->Context(); 65*d676074dSJason Molenda uint32_t event_mask = RNBContext::event_read_packet_available; 66*d676074dSJason Molenda 67*d676074dSJason Molenda // Spin waiting to get the A packet. 68*d676074dSJason Molenda while (1) 69*d676074dSJason Molenda { 70*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MAX, "%s ctx.Events().WaitForSetEvents( 0x%08x ) ...",__FUNCTION__, event_mask); 71*d676074dSJason Molenda nub_event_t set_events = ctx.Events().WaitForSetEvents(event_mask); 72*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MAX, "%s ctx.Events().WaitForSetEvents( 0x%08x ) => 0x%08x", __FUNCTION__, event_mask, set_events); 73*d676074dSJason Molenda 74*d676074dSJason Molenda if (set_events & RNBContext::event_read_packet_available) 75*d676074dSJason Molenda { 76*d676074dSJason Molenda rnb_err_t err = rnb_err; 77*d676074dSJason Molenda RNBRemote::PacketEnum type; 78*d676074dSJason Molenda 79*d676074dSJason Molenda err = remote->HandleReceivedPacket (&type); 80*d676074dSJason Molenda 81*d676074dSJason Molenda // check if we tried to attach to a process 82*d676074dSJason Molenda if (type == RNBRemote::vattach || type == RNBRemote::vattachwait) 83*d676074dSJason Molenda { 84*d676074dSJason Molenda if (err == rnb_success) 85*d676074dSJason Molenda return eRNBRunLoopModeInferiorExecuting; 86*d676074dSJason Molenda else 87*d676074dSJason Molenda { 88*d676074dSJason Molenda RNBLogSTDERR ("error: attach failed."); 89*d676074dSJason Molenda return eRNBRunLoopModeExit; 90*d676074dSJason Molenda } 91*d676074dSJason Molenda } 92*d676074dSJason Molenda 93*d676074dSJason Molenda 94*d676074dSJason Molenda if (err == rnb_success) 95*d676074dSJason Molenda { 96*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s Got success...",__FUNCTION__); 97*d676074dSJason Molenda continue; 98*d676074dSJason Molenda } 99*d676074dSJason Molenda else if (err == rnb_not_connected) 100*d676074dSJason Molenda { 101*d676074dSJason Molenda RNBLogSTDERR ("error: connection lost."); 102*d676074dSJason Molenda return eRNBRunLoopModeExit; 103*d676074dSJason Molenda } 104*d676074dSJason Molenda else 105*d676074dSJason Molenda { 106*d676074dSJason Molenda // a catch all for any other gdb remote packets that failed 107*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s Error getting packet.",__FUNCTION__); 108*d676074dSJason Molenda continue; 109*d676074dSJason Molenda } 110*d676074dSJason Molenda 111*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "#### %s", __FUNCTION__); 112*d676074dSJason Molenda } 113*d676074dSJason Molenda else 114*d676074dSJason Molenda { 115*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s Connection closed before getting \"A\" packet.", __FUNCTION__); 116*d676074dSJason Molenda return eRNBRunLoopModeExit; 117*d676074dSJason Molenda } 118*d676074dSJason Molenda } 119*d676074dSJason Molenda } 120*d676074dSJason Molenda return eRNBRunLoopModeExit; 121*d676074dSJason Molenda } 122*d676074dSJason Molenda 123*d676074dSJason Molenda 124*d676074dSJason Molenda //---------------------------------------------------------------------- 125*d676074dSJason Molenda // Watch for signals: 126*d676074dSJason Molenda // SIGINT: so we can halt our inferior. (disabled for now) 127*d676074dSJason Molenda // SIGPIPE: in case our child process dies 128*d676074dSJason Molenda //---------------------------------------------------------------------- 129*d676074dSJason Molenda nub_process_t g_pid; 130*d676074dSJason Molenda int g_sigpipe_received = 0; 131*d676074dSJason Molenda void 132*d676074dSJason Molenda signal_handler(int signo) 133*d676074dSJason Molenda { 134*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (%s)", __FUNCTION__, SysSignal::Name(signo)); 135*d676074dSJason Molenda 136*d676074dSJason Molenda switch (signo) 137*d676074dSJason Molenda { 138*d676074dSJason Molenda // case SIGINT: 139*d676074dSJason Molenda // DNBProcessKill (g_pid, signo); 140*d676074dSJason Molenda // break; 141*d676074dSJason Molenda 142*d676074dSJason Molenda case SIGPIPE: 143*d676074dSJason Molenda g_sigpipe_received = 1; 144*d676074dSJason Molenda break; 145*d676074dSJason Molenda } 146*d676074dSJason Molenda } 147*d676074dSJason Molenda 148*d676074dSJason Molenda // Return the new run loop mode based off of the current process state 149*d676074dSJason Molenda RNBRunLoopMode 150*d676074dSJason Molenda HandleProcessStateChange (RNBRemoteSP &remote, bool initialize) 151*d676074dSJason Molenda { 152*d676074dSJason Molenda RNBContext& ctx = remote->Context(); 153*d676074dSJason Molenda nub_process_t pid = ctx.ProcessID(); 154*d676074dSJason Molenda 155*d676074dSJason Molenda if (pid == INVALID_NUB_PROCESS) 156*d676074dSJason Molenda { 157*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "#### %s error: pid invalid, exiting...", __FUNCTION__); 158*d676074dSJason Molenda return eRNBRunLoopModeExit; 159*d676074dSJason Molenda } 160*d676074dSJason Molenda nub_state_t pid_state = DNBProcessGetState (pid); 161*d676074dSJason Molenda 162*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (&remote, initialize=%i) pid_state = %s", __FUNCTION__, (int)initialize, DNBStateAsString (pid_state)); 163*d676074dSJason Molenda 164*d676074dSJason Molenda switch (pid_state) 165*d676074dSJason Molenda { 166*d676074dSJason Molenda case eStateInvalid: 167*d676074dSJason Molenda case eStateUnloaded: 168*d676074dSJason Molenda // Something bad happened 169*d676074dSJason Molenda return eRNBRunLoopModeExit; 170*d676074dSJason Molenda break; 171*d676074dSJason Molenda 172*d676074dSJason Molenda case eStateAttaching: 173*d676074dSJason Molenda case eStateLaunching: 174*d676074dSJason Molenda return eRNBRunLoopModeInferiorExecuting; 175*d676074dSJason Molenda 176*d676074dSJason Molenda case eStateSuspended: 177*d676074dSJason Molenda case eStateCrashed: 178*d676074dSJason Molenda case eStateStopped: 179*d676074dSJason Molenda if (initialize == false) 180*d676074dSJason Molenda { 181*d676074dSJason Molenda // Compare the last stop count to our current notion of a stop count 182*d676074dSJason Molenda // to make sure we don't notify more than once for a given stop. 183*d676074dSJason Molenda nub_size_t prev_pid_stop_count = ctx.GetProcessStopCount(); 184*d676074dSJason Molenda bool pid_stop_count_changed = ctx.SetProcessStopCount(DNBProcessGetStopCount(pid)); 185*d676074dSJason Molenda if (pid_stop_count_changed) 186*d676074dSJason Molenda { 187*d676074dSJason Molenda remote->FlushSTDIO(); 188*d676074dSJason Molenda 189*d676074dSJason Molenda if (ctx.GetProcessStopCount() == 1) 190*d676074dSJason Molenda { 191*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (&remote, initialize=%i) pid_state = %s pid_stop_count %u (old %u)) Notify??? no, first stop...", __FUNCTION__, (int)initialize, DNBStateAsString (pid_state), ctx.GetProcessStopCount(), prev_pid_stop_count); 192*d676074dSJason Molenda } 193*d676074dSJason Molenda else 194*d676074dSJason Molenda { 195*d676074dSJason Molenda 196*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (&remote, initialize=%i) pid_state = %s pid_stop_count %u (old %u)) Notify??? YES!!!", __FUNCTION__, (int)initialize, DNBStateAsString (pid_state), ctx.GetProcessStopCount(), prev_pid_stop_count); 197*d676074dSJason Molenda remote->NotifyThatProcessStopped (); 198*d676074dSJason Molenda } 199*d676074dSJason Molenda } 200*d676074dSJason Molenda else 201*d676074dSJason Molenda { 202*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "%s (&remote, initialize=%i) pid_state = %s pid_stop_count %u (old %u)) Notify??? skipping...", __FUNCTION__, (int)initialize, DNBStateAsString (pid_state), ctx.GetProcessStopCount(), prev_pid_stop_count); 203*d676074dSJason Molenda } 204*d676074dSJason Molenda } 205*d676074dSJason Molenda return eRNBRunLoopModeInferiorExecuting; 206*d676074dSJason Molenda 207*d676074dSJason Molenda case eStateStepping: 208*d676074dSJason Molenda case eStateRunning: 209*d676074dSJason Molenda return eRNBRunLoopModeInferiorExecuting; 210*d676074dSJason Molenda 211*d676074dSJason Molenda case eStateExited: 212*d676074dSJason Molenda remote->HandlePacket_last_signal(NULL); 213*d676074dSJason Molenda return eRNBRunLoopModeExit; 214*d676074dSJason Molenda case eStateDetached: 215*d676074dSJason Molenda return eRNBRunLoopModeExit; 216*d676074dSJason Molenda 217*d676074dSJason Molenda } 218*d676074dSJason Molenda 219*d676074dSJason Molenda // Catch all... 220*d676074dSJason Molenda return eRNBRunLoopModeExit; 221*d676074dSJason Molenda } 222*d676074dSJason Molenda // This function handles the case where our inferior program is stopped and 223*d676074dSJason Molenda // we are waiting for gdb remote protocol packets. When a packet occurs that 224*d676074dSJason Molenda // makes the inferior run, we need to leave this function with a new state 225*d676074dSJason Molenda // as the return code. 226*d676074dSJason Molenda RNBRunLoopMode 227*d676074dSJason Molenda RNBRunLoopInferiorExecuting (RNBRemoteSP &remote) 228*d676074dSJason Molenda { 229*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_MINIMAL, "#### %s", __FUNCTION__); 230*d676074dSJason Molenda RNBContext& ctx = remote->Context(); 231*d676074dSJason Molenda 232*d676074dSJason Molenda // Init our mode and set 'is_running' based on the current process state 233*d676074dSJason Molenda RNBRunLoopMode mode = HandleProcessStateChange (remote, true); 234*d676074dSJason Molenda 235*d676074dSJason Molenda while (ctx.ProcessID() != INVALID_NUB_PROCESS) 236*d676074dSJason Molenda { 237*d676074dSJason Molenda 238*d676074dSJason Molenda std::string set_events_str; 239*d676074dSJason Molenda uint32_t event_mask = ctx.NormalEventBits(); 240*d676074dSJason Molenda 241*d676074dSJason Molenda if (!ctx.ProcessStateRunning()) 242*d676074dSJason Molenda { 243*d676074dSJason Molenda // Clear the stdio bits if we are not running so we don't send any async packets 244*d676074dSJason Molenda event_mask &= ~RNBContext::event_proc_stdio_available; 245*d676074dSJason Molenda } 246*d676074dSJason Molenda 247*d676074dSJason Molenda // We want to make sure we consume all process state changes and have 248*d676074dSJason Molenda // whomever is notifying us to wait for us to reset the event bit before 249*d676074dSJason Molenda // continuing. 250*d676074dSJason Molenda //ctx.Events().SetResetAckMask (RNBContext::event_proc_state_changed); 251*d676074dSJason Molenda 252*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_EVENTS, "%s ctx.Events().WaitForSetEvents(0x%08x) ...",__FUNCTION__, event_mask); 253*d676074dSJason Molenda nub_event_t set_events = ctx.Events().WaitForSetEvents(event_mask); 254*d676074dSJason Molenda DNBLogThreadedIf (LOG_RNB_EVENTS, "%s ctx.Events().WaitForSetEvents(0x%08x) => 0x%08x (%s)",__FUNCTION__, event_mask, set_events, ctx.EventsAsString(set_events, set_events_str)); 255*d676074dSJason Molenda 256*d676074dSJason Molenda if (set_events) 257*d676074dSJason Molenda { 258*d676074dSJason Molenda if ((set_events & RNBContext::event_proc_thread_exiting) || 259*d676074dSJason Molenda (set_events & RNBContext::event_proc_stdio_available)) 260*d676074dSJason Molenda { 261*d676074dSJason Molenda remote->FlushSTDIO(); 262*d676074dSJason Molenda } 263*d676074dSJason Molenda 264*d676074dSJason Molenda if (set_events & RNBContext::event_read_packet_available) 265*d676074dSJason Molenda { 266*d676074dSJason Molenda // handleReceivedPacket will take care of resetting the 267*d676074dSJason Molenda // event_read_packet_available events when there are no more... 268*d676074dSJason Molenda set_events ^= RNBContext::event_read_packet_available; 269*d676074dSJason Molenda 270*d676074dSJason Molenda if (ctx.ProcessStateRunning()) 271*d676074dSJason Molenda { 272*d676074dSJason Molenda if (remote->HandleAsyncPacket() == rnb_not_connected) 273*d676074dSJason Molenda { 274*d676074dSJason Molenda // TODO: connect again? Exit? 275*d676074dSJason Molenda } 276*d676074dSJason Molenda } 277*d676074dSJason Molenda else 278*d676074dSJason Molenda { 279*d676074dSJason Molenda if (remote->HandleReceivedPacket() == rnb_not_connected) 280*d676074dSJason Molenda { 281*d676074dSJason Molenda // TODO: connect again? Exit? 282*d676074dSJason Molenda } 283*d676074dSJason Molenda } 284*d676074dSJason Molenda } 285*d676074dSJason Molenda 286*d676074dSJason Molenda if (set_events & RNBContext::event_proc_state_changed) 287*d676074dSJason Molenda { 288*d676074dSJason Molenda mode = HandleProcessStateChange (remote, false); 289*d676074dSJason Molenda ctx.Events().ResetEvents(RNBContext::event_proc_state_changed); 290*d676074dSJason Molenda set_events ^= RNBContext::event_proc_state_changed; 291*d676074dSJason Molenda } 292*d676074dSJason Molenda 293*d676074dSJason Molenda if (set_events & RNBContext::event_proc_thread_exiting) 294*d676074dSJason Molenda { 295*d676074dSJason Molenda mode = eRNBRunLoopModeExit; 296*d676074dSJason Molenda } 297*d676074dSJason Molenda 298*d676074dSJason Molenda if (set_events & RNBContext::event_read_thread_exiting) 299*d676074dSJason Molenda { 300*d676074dSJason Molenda // Out remote packet receiving thread exited, exit for now. 301*d676074dSJason Molenda if (ctx.HasValidProcessID()) 302*d676074dSJason Molenda { 303*d676074dSJason Molenda // TODO: We should add code that will leave the current process 304*d676074dSJason Molenda // in its current state and listen for another connection... 305*d676074dSJason Molenda if (ctx.ProcessStateRunning()) 306*d676074dSJason Molenda { 307*d676074dSJason Molenda DNBProcessKill (ctx.ProcessID(), SIGINT); 308*d676074dSJason Molenda } 309*d676074dSJason Molenda } 310*d676074dSJason Molenda mode = eRNBRunLoopModeExit; 311*d676074dSJason Molenda } 312*d676074dSJason Molenda } 313*d676074dSJason Molenda 314*d676074dSJason Molenda // Reset all event bits that weren't reset for now... 315*d676074dSJason Molenda if (set_events != 0) 316*d676074dSJason Molenda ctx.Events().ResetEvents(set_events); 317*d676074dSJason Molenda 318*d676074dSJason Molenda if (mode != eRNBRunLoopModeInferiorExecuting) 319*d676074dSJason Molenda break; 320*d676074dSJason Molenda } 321*d676074dSJason Molenda 322*d676074dSJason Molenda return mode; 323*d676074dSJason Molenda } 324*d676074dSJason Molenda 325*d676074dSJason Molenda void 326*d676074dSJason Molenda ASLLogCallback(void *baton, uint32_t flags, const char *format, va_list args) 327*d676074dSJason Molenda { 328*d676074dSJason Molenda #if 0 329*d676074dSJason Molenda vprintf(format, args); 330*d676074dSJason Molenda #endif 331*d676074dSJason Molenda } 332*d676074dSJason Molenda 333*d676074dSJason Molenda extern "C" int 334*d676074dSJason Molenda debug_server_main(int fd) 335*d676074dSJason Molenda { 336*d676074dSJason Molenda #if 1 337*d676074dSJason Molenda g_isatty = 0; 338*d676074dSJason Molenda #else 339*d676074dSJason Molenda g_isatty = ::isatty (STDIN_FILENO); 340*d676074dSJason Molenda 341*d676074dSJason Molenda DNBLogSetDebug(1); 342*d676074dSJason Molenda DNBLogSetVerbose(1); 343*d676074dSJason Molenda DNBLogSetLogMask(-1); 344*d676074dSJason Molenda DNBLogSetLogCallback(ASLLogCallback, NULL); 345*d676074dSJason Molenda #endif 346*d676074dSJason Molenda 347*d676074dSJason Molenda signal (SIGPIPE, signal_handler); 348*d676074dSJason Molenda 349*d676074dSJason Molenda g_remoteSP.reset (new RNBRemote); 350*d676074dSJason Molenda 351*d676074dSJason Molenda RNBRemote *remote = g_remoteSP.get(); 352*d676074dSJason Molenda if (remote == NULL) 353*d676074dSJason Molenda { 354*d676074dSJason Molenda RNBLogSTDERR ("error: failed to create a remote connection class\n"); 355*d676074dSJason Molenda return -1; 356*d676074dSJason Molenda } 357*d676074dSJason Molenda 358*d676074dSJason Molenda 359*d676074dSJason Molenda RNBRunLoopMode mode = eRNBRunLoopModeGetStartModeFromRemoteProtocol; 360*d676074dSJason Molenda 361*d676074dSJason Molenda while (mode != eRNBRunLoopModeExit) 362*d676074dSJason Molenda { 363*d676074dSJason Molenda switch (mode) 364*d676074dSJason Molenda { 365*d676074dSJason Molenda case eRNBRunLoopModeGetStartModeFromRemoteProtocol: 366*d676074dSJason Molenda if (g_remoteSP->Comm().useFD(fd) == rnb_success) { 367*d676074dSJason Molenda RNBLogSTDOUT("Starting remote data thread.\n"); 368*d676074dSJason Molenda g_remoteSP->StartReadRemoteDataThread(); 369*d676074dSJason Molenda 370*d676074dSJason Molenda RNBLogSTDOUT("Waiting for start mode from remote.\n"); 371*d676074dSJason Molenda mode = RNBRunLoopGetStartModeFromRemote(g_remoteSP); 372*d676074dSJason Molenda } 373*d676074dSJason Molenda else 374*d676074dSJason Molenda { 375*d676074dSJason Molenda mode = eRNBRunLoopModeExit; 376*d676074dSJason Molenda } 377*d676074dSJason Molenda break; 378*d676074dSJason Molenda 379*d676074dSJason Molenda case eRNBRunLoopModeInferiorExecuting: 380*d676074dSJason Molenda mode = RNBRunLoopInferiorExecuting(g_remoteSP); 381*d676074dSJason Molenda break; 382*d676074dSJason Molenda 383*d676074dSJason Molenda default: 384*d676074dSJason Molenda mode = eRNBRunLoopModeExit; 385*d676074dSJason Molenda break; 386*d676074dSJason Molenda 387*d676074dSJason Molenda case eRNBRunLoopModeExit: 388*d676074dSJason Molenda break; 389*d676074dSJason Molenda } 390*d676074dSJason Molenda } 391*d676074dSJason Molenda 392*d676074dSJason Molenda g_remoteSP->StopReadRemoteDataThread (); 393*d676074dSJason Molenda g_remoteSP->Context().SetProcessID(INVALID_NUB_PROCESS); 394*d676074dSJason Molenda 395*d676074dSJason Molenda return 0; 396*d676074dSJason Molenda } 397