Index: docs/lldb-gdb-remote.txt =================================================================== --- docs/lldb-gdb-remote.txt +++ docs/lldb-gdb-remote.txt @@ -1530,3 +1530,66 @@ // Low. If this packet is absent, lldb will read the Mach-O headers/load // commands out of memory. //---------------------------------------------------------------------- + +//---------------------------------------------------------------------- +// "jThreadsInfo" +// +// BRIEF +// Ask for the server for thread stop information of all threads. +// +// PRIORITY TO IMPLEMENT +// Low. This is a performance optimization, which speeds up debugging by avoiding +// multiple round-trips for retrieving thread information. The information from this +// packet can be retrieved using a combination of qThreadStopInfo and m packets. +//---------------------------------------------------------------------- + +The data in this packet is very similar to the stop reply packets, but is packaged in +JSON and uses JSON arrays where applicable. The JSON output looks like: + [ + { "tid":1580681, + "metype":6, + "medata":[2,0], + "reason":"exception", + "qaddr":140735118423168, + "registers": { + "0":"8000000000000000", + "1":"0000000000000000", + "2":"20fabf5fff7f0000", + "3":"e8f8bf5fff7f0000", + "4":"0100000000000000", + "5":"d8f8bf5fff7f0000", + "6":"b0f8bf5fff7f0000", + "7":"20f4bf5fff7f0000", + "8":"8000000000000000", + "9":"61a8db78a61500db", + "10":"3200000000000000", + "11":"4602000000000000", + "12":"0000000000000000", + "13":"0000000000000000", + "14":"0000000000000000", + "15":"0000000000000000", + "16":"960b000001000000", + "17":"0202000000000000", + "18":"2b00000000000000", + "19":"0000000000000000", + "20":"0000000000000000" + }, + "memory":[ + {"address":140734799804592,"bytes":"c8f8bf5fff7f0000c9a59e8cff7f0000"}, + {"address":140734799804616,"bytes":"00000000000000000100000000000000"} + ] + } + ] + +It contains an array of dictionaries with all of the key value pairs that are +normally in the stop reply packet, including the expedited registers. The registers are +passed as hex-encoded JSON string in debuggee-endian byte order. Notice that +is also contains expedited memory in the "memory" key. This allows the server to +expedite memory that the client is likely to use (e.g., areas around the +stack pointer, which are needed for computing backtraces) and it reduces the packet +count. + +On MacOSX with debugserver, we expedite the frame pointer backchain for a thread +(up to 256 entries) by reading 2 pointers worth of bytes at the frame pointer (for +the previous FP and PC), and follow the backchain. Most backtraces on MacOSX and +iOS now don't require us to read any memory! Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -241,6 +241,9 @@ Handle_qThreadStopInfo (StringExtractorGDBRemote &packet); PacketResult + Handle_jThreadsInfo (StringExtractorGDBRemote &packet); + + PacketResult Handle_qWatchpointSupportInfo (StringExtractorGDBRemote &packet); PacketResult Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -43,6 +43,7 @@ #include "lldb/Host/common/NativeRegisterContext.h" #include "lldb/Host/common/NativeProcessProtocol.h" #include "lldb/Host/common/NativeThreadProtocol.h" +#include "lldb/Utility/JSON.h" // Project includes #include "Utility/StringExtractorGDBRemote.h" @@ -161,6 +162,8 @@ &GDBRemoteCommunicationServerLLGS::Handle_qsThreadInfo); RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qThreadStopInfo, &GDBRemoteCommunicationServerLLGS::Handle_qThreadStopInfo); + RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_jThreadsInfo, + &GDBRemoteCommunicationServerLLGS::Handle_jThreadsInfo); RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qWatchpointSupportInfo, &GDBRemoteCommunicationServerLLGS::Handle_qWatchpointSupportInfo); RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qXfer_auxv_read, @@ -455,6 +458,85 @@ } } +static JSONObject::SP +GetRegistersAsJSON(NativeThreadProtocol &thread) +{ + Log *log (GetLogIfAnyCategoriesSet (LIBLLDB_LOG_THREAD)); + + NativeRegisterContextSP reg_ctx_sp = thread.GetRegisterContext (); + if (! reg_ctx_sp) + return nullptr; + + static constexpr int k_expedited_registers[] = { + LLDB_REGNUM_GENERIC_PC, LLDB_REGNUM_GENERIC_SP, LLDB_REGNUM_GENERIC_FP, LLDB_REGNUM_GENERIC_RA + }; + JSONObject::SP register_object_sp (new JSONObject); + for (int regnum: k_expedited_registers) + { + const uint32_t regindex = + reg_ctx_sp->ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, regnum); + if (regindex == LLDB_INVALID_REGNUM) + continue; // The target does not support the given register. + + const RegisterInfo *const reg_info_p = reg_ctx_sp->GetRegisterInfoAtIndex(regindex); + if (reg_info_p == nullptr) + { + if (log) + log->Printf("%s failed to get register info for register index %" PRIu32, + __FUNCTION__, regindex); + continue; + } + + RegisterValue reg_value; + Error error = reg_ctx_sp->ReadRegister(reg_info_p, reg_value); + if (error.Fail()) + { + if (log) + log->Printf("%s failed to read register '%s' index %" PRIu32 ": %s", __FUNCTION__, reg_info_p->name ? reg_info_p->name : "", regnum, error.AsCString ()); + continue; + } + + StreamString stream; + stream.Printf("%d", regindex); + std::string reg_num_str = stream.GetString(); + + stream.Clear(); + WriteRegisterValueInHexFixedWidth(stream, reg_ctx_sp, *reg_info_p, ®_value); + + register_object_sp->SetObject(reg_num_str, JSONString::SP(new JSONString(stream.GetString()))); + } + + return register_object_sp; +} + +static const char * +GetStopReasonString(StopReason stop_reason) +{ + switch (stop_reason) + { + case eStopReasonTrace: + return "trace"; + case eStopReasonBreakpoint: + return "breakpoint"; + break; + case eStopReasonWatchpoint: + return "watchpoint"; + case eStopReasonSignal: + return "signal"; + case eStopReasonException: + return "exception"; + case eStopReasonExec: + return "exec"; + case eStopReasonInstrumentation: + case eStopReasonInvalid: + case eStopReasonPlanComplete: + case eStopReasonThreadExiting: + case eStopReasonNone: + break; // ignored + } + return nullptr; +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::SendStopReplyPacketForThread (lldb::tid_t tid) { @@ -595,34 +677,7 @@ } } - const char* reason_str = nullptr; - switch (tid_stop_info.reason) - { - case eStopReasonTrace: - reason_str = "trace"; - break; - case eStopReasonBreakpoint: - reason_str = "breakpoint"; - break; - case eStopReasonWatchpoint: - reason_str = "watchpoint"; - break; - case eStopReasonSignal: - reason_str = "signal"; - break; - case eStopReasonException: - reason_str = "exception"; - break; - case eStopReasonExec: - reason_str = "exec"; - break; - case eStopReasonInstrumentation: - case eStopReasonInvalid: - case eStopReasonPlanComplete: - case eStopReasonThreadExiting: - case eStopReasonNone: - break; - } + const char* reason_str = GetStopReasonString(tid_stop_info.reason); if (reason_str != nullptr) { response.Printf ("reason:%s;", reason_str); @@ -2638,6 +2693,92 @@ } GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jThreadsInfo (StringExtractorGDBRemote &) +{ + Log *log (GetLogIfAnyCategoriesSet (LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_THREAD)); + + // Ensure we have a debugged process. + if (!m_debugged_process_sp || (m_debugged_process_sp->GetID () == LLDB_INVALID_PROCESS_ID)) + return SendErrorResponse (50); + + if (log) + log->Printf ("GDBRemoteCommunicationServerLLGS::%s preparing packet for pid %" PRIu64, + __FUNCTION__, m_debugged_process_sp->GetID()); + + JSONArray threads_array; + + // Ensure we can get info on the given thread. + uint32_t thread_idx = 0; + for ( NativeThreadProtocolSP thread_sp; + (thread_sp = m_debugged_process_sp->GetThreadAtIndex(thread_idx)) != nullptr; + ++thread_idx) + { + + JSONObject::SP thread_obj_sp (new JSONObject); + + lldb::tid_t tid = thread_sp->GetID(); + + // Grab the reason this thread stopped. + struct ThreadStopInfo tid_stop_info; + std::string description; + if (!thread_sp->GetStopReason (tid_stop_info, description)) + return SendErrorResponse (52); + + const int signum = tid_stop_info.details.signal.signo; + if (log) + { + log->Printf ("GDBRemoteCommunicationServerLLGS::%s pid %" PRIu64 " tid %" PRIu64 " got signal signo = %d, reason = %d, exc_type = %" PRIu64, + __FUNCTION__, + m_debugged_process_sp->GetID (), + tid, + signum, + tid_stop_info.reason, + tid_stop_info.details.exception.type); + } + + thread_obj_sp->SetObject("tid", std::make_shared(tid)); + if (signum != LLDB_INVALID_SIGNAL_NUMBER) + thread_obj_sp->SetObject("signal", std::make_shared(uint64_t(signum))); + + const std::string thread_name = thread_sp->GetName (); + if (! thread_name.empty()) + thread_obj_sp->SetObject("name", std::make_shared(thread_name)); + + if (JSONObject::SP registers_sp = GetRegistersAsJSON(*thread_sp)) + thread_obj_sp->SetObject("registers", registers_sp); + + if (const char *stop_reason_str = GetStopReasonString(tid_stop_info.reason)) + thread_obj_sp->SetObject("reason", std::make_shared(stop_reason_str)); + + if (! description.empty()) + thread_obj_sp->SetObject("description", std::make_shared(description)); + + if ((tid_stop_info.reason == eStopReasonException) && tid_stop_info.details.exception.type) + { + thread_obj_sp->SetObject("metype", + std::make_shared(tid_stop_info.details.exception.type)); + + JSONArray::SP medata_array_sp (new JSONArray); + for (uint32_t i = 0; i < tid_stop_info.details.exception.data_count; ++i) + { + medata_array_sp->AppendObject(std::make_shared( + tid_stop_info.details.exception.data[i])); + } + thread_obj_sp->SetObject("medata", medata_array_sp); + } + + threads_array.AppendObject(thread_obj_sp); + } + // TODO: Expedite interesting regions of inferior memory + + StreamString response; + threads_array.Write(response); + StreamGDBRemote escaped_response; + escaped_response.PutEscapedBytes(response.GetData(), response.GetSize()); + return SendPacketNoLock (escaped_response.GetData(), escaped_response.GetSize()); +} + +GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::Handle_qWatchpointSupportInfo (StringExtractorGDBRemote &packet) { // Fail if we don't have a current process. Index: source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1397,6 +1397,7 @@ m_continue_C_tids.clear(); m_continue_s_tids.clear(); m_continue_S_tids.clear(); + m_threads_info_sp.reset(); return Error(); } @@ -2094,6 +2095,7 @@ static ConstString g_key_address("address"); static ConstString g_key_bytes("bytes"); static ConstString g_key_description("description"); + static ConstString g_key_signal("signal"); // Stop with signal and thread info lldb::tid_t tid = LLDB_INVALID_THREAD_ID; @@ -2238,6 +2240,8 @@ } } + else if (key == g_key_signal) + signo = object->GetIntegerValue(LLDB_INVALID_SIGNAL_NUMBER); return true; // Keep iterating through all dictionary key/value pairs }); Index: source/Utility/StringExtractorGDBRemote.h =================================================================== --- source/Utility/StringExtractorGDBRemote.h +++ source/Utility/StringExtractorGDBRemote.h @@ -97,6 +97,7 @@ eServerPacketType_QSyncThreadState, eServerPacketType_QThreadSuffixSupported, + eServerPacketType_jThreadsInfo, eServerPacketType_qsThreadInfo, eServerPacketType_qfThreadInfo, eServerPacketType_qGetPid, Index: source/Utility/StringExtractorGDBRemote.cpp =================================================================== --- source/Utility/StringExtractorGDBRemote.cpp +++ source/Utility/StringExtractorGDBRemote.cpp @@ -221,7 +221,9 @@ break; case 'j': - if (PACKET_MATCHES("jSignalInfo")) return eServerPacketType_jSignalsInfo; + if (PACKET_MATCHES("jSignalInfo")) return eServerPacketType_jSignalsInfo; + if (PACKET_MATCHES("jThreadsInfo")) return eServerPacketType_jThreadsInfo; + case 'v': if (PACKET_STARTS_WITH("vFile:")) Index: test/functionalities/thread/concurrent_events/TestConcurrentEvents.py =================================================================== --- test/functionalities/thread/concurrent_events/TestConcurrentEvents.py +++ test/functionalities/thread/concurrent_events/TestConcurrentEvents.py @@ -503,7 +503,7 @@ # The inferior process should have exited without crashing self.assertEqual(0, self.crash_count, "Unexpected thread(s) in crashed state") - self.assertTrue(self.inferior_process.GetState() == lldb.eStateExited, PROCESS_EXITED) + self.assertEqual(self.inferior_process.GetState(), lldb.eStateExited, PROCESS_EXITED) # Verify the number of actions took place matches expected numbers expected_breakpoint_threads = num_delay_breakpoint_threads + num_breakpoint_threads