Added an option to "process detach" to keep the process stopped, if the process plugin (or in the
case of ProcessGDBRemote the stub we are talking to) know how to do that.

rdar://problem/13680832


git-svn-id: https://llvm.org/svn/llvm-project/lldb/trunk@180831 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/lldb/API/SBProcess.h b/include/lldb/API/SBProcess.h
index 73ef89d..288d7d0 100644
--- a/include/lldb/API/SBProcess.h
+++ b/include/lldb/API/SBProcess.h
@@ -205,6 +205,9 @@
     Detach ();
 
     lldb::SBError
+    Detach (bool keep_stopped);
+
+    lldb::SBError
     Signal (int signal);
 
     void
diff --git a/include/lldb/Target/Process.h b/include/lldb/Target/Process.h
index 82e668f..090b759 100644
--- a/include/lldb/Target/Process.h
+++ b/include/lldb/Target/Process.h
@@ -91,6 +91,12 @@
     
     void
     SetStopOnSharedLibraryEvents (bool stop);
+    
+    bool
+    GetDetachKeepsStopped () const;
+    
+    void
+    SetDetachKeepsStopped (bool keep_stopped);
 };
 
 typedef std::shared_ptr<ProcessProperties> ProcessPropertiesSP;
@@ -1943,11 +1949,14 @@
     /// This function is not meant to be overridden by Process
     /// subclasses.
     ///
+    /// @param[in] keep_stopped
+    ///     If true, don't resume the process on detach.
+    ///
     /// @return
     ///     Returns an error object.
     //------------------------------------------------------------------
     Error
-    Detach ();
+    Detach (bool keep_stopped);
 
     //------------------------------------------------------------------
     /// Kills the process and shuts down all threads that were spawned
@@ -2334,7 +2343,7 @@
     ///     false otherwise.
     //------------------------------------------------------------------
     virtual Error
