diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -130,6 +130,24 @@ void SetWaitForLaunch(bool b) { m_wait_for_launch = b; } + llvm::Optional GetWaitForLaunchInterval() const { + return m_wait_for_launch_interval; + } + + void SetWaitForLaunchInterval( + llvm::Optional new_interval) { + m_wait_for_launch_interval = new_interval; + } + + llvm::Optional GetWaitForLaunchDuration() const { + return m_wait_for_launch_duration; + } + + void + SetWaitForLaunchDuration(llvm::Optional new_duration) { + m_wait_for_launch_duration = new_duration; + } + bool GetAsync() const { return m_async; } void SetAsync(bool b) { m_async = b; } @@ -159,6 +177,8 @@ m_plugin_name.clear(); m_resume_count = 0; m_wait_for_launch = false; + m_wait_for_launch_interval = llvm::None; + m_wait_for_launch_duration = llvm::None; m_ignore_existing = true; m_continue_once_attached = false; } @@ -198,6 +218,8 @@ std::string m_plugin_name; uint32_t m_resume_count; // How many times do we resume after launching bool m_wait_for_launch; + llvm::Optional m_wait_for_launch_interval; + llvm::Optional m_wait_for_launch_duration; bool m_ignore_existing; bool m_continue_once_attached; // Supports the use-case scenario of // immediately continuing the process once diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -122,6 +122,7 @@ eServerPacketType_qThreadExtraInfo, eServerPacketType_qThreadStopInfo, eServerPacketType_qVAttachOrWaitSupported, + eServerPacketType_qJAttachSupported, eServerPacketType_qWatchpointSupportInfo, eServerPacketType_qWatchpointSupportInfoSupported, eServerPacketType_qXfer, @@ -132,6 +133,7 @@ eServerPacketType_vAttach, eServerPacketType_vAttachWait, eServerPacketType_vAttachOrWait, + eServerPacketType_jAttach, eServerPacketType_vAttachName, eServerPacketType_vCont, eServerPacketType_vCont_actions, // vCont? diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -597,6 +597,8 @@ eArgTypeCommand, eArgTypeColumnNum, eArgTypeModuleUUID, + eArgTypeWaitforInterval, + eArgTypeWaitforDuration, eArgTypeLastArg // Always keep this entry as the last entry in this // enumeration!! }; diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -307,6 +307,27 @@ attach_info.SetIgnoreExisting(false); break; + case 'I': { + uint64_t interval; + if (option_arg.getAsInteger(0, interval)) { + error.SetErrorStringWithFormat("invalid polling interval '%s'", + option_arg.str().c_str()); + } else { + attach_info.SetWaitForLaunchInterval( + std::chrono::microseconds(interval)); + } + break; + } + case 'd': { + uint64_t duration; + if (option_arg.getAsInteger(0, duration)) { + error.SetErrorStringWithFormat("invalid duration '%s'", + option_arg.str().c_str()); + } else { + attach_info.SetWaitForLaunchDuration(std::chrono::seconds(duration)); + } + break; + } default: llvm_unreachable("Unimplemented option"); } diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -688,6 +688,12 @@ Group<2>, Desc<"Include existing processes when doing attach -w.">; def process_attach_waitfor : Option<"waitfor", "w">, Group<2>, Desc<"Wait for the process with to launch.">; + def process_attach_waitfor_interval : Option<"waitfor-interval", "I">, + Group<2>, Arg<"WaitforInterval">, + Desc<"The interval that waitfor uses to poll the list of processes.">; + def process_attach_waitfor_duration : Option<"waitfor-duration", "d">, + Group<2>, Arg<"WaitforDuration">, + Desc<"The interval that waitfor uses to poll the list of processes.">; } let Command = "process continue" in { diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -257,6 +257,8 @@ bool GetVAttachOrWaitSupported(); + bool GetJAttachSupported(); + bool GetSyncThreadStateSupported(); void ResetDiscoverableSettings(bool did_exec); @@ -542,6 +544,7 @@ LazyBool m_supports_detach_stay_stopped; LazyBool m_watchpoints_trigger_after_instruction; LazyBool m_attach_or_wait_reply; + LazyBool m_j_attach_wait_reply; LazyBool m_prepare_for_reg_writing_reply; LazyBool m_supports_p; LazyBool m_supports_x; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -74,6 +74,7 @@ m_supports_detach_stay_stopped(eLazyBoolCalculate), m_watchpoints_trigger_after_instruction(eLazyBoolCalculate), m_attach_or_wait_reply(eLazyBoolCalculate), + m_j_attach_wait_reply(eLazyBoolCalculate), m_prepare_for_reg_writing_reply(eLazyBoolCalculate), m_supports_p(eLazyBoolCalculate), m_supports_x(eLazyBoolCalculate), m_avoid_g_packets(eLazyBoolCalculate), @@ -257,6 +258,20 @@ return m_attach_or_wait_reply == eLazyBoolYes; } +bool GDBRemoteCommunicationClient::GetJAttachSupported() { + if (m_j_attach_wait_reply == eLazyBoolCalculate) { + m_j_attach_wait_reply = eLazyBoolNo; + + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse("qJAttachSupported", response, false) == + PacketResult::Success) { + if (response.IsOKResponse()) + m_j_attach_wait_reply = eLazyBoolYes; + } + } + return m_j_attach_wait_reply == eLazyBoolYes; +} + bool GDBRemoteCommunicationClient::GetSyncThreadStateSupported() { if (m_prepare_for_reg_writing_reply == eLazyBoolCalculate) { m_prepare_for_reg_writing_reply = eLazyBoolNo; @@ -291,6 +306,7 @@ m_supports_memory_region_info = eLazyBoolCalculate; m_prepare_for_reg_writing_reply = eLazyBoolCalculate; m_attach_or_wait_reply = eLazyBoolCalculate; + m_j_attach_wait_reply = eLazyBoolCalculate; m_avoid_g_packets = eLazyBoolCalculate; m_supports_qXfer_auxv_read = eLazyBoolCalculate; m_supports_qXfer_libraries_read = eLazyBoolCalculate; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -65,10 +65,26 @@ /// with a given name and attaching llgs to that via the configured /// Platform. /// + /// \param process_name + /// The process we'd like to attach to. + /// \param include_existing + /// Look for the process we'd like to attach to among the processes + /// already running. + /// \param custom_polling_interval + /// The polling interval the function should use in microseconds. Defaults + /// to 1000. + /// \param timeout + /// How long we should look for the process in seconds. Defaults to + /// infinity. + /// /// \return /// An Status object indicating the success or failure of the /// attach operation. - Status AttachWaitProcess(llvm::StringRef process_name, bool include_existing); + Status AttachWaitProcess( + llvm::StringRef process_name, bool include_existing, + llvm::Optional custom_polling_interval = + llvm::None, + llvm::Optional timeout = llvm::None); // NativeProcessProtocol::NativeDelegate overrides void InitializeDelegate(NativeProcessProtocol *process) override; @@ -187,6 +203,10 @@ PacketResult Handle_vAttachOrWait(StringExtractorGDBRemote &packet); + PacketResult Handle_qJAttachSupported(StringExtractorGDBRemote &packet); + + PacketResult Handle_jAttach(StringExtractorGDBRemote &packet); + PacketResult Handle_D(StringExtractorGDBRemote &packet); PacketResult Handle_qThreadStopInfo(StringExtractorGDBRemote &packet); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -168,6 +168,12 @@ RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_vAttachOrWait, &GDBRemoteCommunicationServerLLGS::Handle_vAttachOrWait); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_qJAttachSupported, + &GDBRemoteCommunicationServerLLGS::Handle_qJAttachSupported); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_jAttach, + &GDBRemoteCommunicationServerLLGS::Handle_jAttach); RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_vCont, &GDBRemoteCommunicationServerLLGS::Handle_vCont); @@ -344,10 +350,13 @@ } Status GDBRemoteCommunicationServerLLGS::AttachWaitProcess( - llvm::StringRef process_name, bool include_existing) { + llvm::StringRef process_name, bool include_existing, + llvm::Optional custom_polling_interval, + llvm::Optional timeout) { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); - std::chrono::milliseconds polling_interval = std::chrono::milliseconds(1); + auto polling_interval = + custom_polling_interval.getValueOr(std::chrono::microseconds(1000)); // Create the matcher used to search the process list. ProcessInstanceInfoList exclusion_list; @@ -376,8 +385,13 @@ return false; }; + auto target = std::chrono::steady_clock::now(); + if (timeout.hasValue()) + target = std::chrono::steady_clock::now() + + std::chrono::seconds(timeout.getValue()); + ProcessInstanceInfoList loop_process_list; - while (true) { + do { loop_process_list.clear(); if (Host::FindProcesses(match_info, loop_process_list)) { // Remove all the elements that are in the exclusion list. @@ -407,9 +421,14 @@ } } // No matches, we have not found the process. Sleep until next poll. - LLDB_LOG(log, "sleep {0} seconds", polling_interval); + LLDB_LOG(log, "sleep for {0}", polling_interval); std::this_thread::sleep_for(polling_interval); - } + } while (!timeout.hasValue() || std::chrono::steady_clock::now() < target); + + // Timed out + Status error; + error.SetErrorString("attach timed out."); + return error; } void GDBRemoteCommunicationServerLLGS::InitializeDelegate( @@ -3332,6 +3351,55 @@ return SendStopReasonForState(m_debugged_process_up->GetState()); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_qJAttachSupported( + StringExtractorGDBRemote &packet) { + return SendOKResponse(); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jAttach( + StringExtractorGDBRemote &packet) { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); + + if (!packet.ConsumeFront("jAttach:")) + return SendIllFormedResponse(packet, "jAttach: Ill formed packet "); + + auto json_object = StructuredData::ParseJSON(packet.Peek()); + + if (!json_object || + json_object->GetType() != lldb::eStructuredDataTypeDictionary) + return SendIllFormedResponse(packet, "jAttach: Ill formed packet "); + + auto *json_dict = json_object->GetAsDictionary(); + + StringRef process_name; + llvm::Optional polling_interval; + llvm::Optional polling_duration; + bool include_existing = false; + uint64_t temp; + + json_dict->GetValueForKeyAsString("process_name", process_name); + json_dict->GetValueForKeyAsBoolean("include-existing", include_existing); + if (json_dict->GetValueForKeyAsInteger("waitfor-interval-usec", temp)) + polling_interval = std::chrono::microseconds(temp); + if (json_dict->GetValueForKeyAsInteger("waitfor-duration-sec", temp)) + polling_duration = std::chrono::seconds(temp); + + LLDB_LOG(log, "attempting to attach to process named '{0}'", process_name); + + Status error = AttachWaitProcess(process_name, include_existing, + polling_interval, polling_duration); + if (error.Fail()) { + LLDB_LOG(log, "failed to attach to process named '{0}': {1}", process_name, + error); + return SendErrorResponse(error); + } + + // Notify we attached by sending a stop packet. + return SendStopReasonForState(m_debugged_process_up->GetState()); +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::Handle_D(StringExtractorGDBRemote &packet) { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1160,41 +1160,72 @@ return error; } +static StreamGDBRemote MakeJAttachPacket(const char *process_name, + const ProcessAttachInfo &attach_info) { + + StreamString json_string; + json_string.PutCString("jAttach:"); + + StructuredData::Dictionary json_packet; + json_packet.AddStringItem("process_name", process_name); + + if (auto interval = attach_info.GetWaitForLaunchInterval()) + json_packet.AddIntegerItem("waitfor-interval-usec", interval->count()); + + if (auto duration = attach_info.GetWaitForLaunchDuration()) + json_packet.AddIntegerItem("waitfor-duration-sec", duration->count()); + + json_packet.AddBooleanItem("include-existing", + !attach_info.GetIgnoreExisting()); + + json_packet.Dump(json_string, false); + StreamGDBRemote escaped_packet; + escaped_packet.PutEscapedBytes(json_string.GetData(), json_string.GetSize()); + return escaped_packet; +} + Status ProcessGDBRemote::DoAttachToProcessWithName( const char *process_name, const ProcessAttachInfo &attach_info) { Status error; // Clear out and clean up from any current state Clear(); - if (process_name && process_name[0]) { - error = EstablishConnectionIfNeeded(attach_info); - if (error.Success()) { - StreamString packet; + if (!process_name || !process_name[0]) + return error; - m_gdb_comm.SetDetachOnError(attach_info.GetDetachOnError()); + error = EstablishConnectionIfNeeded(attach_info); + if (!error.Success()) { + SetExitStatus(-1, error.AsCString()); + return error; + } - if (attach_info.GetWaitForLaunch()) { - if (!m_gdb_comm.GetVAttachOrWaitSupported()) { + m_gdb_comm.SetDetachOnError(attach_info.GetDetachOnError()); + if (attach_info.GetWaitForLaunch() && m_gdb_comm.GetJAttachSupported()) { + auto gdb_stream = MakeJAttachPacket(process_name, attach_info); + m_async_broadcaster.BroadcastEvent( + eBroadcastBitAsyncContinue, + new EventDataBytes(gdb_stream.GetString().data(), + gdb_stream.GetSize())); + } else { + StreamString packet; + if (attach_info.GetWaitForLaunch()) { + if (!m_gdb_comm.GetVAttachOrWaitSupported()) { + packet.PutCString("vAttachWait"); + } else { + if (attach_info.GetIgnoreExisting()) packet.PutCString("vAttachWait"); - } else { - if (attach_info.GetIgnoreExisting()) - packet.PutCString("vAttachWait"); - else - packet.PutCString("vAttachOrWait"); - } - } else - packet.PutCString("vAttachName"); - packet.PutChar(';'); - packet.PutBytesAsRawHex8(process_name, strlen(process_name), - endian::InlHostByteOrder(), - endian::InlHostByteOrder()); - - m_async_broadcaster.BroadcastEvent( - eBroadcastBitAsyncContinue, - new EventDataBytes(packet.GetString().data(), packet.GetSize())); - + else + packet.PutCString("vAttachOrWait"); + } } else - SetExitStatus(-1, error.AsCString()); + packet.PutCString("vAttachName"); + packet.PutChar(';'); + packet.PutBytesAsRawHex8(process_name, strlen(process_name), + endian::InlHostByteOrder(), + endian::InlHostByteOrder()); + m_async_broadcaster.BroadcastEvent( + eBroadcastBitAsyncContinue, + new EventDataBytes(packet.GetString().data(), packet.GetSize())); } return error; } diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -200,7 +200,10 @@ if (PACKET_MATCHES("qHostInfo")) return eServerPacketType_qHostInfo; break; - + case 'J': + if (PACKET_MATCHES("qJAttachSupported")) + return eServerPacketType_qJAttachSupported; + break; case 'K': if (PACKET_STARTS_WITH("qKillSpawnedProcess")) return eServerPacketType_qKillSpawnedProcess; @@ -294,6 +297,8 @@ break; case 'j': + if (PACKET_STARTS_WITH("jAttach:")) + return eServerPacketType_jAttach; if (PACKET_STARTS_WITH("jModulesInfo:")) return eServerPacketType_jModulesInfo; if (PACKET_MATCHES("jSignalsInfo"))