Skip to content

Commit

Permalink
Add jThreadsInfo support to lldb-server
Browse files Browse the repository at this point in the history
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
  • Loading branch information
labath committed Jul 16, 2015
1 parent a0cd89a commit 4a4bb12
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 30 deletions.
64 changes: 64 additions & 0 deletions lldb/docs/lldb-gdb-remote.txt
Expand Up @@ -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!
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2637,6 +2690,92 @@ GDBRemoteCommunicationServerLLGS::Handle_qThreadStopInfo (StringExtractorGDBRemo
return SendStopReplyPacketForThread (tid);
}

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)
{
Expand Down
Expand Up @@ -240,6 +240,9 @@ class GDBRemoteCommunicationServerLLGS :
PacketResult
Handle_qThreadStopInfo (StringExtractorGDBRemote &packet);

PacketResult
Handle_jThreadsInfo (StringExtractorGDBRemote &packet);

PacketResult
Handle_qWatchpointSupportInfo (StringExtractorGDBRemote &packet);

Expand Down
4 changes: 4 additions & 0 deletions lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
Expand Up @@ -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();
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
});

Expand Down
4 changes: 3 additions & 1 deletion lldb/source/Utility/StringExtractorGDBRemote.cpp
Expand Up @@ -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:"))
Expand Down
1 change: 1 addition & 0 deletions lldb/source/Utility/StringExtractorGDBRemote.h
Expand Up @@ -97,6 +97,7 @@ class StringExtractorGDBRemote : public StringExtractor
eServerPacketType_QSyncThreadState,
eServerPacketType_QThreadSuffixSupported,

eServerPacketType_jThreadsInfo,
eServerPacketType_qsThreadInfo,
eServerPacketType_qfThreadInfo,
eServerPacketType_qGetPid,
Expand Down
Expand Up @@ -503,7 +503,7 @@ def do_thread_actions(self,

# 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
Expand Down

0 comments on commit 4a4bb12

Please sign in to comment.