-    DoDetach ()
+    DoDetach (bool keep_stopped)
     {
         Error error;
         error.SetErrorStringWithFormat("error: %s does not support detaching from processes", GetShortPluginName());
diff --git a/source/API/SBProcess.cpp b/source/API/SBProcess.cpp
index ba45ce5..f04fed2 100644
--- a/source/API/SBProcess.cpp
+++ b/source/API/SBProcess.cpp
@@ -786,12 +786,20 @@
 SBError
 SBProcess::Detach ()
 {
+    // FIXME: This should come from a process default.
+    bool keep_stopped = false;
+    Detach (keep_stopped);
+}
+
+SBError
+SBProcess::Detach (bool keep_stopped)
+{
     SBError sb_error;
     ProcessSP process_sp(GetSP());
     if (process_sp)
     {
         Mutex::Locker api_locker (process_sp->GetTarget().GetAPIMutex());
-        sb_error.SetError (process_sp->Detach());
+        sb_error.SetError (process_sp->Detach(keep_stopped));
     }
     else
         sb_error.SetErrorString ("SBProcess is invalid");    
diff --git a/source/Commands/CommandObjectProcess.cpp b/source/Commands/CommandObjectProcess.cpp
index 2221514..7ad5f7b 100644
--- a/source/Commands/CommandObjectProcess.cpp
+++ b/source/Commands/CommandObjectProcess.cpp
@@ -75,7 +75,8 @@
                 {
                     if (process->GetShouldDetach())
                     {
-                        Error detach_error (process->Detach());
+                        bool keep_stopped = false;
+                        Error detach_error (process->Detach(keep_stopped));
                         if (detach_error.Success())
                         {
                             result.SetStatus (eReturnStatusSuccessFinishResult);
@@ -903,6 +904,68 @@
 class CommandObjectProcessDetach : public CommandObjectParsed
 {
 public:
+    class CommandOptions : public Options
+    {
+    public:
+        
+        CommandOptions (CommandInterpreter &interpreter) :
+            Options (interpreter)
+        {
+            OptionParsingStarting ();
+        }
+
+        ~CommandOptions ()
+        {
+        }
+
+        Error
+        SetOptionValue (uint32_t option_idx, const char *option_arg)
+        {
+            Error error;
+            const int short_option = m_getopt_table[option_idx].val;
+            
+            switch (short_option)
+            {
+                case 's':
+                    bool tmp_result;
+                    bool success;
+                    tmp_result = Args::StringToBoolean(option_arg, false, &success);
+                    if (!success)
+                        error.SetErrorStringWithFormat("invalid boolean option: \"%s\"", option_arg);
+                    else
+                    {
+                        if (tmp_result)
+                            m_keep_stopped = eLazyBoolYes;
+                        else
+                            m_keep_stopped = eLazyBoolNo;
+                    }
+                    break;
+                default:
+                    error.SetErrorStringWithFormat("invalid short option character '%c'", short_option);
+                    break;
+            }
+            return error;
+        }
+
+        void
+        OptionParsingStarting ()
+        {
+            m_keep_stopped = eLazyBoolCalculate;
+        }
+
+        const OptionDefinition*
+        GetDefinitions ()
+        {
+            return g_option_table;
+        }
+
+        // Options table: Required for subclasses of Options.
+
+        static OptionDefinition g_option_table[];
+
+        // Instance variables to hold the values for command options.
+        LazyBool m_keep_stopped;
+    };
 
     CommandObjectProcessDetach (CommandInterpreter &interpreter) :
         CommandObjectParsed (interpreter,
@@ -911,7 +974,8 @@
                              "process detach",
                              eFlagRequiresProcess      |
                              eFlagTryTargetAPILock     |
-                             eFlagProcessMustBeLaunched)
+                             eFlagProcessMustBeLaunched),
+        m_options(interpreter)
     {
     }
 
@@ -919,13 +983,35 @@
     {
     }
 
+    Options *
+    GetOptions ()
+    {
+        return &m_options;
+    }
+
+
 protected:
     bool
     DoExecute (Args& command, CommandReturnObject &result)
     {
         Process *process = m_exe_ctx.GetProcessPtr();
         result.AppendMessageWithFormat ("Detaching from process %" PRIu64 "\n", process->GetID());
-        Error error (process->Detach());
+        // FIXME: This will be a Command Option:
+        bool keep_stopped;
+        if (m_options.m_keep_stopped == eLazyBoolCalculate)
+        {
+            // Check the process default:
+            if (process->GetDetachKeepsStopped())
+                keep_stopped = true;
+            else
+                keep_stopped = false;
+        }
+        else if (m_options.m_keep_stopped == eLazyBoolYes)
+            keep_stopped = true;
+        else
+            keep_stopped = false;
+        
+        Error error (process->Detach(keep_stopped));
         if (error.Success())
         {
             result.SetStatus (eReturnStatusSuccessFinishResult);
@@ -938,6 +1024,15 @@
         }
         return result.Succeeded();
     }
+
+    CommandOptions m_options;
+};
+
+OptionDefinition
+CommandObjectProcessDetach::CommandOptions::g_option_table[] =
+{
+{ LLDB_OPT_SET_1, false, "keep-stopped",   's', required_argument, NULL, 0, eArgTypeBoolean, "Whether or not the process should be kept stopped on detach (if possible)." },
+{ 0, false, NULL, 0, 0, NULL, 0, eArgTypeNone, NULL }
 };
 
 //-------------------------------------------------------------------------
diff --git a/source/Interpreter/CommandObject.cpp b/source/Interpreter/CommandObject.cpp
index d2b2ce5..6875605 100644
--- a/source/Interpreter/CommandObject.cpp
+++ b/source/Interpreter/CommandObject.cpp
@@ -295,7 +295,6 @@
         else
         {
             StateType state = process->GetState();
-            
             switch (state)
             {
             case eStateInvalid:
diff --git a/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp b/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
index 9e4cc56..f9b1055 100644
--- a/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
+++ b/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
@@ -507,12 +507,12 @@
 }
 
 Error
-ProcessKDP::DoDetach()
+ProcessKDP::DoDetach(bool keep_stopped)
 {
     Error error;
     Log *log (ProcessKDPLog::GetLogIfAllCategoriesSet(KDP_LOG_PROCESS));
     if (log)
-        log->Printf ("ProcessKDP::DoDetach()");
+        log->Printf ("ProcessKDP::DoDetach(keep_stopped = %i)", keep_stopped);
     
     if (m_comm.IsRunning())
     {
@@ -525,7 +525,8 @@
         
         m_thread_list.DiscardThreadPlans();
         
-        if (m_comm.IsConnected())
+        // If we are going to keep the target stopped, then don't send the disconnect message.
+        if (!keep_stopped && m_comm.IsConnected())
         {
 
             m_comm.SendRequestDisconnect();
@@ -554,7 +555,8 @@
 ProcessKDP::DoDestroy ()
 {
     // For KDP there really is no difference between destroy and detach
-    return DoDetach();
+    bool keep_stopped = false;
+    return DoDetach(keep_stopped);
 }
 
 //------------------------------------------------------------------
diff --git a/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h b/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h
index fba221e..9de2623 100644
--- a/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h
+++ b/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.h
@@ -135,7 +135,7 @@
     DoHalt (bool &caused_stop);
     
     virtual lldb_private::Error
-    DoDetach ();
+    DoDetach (bool keep_stopped);
     
     virtual lldb_private::Error
     DoSignal (int signal);
diff --git a/source/Plugins/Process/POSIX/ProcessPOSIX.cpp b/source/Plugins/Process/POSIX/ProcessPOSIX.cpp
index 137fa46..35c365f 100644
--- a/source/Plugins/Process/POSIX/ProcessPOSIX.cpp
+++ b/source/Plugins/Process/POSIX/ProcessPOSIX.cpp
@@ -283,9 +283,16 @@
 }
 
 Error
-ProcessPOSIX::DoDetach()
+ProcessPOSIX::DoDetach(bool keep_stopped)
 {
     Error error;
+    if (keep_stopped)
+    {
+        // FIXME: If you want to implement keep_stopped on Linux,
+        // this would be the place to do it.
+        error.SetErrorString("Detaching with keep_stopped true is not currently supported on Linux.");
+        return error;
+    }
 
     error = m_monitor->Detach();
     if (error.Success())
diff --git a/source/Plugins/Process/POSIX/ProcessPOSIX.h b/source/Plugins/Process/POSIX/ProcessPOSIX.h
index b16da3a..86c4ef3 100644
--- a/source/Plugins/Process/POSIX/ProcessPOSIX.h
+++ b/source/Plugins/Process/POSIX/ProcessPOSIX.h
@@ -65,7 +65,7 @@
     DoHalt(bool &caused_stop);
 
     virtual lldb_private::Error
-    DoDetach();
+    DoDetach(bool keep_stopped);
 
     virtual lldb_private::Error
     DoSignal(int signal);
diff --git a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 9351b93..6657c2c 100644
--- a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -52,6 +52,7 @@
     m_supports_alloc_dealloc_memory (eLazyBoolCalculate),
     m_supports_memory_region_info  (eLazyBoolCalculate),
     m_supports_watchpoint_support_info  (eLazyBoolCalculate),
+    m_supports_detach_stay_stopped (eLazyBoolCalculate),
     m_watchpoints_trigger_after_instruction(eLazyBoolCalculate),
     m_attach_or_wait_reply(eLazyBoolCalculate),
     m_prepare_for_reg_writing_reply (eLazyBoolCalculate),
@@ -1396,10 +1397,48 @@
     return false;
 }
 
-bool
-GDBRemoteCommunicationClient::Detach ()
+Error
+GDBRemoteCommunicationClient::Detach (bool keep_stopped)
 {
-    return SendPacket ("D", 1) > 0;
+    Error error;
+    
+    if (keep_stopped)
+    {
+        if (m_supports_detach_stay_stopped == eLazyBoolCalculate)
+        {
+            char packet[64];
+            const int packet_len = ::snprintf(packet, sizeof(packet), "qSupportsDetachAndStayStopped:");
+            assert (packet_len < sizeof(packet));
+            StringExtractorGDBRemote response;
+            if (SendPacketAndWaitForResponse (packet, packet_len, response, false))
+            {
+                m_supports_detach_stay_stopped = eLazyBoolYes;        
+            }
+            else
+            {
+                m_supports_detach_stay_stopped = eLazyBoolNo;
+            }
+        }
+
+        if (m_supports_detach_stay_stopped == eLazyBoolNo)
+        {
+            error.SetErrorString("Stays stopped not supported by this target.");
+            return error;
+        }
+        else
+        {
+            size_t num_sent = SendPacket ("D1", 2);
+            if (num_sent == 0)
+                error.SetErrorString ("Sending extended disconnect packet failed.");
+        }
+    }
+    else
+    {
+        size_t num_sent = SendPacket ("D", 1);
+        if (num_sent == 0)
+            error.SetErrorString ("Sending disconnect packet failed.");
+    }
+    return error;
 }
 
 Error
diff --git a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 25b697d..4075eeb 100644
--- a/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -199,8 +199,8 @@
     bool
     DeallocateMemory (lldb::addr_t addr);
 
-    bool
-    Detach ();
+    lldb_private::Error
+    Detach (bool keep_stopped);
 
     lldb_private::Error
     GetMemoryRegionInfo (lldb::addr_t addr, 
@@ -381,6 +381,7 @@
     lldb_private::LazyBool m_supports_alloc_dealloc_memory;
     lldb_private::LazyBool m_supports_memory_region_info;
     lldb_private::LazyBool m_supports_watchpoint_support_info;
+    lldb_private::LazyBool m_supports_detach_stay_stopped;
     lldb_private::LazyBool m_watchpoints_trigger_after_instruction;
     lldb_private::LazyBool m_attach_or_wait_reply;
     lldb_private::LazyBool m_prepare_for_reg_writing_reply;
diff --git a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 35e455d..aa27ae8 100644
--- a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1703,25 +1703,29 @@
 }
 
 Error
-ProcessGDBRemote::DoDetach()
+ProcessGDBRemote::DoDetach(bool keep_stopped)
 {
     Error error;
     Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS));
     if (log)
-        log->Printf ("ProcessGDBRemote::DoDetach()");
-
+        log->Printf ("ProcessGDBRemote::DoDetach(keep_stopped: %i)", keep_stopped);
+ 
     DisableAllBreakpointSites ();
 
     m_thread_list.DiscardThreadPlans();
 
-    bool success = m_gdb_comm.Detach ();
+    error = m_gdb_comm.Detach (keep_stopped);
     if (log)
     {
-        if (success)
+        if (error.Success())
             log->PutCString ("ProcessGDBRemote::DoDetach() detach packet sent successfully");
         else
-            log->PutCString ("ProcessGDBRemote::DoDetach() detach packet send failed");
+            log->Printf ("ProcessGDBRemote::DoDetach() detach packet send failed: %s", error.AsCString() ? error.AsCString() : "<unknown error>");
     }
+    
+    if (!error.Success())
+        return error;
+
     // Sleep for one second to let the process get all detached...
     StopAsyncThread ();
 
diff --git a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index dc4ec56..5cbb13f 100644
--- a/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -141,7 +141,7 @@
     DoHalt (bool &caused_stop);
 
     virtual lldb_private::Error
-    DoDetach ();
+    DoDetach (bool keep_stopped);
     
     virtual bool
     DetachRequiresHalt() { return true; }
diff --git a/source/Target/Process.cpp b/source/Target/Process.cpp
index f60adca..e0ea32a 100644
--- a/source/Target/Process.cpp
+++ b/source/Target/Process.cpp
@@ -101,6 +101,7 @@
     { "unwind-on-error-in-expressions", OptionValue::eTypeBoolean, true, true, NULL, NULL, "If true, errors in expression evaluation will unwind the stack back to the state before the call." },
     { "python-os-plugin-path", OptionValue::eTypeFileSpec, false, true, NULL, NULL, "A path to a python OS plug-in module file that contains a OperatingSystemPlugIn class." },
     { "stop-on-sharedlibrary-events" , OptionValue::eTypeBoolean, true, false, NULL, NULL, "If true, stop when a shared library is loaded or unloaded." },
+    { "detach-keeps-stopped" , OptionValue::eTypeBoolean, true, false, NULL, NULL, "If true, detach will attempt to keep the process stopped." },
     {  NULL                  , OptionValue::eTypeInvalid, false, 0, NULL, NULL, NULL  }
 };
 
@@ -110,7 +111,8 @@
     ePropertyIgnoreBreakpointsInExpressions,
     ePropertyUnwindOnErrorInExpressions,
     ePropertyPythonOSPluginPath,
-    ePropertyStopOnSharedLibraryEvents
+    ePropertyStopOnSharedLibraryEvents,
+    ePropertyDetachKeepsStopped
 };
 
 ProcessProperties::ProcessProperties (bool is_global) :
@@ -213,6 +215,20 @@
     m_collection_sp->SetPropertyAtIndexAsBoolean(NULL, idx, stop);
 }
 
+bool
+ProcessProperties::GetDetachKeepsStopped () const
+{
+    const uint32_t idx = ePropertyDetachKeepsStopped;
+    return m_collection_sp->GetPropertyAtIndexAsBoolean(NULL, idx, g_properties[idx].default_uint_value != 0);
+}
+    
+void
+ProcessProperties::SetDetachKeepsStopped (bool stop)
+{
+    const uint32_t idx = ePropertyDetachKeepsStopped;
+    m_collection_sp->SetPropertyAtIndexAsBoolean(NULL, idx, stop);
+}
+
 void
 ProcessInstanceInfo::Dump (Stream &s, Platform *platform) const
 {
@@ -1096,7 +1112,11 @@
         case eStateCrashed:
         case eStateSuspended:
             if (GetShouldDetach())
-                Detach();
+            {
+                // FIXME: This will have to be a process setting:
+                bool keep_stopped = false;
+                Detach(keep_stopped);
+            }
             else
                 Destroy();
             break;
@@ -3431,7 +3451,7 @@
 }
 
 Error
-Process::Detach ()
+Process::Detach (bool keep_stopped)
 {
     EventSP exit_event_sp;
     Error error;
@@ -3458,12 +3478,16 @@
             }
         }
     
-        error = DoDetach(); 
+        error = DoDetach(keep_stopped);
         if (error.Success())
         {
             DidDetach();
             StopPrivateStateThread();
         }
+        else
+        {
+            return error;
+        }
     }
     m_destroy_in_process = false;