labath created this revision. labath added reviewers: clayborg, ovyalov, tberghammer. labath added a subscriber: lldb-commits.
This commit adds initial support for the jThreadsInfo packet to lldb-server. The current implementation does not expedite inferior memory. It also does not send over all GPR, but only the most important ones (PC, SP, FP, RA), as for some reasons writing the registers is incredibly slow in lldb-server (I intend to investigate this further). Nevertheless, this implementation speeds up stepping of a heavily multi-threaded inferior significantly (about 50%). I have also added a description of the new packet to our protocol documentation (mostly taken from Greg's earlier commit message). http://reviews.llvm.org/D11187 Files: docs/lldb-gdb-remote.txt source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp source/Utility/StringExtractorGDBRemote.cpp source/Utility/StringExtractorGDBRemote.h test/functionalities/thread/concurrent_events/TestConcurrentEvents.py
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 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: 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/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 +GetGPRAsJSON(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 : "<unnamed-register>", 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<JSONNumber>(tid)); + if (signum != LLDB_INVALID_SIGNAL_NUMBER) + thread_obj_sp->SetObject("signal", std::make_shared<JSONNumber>(uint64_t(signum))); + + const std::string thread_name = thread_sp->GetName (); + if (! thread_name.empty()) + thread_obj_sp->SetObject("name", std::make_shared<JSONString>(thread_name)); + + if (JSONObject::SP registers_sp = GetGPRAsJSON(*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<JSONString>(stop_reason_str)); + + if (! description.empty()) + thread_obj_sp->SetObject("description", std::make_shared<JSONString>(description)); + + if ((tid_stop_info.reason == eStopReasonException) && tid_stop_info.details.exception.type) + { + thread_obj_sp->SetObject("metype", + std::make_shared<JSONNumber>(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<JSONNumber>( + 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: 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!
_______________________________________________ lldb-commits mailing list lldb-commits@cs.uiuc.edu http://lists.cs.uiuc.edu/mailman/listinfo/lldb-commits