Add jThreadsInfo support to lldb-server
authorPavel Labath <labath@google.com>
Thu, 16 Jul 2015 14:14:35 +0000 (14:14 +0000)
committerPavel Labath <labath@google.com>
Thu, 16 Jul 2015 14:14:35 +0000 (14:14 +0000)
Summary:
This commit adds initial support for the jThreadsInfo packet to lldb-server. The current
implementation does not expedite inferior memory.  I have also added a description of the new
packet to our protocol documentation (mostly taken from Greg's earlier commit message).

Reviewers: clayborg, ovyalov, tberghammer

Subscribers: lldb-commits

Differential Revision: http://reviews.llvm.org/D11187

llvm-svn: 242402

lldb/docs/lldb-gdb-remote.txt
lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
lldb/source/Utility/StringExtractorGDBRemote.cpp
lldb/source/Utility/StringExtractorGDBRemote.h
lldb/test/functionalities/thread/concurrent_events/TestConcurrentEvents.py

index 78c5b32..1cfaf82 100644 (file)
@@ -1530,3 +1530,67 @@ for this region.
 //  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. Note that the register
+numbers are decimal numbers, unlike the stop-reply packet, where they are written in
+hex. The packet 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 c452325..fc3361e 100644 (file)
@@ -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::RegisterPacketHandlers()
                                   &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,83 @@ WriteRegisterValueInHexFixedWidth (StreamString &response,
     }
 }
 
+static JSONObject::SP
+GetRegistersAsJSON(NativeThreadProtocol &thread)
+{
+    Log *log (GetLogIfAnyCategoriesSet (LIBLLDB_LOG_THREAD));
+
+    NativeRegisterContextSP reg_ctx_sp = thread.GetRegisterContext ();
+    if (! reg_ctx_sp)
+        return nullptr;
+
+    JSONObject::SP register_object_sp = std::make_shared<JSONObject>();
+    // Expedite all registers in the first register set (i.e. should be GPRs) that are not contained in other registers.
+    const RegisterSet *reg_set_p = reg_ctx_sp->GetRegisterSet(0);
+    if (! reg_set_p)
+        return nullptr;
+
+    for (const uint32_t *reg_num_p = reg_set_p->registers; *reg_num_p != LLDB_INVALID_REGNUM; ++reg_num_p)
+    {
+        const RegisterInfo *const reg_info_p = reg_ctx_sp->GetRegisterInfoAtIndex(*reg_num_p);
+        if (reg_info_p == nullptr)
+        {
+            if (log)
+                log->Printf("%s failed to get register info for register index %" PRIu32,
+                        __FUNCTION__, *reg_num_p);
+            continue;
+        }
+
+        if (reg_info_p->value_regs != nullptr)
+            continue; // Only expedite registers that are not contained in other registers.
+
+        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>", *reg_num_p,
+                        error.AsCString ());
+            continue;
+        }
+
+        StreamString stream;
+        WriteRegisterValueInHexFixedWidth(stream, reg_ctx_sp, *reg_info_p, &reg_value);
+
+        register_object_sp->SetObject(std::to_string(*reg_num_p),
+                std::make_shared<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";
+    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 +675,7 @@ GDBRemoteCommunicationServerLLGS::SendStopReplyPacketForThread (lldb::tid_t tid)
         }
     }
 
-    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 +2691,92 @@ GDBRemoteCommunicationServerLLGS::Handle_qThreadStopInfo (StringExtractorGDBRemo
 }
 
 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 = std::make_shared<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 = 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<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 = std::make_shared<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 29f3fde..0d258fb 100644 (file)
@@ -241,6 +241,9 @@ protected:
     Handle_qThreadStopInfo (StringExtractorGDBRemote &packet);
 
     PacketResult
+    Handle_jThreadsInfo (StringExtractorGDBRemote &packet);
+
+    PacketResult
     Handle_qWatchpointSupportInfo (StringExtractorGDBRemote &packet);
 
     PacketResult
index dbd68b4..94dac0c 100644 (file)
@@ -1397,6 +1397,7 @@ ProcessGDBRemote::WillResume ()
     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 @@ ProcessGDBRemote::SetThreadStopInfo (StructuredData::Dictionary *thread_dict)
     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 @@ ProcessGDBRemote::SetThreadStopInfo (StructuredData::Dictionary *thread_dict)
             }
 
         }
+        else if (key == g_key_signal)
+            signo = object->GetIntegerValue(LLDB_INVALID_SIGNAL_NUMBER);
         return true; // Keep iterating through all dictionary key/value pairs
     });
 
index 17ee0b6..e158569 100644 (file)
@@ -221,7 +221,9 @@ StringExtractorGDBRemote::GetServerPacketType () const
         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 475b5a8..da04a12 100644 (file)
@@ -97,6 +97,7 @@ public:
         eServerPacketType_QSyncThreadState,
         eServerPacketType_QThreadSuffixSupported,
 
+        eServerPacketType_jThreadsInfo,
         eServerPacketType_qsThreadInfo,
         eServerPacketType_qfThreadInfo,
         eServerPacketType_qGetPid,
index f0f2c3f..ae073f0 100644 (file)
@@ -503,7 +503,7 @@ class ConcurrentEventsTestCase(TestBase):
 
             # 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