Reduced packet counts to the remote GDB server where possible.

We have been working on reducing the packet count that is sent between LLDB and the debugserver on MacOSX and iOS. Our approach to this was to reduce the packets required when debugging multiple threads. We currently make one qThreadStopInfoXXXX call (where XXXX is the thread ID in hex) per thread except the thread that stopped with a stop reply packet. In order to implement multiple thread infos in a single reply, we need to use structured data, which means JSON. The new jThreadsInfo packet will attempt to retrieve all thread infos in a single packet. The data is very similar to the stop reply packets, but 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 dicitionaries with all of the key value pairs that are normally in the stop reply packet. Including the expedited registers. Notice that is also contains expedited memory in the "memory" key. Any values in this memory will get included in a new L1 cache in lldb_private::Process where if a memory read request is made and that memory request fits into one of the L1 memory cache blocks, it will use that memory data. If a memory request fails in the L1 cache, it will fall back to the L2 cache which is the same block sized caching we were using before these changes. This allows a process to expedite memory that you are likely to use and it reduces 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!

We will try these packets out and if successful, we should port these to lldb-server in the near future. 

<rdar://problem/21494354>

llvm-svn: 240354
This commit is contained in:
Greg Clayton 2015-06-22 23:12:45 +00:00
parent ab9db51042
commit ffb2d44ab9
15 changed files with 1369 additions and 279 deletions

View File

@ -128,9 +128,10 @@ namespace lldb_private {
{
return Contains(range.GetRangeBase()) && ContainsEndInclusive(range.GetRangeEnd());
}
// Returns true if the two ranges adjoing or intersect
bool
Overlap (const Range &rhs) const
DoesAdjoinOrIntersect (const Range &rhs) const
{
const BaseType lhs_base = this->GetRangeBase();
const BaseType rhs_base = rhs.GetRangeBase();
@ -139,7 +140,19 @@ namespace lldb_private {
bool result = (lhs_base <= rhs_end) && (lhs_end >= rhs_base);
return result;
}
// Returns true if the two ranges intersect
bool
DoesIntersect (const Range &rhs) const
{
const BaseType lhs_base = this->GetRangeBase();
const BaseType rhs_base = rhs.GetRangeBase();
const BaseType lhs_end = this->GetRangeEnd();
const BaseType rhs_end = rhs.GetRangeEnd();
bool result = (lhs_base < rhs_end) && (lhs_end > rhs_base);
return result;
}
bool
operator < (const Range &rhs) const
{
@ -241,7 +254,7 @@ namespace lldb_private {
// don't end up allocating and making a new collection for no reason
for (pos = m_entries.begin(), end = m_entries.end(), prev = end; pos != end; prev = pos++)
{
if (prev != end && prev->Overlap(*pos))
if (prev != end && prev->DoesAdjoinOrIntersect(*pos))
{
can_combine = true;
break;
@ -255,7 +268,7 @@ namespace lldb_private {
Collection minimal_ranges;
for (pos = m_entries.begin(), end = m_entries.end(), prev = end; pos != end; prev = pos++)
{
if (prev != end && prev->Overlap(*pos))
if (prev != end && prev->DoesAdjoinOrIntersect(*pos))
minimal_ranges.back().SetRangeEnd (std::max<BaseType>(prev->GetRangeEnd(), pos->GetRangeEnd()));
else
minimal_ranges.push_back (*pos);
@ -521,7 +534,7 @@ namespace lldb_private {
// don't end up allocating and making a new collection for no reason
for (pos = m_entries.begin(), end = m_entries.end(), prev = end; pos != end; prev = pos++)
{
if (prev != end && prev->Overlap(*pos))
if (prev != end && prev->DoesAdjoinOrIntersect(*pos))
{
can_combine = true;
break;
@ -535,7 +548,7 @@ namespace lldb_private {
Collection minimal_ranges;
for (pos = m_entries.begin(), end = m_entries.end(), prev = end; pos != end; prev = pos++)
{
if (prev != end && prev->Overlap(*pos))
if (prev != end && prev->DoesAdjoinOrIntersect(*pos))
minimal_ranges.back().SetRangeEnd (std::max<BaseType>(prev->GetRangeEnd(), pos->GetRangeEnd()));
else
minimal_ranges.push_back (*pos);

View File

@ -13,10 +13,11 @@
// C Includes
// C++ Includes
#include <functional>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <string>
#include "llvm/ADT/StringRef.h"
@ -140,6 +141,15 @@ public:
return NULL;
}
uint64_t
GetIntegerValue (uint64_t fail_value = 0)
{
Integer *integer = GetAsInteger ();
if (integer)
return integer->GetValue();
return fail_value;
}
Float *
GetAsFloat ()
{
@ -148,6 +158,15 @@ public:
return NULL;
}
double
GetFloatValue (double fail_value = 0.0)
{
Float *f = GetAsFloat ();
if (f)
return f->GetValue();
return fail_value;
}
Boolean *
GetAsBoolean ()
{
@ -156,6 +175,15 @@ public:
return NULL;
}
bool
GetBooleanValue (bool fail_value = false)
{
Boolean *b = GetAsBoolean ();
if (b)
return b->GetValue();
return fail_value;
}
String *
GetAsString ()
{
@ -164,6 +192,19 @@ public:
return NULL;
}
std::string
GetStringValue(const char *fail_value = NULL)
{
String *s = GetAsString ();
if (s)
return s->GetValue();
if (fail_value && fail_value[0])
return std::string(fail_value);
return std::string();
}
Generic *
GetAsGeneric()
{
@ -197,6 +238,17 @@ public:
{
}
void
ForEach (std::function <bool(Object* object)> const &foreach_callback) const
{
for (const auto &object_sp : m_items)
{
if (foreach_callback(object_sp.get()) == false)
break;
}
}
size_t
GetSize() const
{
@ -447,7 +499,7 @@ public:
m_value = string;
}
std::string
const std::string &
GetValue ()
{
return m_value;
@ -462,6 +514,7 @@ public:
class Dictionary : public Object
{
public:
Dictionary () :
Object (Type::eTypeDictionary),
m_dict ()
@ -478,6 +531,16 @@ public:
return m_dict.size();
}
void
ForEach (std::function <bool(ConstString key, Object* object)> const &callback) const
{
for (const auto &pair : m_dict)
{
if (callback (pair.first, pair.second.get()) == false)
break;
}
}
ObjectSP
GetKeys() const
{
@ -628,33 +691,25 @@ public:
void
AddIntegerItem (llvm::StringRef key, uint64_t value)
{
ObjectSP val_obj (new Integer());
val_obj->GetAsInteger()->SetValue (value);
AddItem (key, val_obj);
AddItem (key, ObjectSP (new Integer(value)));
}
void
AddFloatItem (llvm::StringRef key, double value)
{
ObjectSP val_obj (new Float());
val_obj->GetAsFloat()->SetValue (value);
AddItem (key, val_obj);
AddItem (key, ObjectSP (new Float(value)));
}
void
AddStringItem (llvm::StringRef key, std::string value)
{
ObjectSP val_obj (new String());
val_obj->GetAsString()->SetValue (value);
AddItem (key, val_obj);
AddItem (key, ObjectSP (new String(std::move(value))));
}
void
AddBooleanItem (llvm::StringRef key, bool value)
{
ObjectSP val_obj (new Boolean());
val_obj->GetAsBoolean()->SetValue (value);
AddItem (key, val_obj);
AddItem (key, ObjectSP (new Boolean(value)));
}
void Dump(Stream &s) const override;
@ -690,9 +745,9 @@ public:
class Generic : public Object
{
public:
explicit Generic(void *object = nullptr)
: Object(Type::eTypeGeneric)
, m_object(object)
explicit Generic(void *object = nullptr) :
Object (Type::eTypeGeneric),
m_object (object)
{
}

View File

@ -52,7 +52,7 @@ namespace lldb_private {
uint32_t
GetMemoryCacheLineSize() const
{
return m_cache_line_byte_size ;
return m_L2_cache_line_byte_size ;
}
void
@ -61,17 +61,26 @@ namespace lldb_private {
bool
RemoveInvalidRange (lldb::addr_t base_addr, lldb::addr_t byte_size);
// Allow external sources to populate data into the L1 memory cache
void
AddL1CacheData(lldb::addr_t addr, const void *src, size_t src_len);
void
AddL1CacheData(lldb::addr_t addr, const lldb::DataBufferSP &data_buffer_sp);
protected:
typedef std::map<lldb::addr_t, lldb::DataBufferSP> BlockMap;
typedef RangeArray<lldb::addr_t, lldb::addr_t, 4> InvalidRanges;
typedef Range<lldb::addr_t, lldb::addr_t> AddrRange;
//------------------------------------------------------------------
// Classes that inherit from MemoryCache can see and modify these
//------------------------------------------------------------------
Process &m_process;
uint32_t m_cache_line_byte_size;
Mutex m_mutex;
BlockMap m_cache;
BlockMap m_L1_cache; // A first level memory cache whose chunk sizes vary that will be used only if the memory read fits entirely in a chunk
BlockMap m_L2_cache; // A memory cache of fixed size chinks (m_L2_cache_line_byte_size bytes in size each)
InvalidRanges m_invalid_ranges;
Process &m_process;
uint32_t m_L2_cache_line_byte_size;
private:
DISALLOW_COPY_AND_ASSIGN (MemoryCache);
};

View File

@ -114,13 +114,13 @@ SBThread::GetQueue () const
else
{
if (log)
log->Printf ("SBThread(%p)::GetQueueKind() => error: process is running",
log->Printf ("SBThread(%p)::GetQueue() => error: process is running",
static_cast<void*>(exe_ctx.GetThreadPtr()));
}
}
if (log)
log->Printf ("SBThread(%p)::GetQueueKind () => SBQueue(%p)",
log->Printf ("SBThread(%p)::GetQueue () => SBQueue(%p)",
static_cast<void*>(exe_ctx.GetThreadPtr()), static_cast<void*>(queue_sp.get()));
return sb_queue;

View File

@ -298,6 +298,10 @@ StructuredData::ParseJSON (std::string json_text)
{
object_sp = read_json_object (&c);
}
else if (*c == '[')
{
object_sp = read_json_array (&c);
}
else
{
// We have bad characters here, this is likely an illegal JSON string.

View File

@ -98,6 +98,7 @@ GDBRemoteCommunicationClient::GDBRemoteCommunicationClient() :
m_supports_z4 (true),
m_supports_QEnvironment (true),
m_supports_QEnvironmentHexEncoded (true),
m_supports_jThreadsInfo (true),
m_curr_pid (LLDB_INVALID_PROCESS_ID),
m_curr_tid (LLDB_INVALID_THREAD_ID),
m_curr_tid_run (LLDB_INVALID_THREAD_ID),
@ -601,6 +602,32 @@ GDBRemoteCommunicationClient::GetpPacketSupported (lldb::tid_t tid)
return m_supports_p;
}
StructuredData::ObjectSP
GDBRemoteCommunicationClient::GetThreadsInfo()
{
// Get information on all threads at one using the "jThreadsInfo" packet
StructuredData::ObjectSP object_sp;
if (m_supports_jThreadsInfo)
{
StringExtractorGDBRemote response;
m_supports_jThreadExtendedInfo = eLazyBoolNo;
if (SendPacketAndWaitForResponse("jThreadsInfo", response, false) == PacketResult::Success)
{
if (response.IsUnsupportedResponse())
{
m_supports_jThreadsInfo = false;
}
else if (!response.Empty())
{
object_sp = StructuredData::ParseJSON (response.GetStringRef());
}
}
}
return object_sp;
}
bool
GDBRemoteCommunicationClient::GetThreadExtendedInfoSupported ()
{

View File

@ -17,6 +17,7 @@
// Other libraries and framework includes
// Project includes
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/StructuredData.h"
#include "lldb/Target/Process.h"
#include "GDBRemoteCommunication.h"
@ -537,6 +538,9 @@ public:
bool
AvoidGPackets(ProcessGDBRemote *process);
StructuredData::ObjectSP
GetThreadsInfo();
bool
GetThreadExtendedInfoSupported();
@ -615,7 +619,8 @@ protected:
m_supports_z3:1,
m_supports_z4:1,
m_supports_QEnvironment:1,
m_supports_QEnvironmentHexEncoded:1;
m_supports_QEnvironmentHexEncoded:1,
m_supports_jThreadsInfo:1;
lldb::pid_t m_curr_pid;
lldb::tid_t m_curr_tid; // Current gdb remote protocol thread index for all other operations

View File

@ -378,6 +378,7 @@ ProcessGDBRemote::ProcessGDBRemote(Target& target, Listener &listener) :
m_async_broadcaster (NULL, "lldb.process.gdb-remote.async-broadcaster"),
m_async_thread_state_mutex(Mutex::eMutexTypeRecursive),
m_thread_ids (),
m_threads_info_sp (),
m_continue_c_tids (),
m_continue_C_tids (),
m_continue_s_tids (),
@ -1784,6 +1785,356 @@ ProcessGDBRemote::UpdateThreadList (ThreadList &old_thread_list, ThreadList &new
return true;
}
bool
ProcessGDBRemote::CalculateThreadStopInfo (ThreadGDBRemote *thread)
{
// See if we got thread stop infos for all threads via the "jThreadsInfo" packet
if (m_threads_info_sp)
{
StructuredData::Array *thread_infos = m_threads_info_sp->GetAsArray();
if (thread_infos)
{
lldb::tid_t tid;
const size_t n = thread_infos->GetSize();
for (size_t i=0; i<n; ++i)
{
StructuredData::Dictionary *thread_dict = thread_infos->GetItemAtIndex(i)->GetAsDictionary();
if (thread_dict)
{
if (thread_dict->GetValueForKeyAsInteger<lldb::tid_t>("tid", tid, LLDB_INVALID_THREAD_ID))
{
if (tid == thread->GetID())
return SetThreadStopInfo(thread_dict);
}
}
}
}
}
// Fall back to using the qThreadStopInfo packet
StringExtractorGDBRemote stop_packet;
if (GetGDBRemote().GetThreadStopInfo(thread->GetProtocolID(), stop_packet))
return SetThreadStopInfo (stop_packet) == eStateStopped;
return false;
}
ThreadSP
ProcessGDBRemote::SetThreadStopInfo (lldb::tid_t tid,
uint8_t signo,
const std::string &thread_name,
const std::string &reason,
const std::string &description,
uint32_t exc_type,
const std::vector<addr_t> &exc_data,
addr_t thread_dispatch_qaddr)
{
ThreadSP thread_sp;
if (tid != LLDB_INVALID_THREAD_ID)
{
// Scope for "locker" below
{
// m_thread_list_real does have its own mutex, but we need to
// hold onto the mutex between the call to m_thread_list_real.FindThreadByID(...)
// and the m_thread_list_real.AddThread(...) so it doesn't change on us
Mutex::Locker locker (m_thread_list_real.GetMutex ());
thread_sp = m_thread_list_real.FindThreadByProtocolID(tid, false);
if (!thread_sp)
{
// Create the thread if we need to
thread_sp.reset (new ThreadGDBRemote (*this, tid));
m_thread_list_real.AddThread(thread_sp);
}
}
if (thread_sp)
{
ThreadGDBRemote *gdb_thread = static_cast<ThreadGDBRemote *> (thread_sp.get());
// Clear the stop info just in case we don't set it to anything
thread_sp->SetStopInfo (StopInfoSP());
thread_sp->SetName (thread_name.empty() ? NULL : thread_name.c_str());
gdb_thread->SetThreadDispatchQAddr (thread_dispatch_qaddr);
if (exc_type != 0)
{
const size_t exc_data_size = exc_data.size();
thread_sp->SetStopInfo (StopInfoMachException::CreateStopReasonWithMachException (*thread_sp,
exc_type,
exc_data_size,
exc_data_size >= 1 ? exc_data[0] : 0,
exc_data_size >= 2 ? exc_data[1] : 0,
exc_data_size >= 3 ? exc_data[2] : 0));
}
else
{
bool handled = false;
bool did_exec = false;
if (!reason.empty())
{
if (reason.compare("trace") == 0)
{
thread_sp->SetStopInfo (StopInfo::CreateStopReasonToTrace (*thread_sp));
handled = true;
}
else if (reason.compare("breakpoint") == 0)
{
addr_t pc = thread_sp->GetRegisterContext()->GetPC();
lldb::BreakpointSiteSP bp_site_sp = thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc);
if (bp_site_sp)
{
// If the breakpoint is for this thread, then we'll report the hit, but if it is for another thread,
// we can just report no reason. We don't need to worry about stepping over the breakpoint here, that
// will be taken care of when the thread resumes and notices that there's a breakpoint under the pc.
handled = true;
if (bp_site_sp->ValidForThisThread (thread_sp.get()))
{
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithBreakpointSiteID (*thread_sp, bp_site_sp->GetID()));
}
else
{
StopInfoSP invalid_stop_info_sp;
thread_sp->SetStopInfo (invalid_stop_info_sp);
}
}
}
else if (reason.compare("trap") == 0)
{
// Let the trap just use the standard signal stop reason below...
}
else if (reason.compare("watchpoint") == 0)
{
StringExtractor desc_extractor(description.c_str());
addr_t wp_addr = desc_extractor.GetU64(LLDB_INVALID_ADDRESS);
uint32_t wp_index = desc_extractor.GetU32(LLDB_INVALID_INDEX32);
watch_id_t watch_id = LLDB_INVALID_WATCH_ID;
if (wp_addr != LLDB_INVALID_ADDRESS)
{
WatchpointSP wp_sp = GetTarget().GetWatchpointList().FindByAddress(wp_addr);
if (wp_sp)
{
wp_sp->SetHardwareIndex(wp_index);
watch_id = wp_sp->GetID();
}
}
if (watch_id == LLDB_INVALID_WATCH_ID)
{
Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_WATCHPOINTS));
if (log) log->Printf ("failed to find watchpoint");
}
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithWatchpointID (*thread_sp, watch_id));
handled = true;
}
else if (reason.compare("exception") == 0)
{
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithException(*thread_sp, description.c_str()));
handled = true;
}
else if (reason.compare("exec") == 0)
{
did_exec = true;
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithExec(*thread_sp));
handled = true;
}
}
if (!handled && signo && did_exec == false)
{
if (signo == SIGTRAP)
{
// Currently we are going to assume SIGTRAP means we are either
// hitting a breakpoint or hardware single stepping.
handled = true;
addr_t pc = thread_sp->GetRegisterContext()->GetPC() + m_breakpoint_pc_offset;
lldb::BreakpointSiteSP bp_site_sp = thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc);
if (bp_site_sp)
{
// If the breakpoint is for this thread, then we'll report the hit, but if it is for another thread,
// we can just report no reason. We don't need to worry about stepping over the breakpoint here, that
// will be taken care of when the thread resumes and notices that there's a breakpoint under the pc.
if (bp_site_sp->ValidForThisThread (thread_sp.get()))
{
if(m_breakpoint_pc_offset != 0)
thread_sp->GetRegisterContext()->SetPC(pc);
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithBreakpointSiteID (*thread_sp, bp_site_sp->GetID()));
}
else
{
StopInfoSP invalid_stop_info_sp;
thread_sp->SetStopInfo (invalid_stop_info_sp);
}
}
else
{
// If we were stepping then assume the stop was the result of the trace. If we were
// not stepping then report the SIGTRAP.
// FIXME: We are still missing the case where we single step over a trap instruction.
if (thread_sp->GetTemporaryResumeState() == eStateStepping)
thread_sp->SetStopInfo (StopInfo::CreateStopReasonToTrace (*thread_sp));
else
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithSignal(*thread_sp, signo, description.c_str()));
}
}
if (!handled)
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithSignal (*thread_sp, signo, description.c_str()));
}
if (!description.empty())
{
lldb::StopInfoSP stop_info_sp (thread_sp->GetStopInfo ());
if (stop_info_sp)
{
const char *stop_info_desc = stop_info_sp->GetDescription();
if (!stop_info_desc || !stop_info_desc[0])
stop_info_sp->SetDescription (description.c_str());
}
else
{
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithException (*thread_sp, description.c_str()));
}
}
}
}
}
return thread_sp;
}
StateType
ProcessGDBRemote::SetThreadStopInfo (StructuredData::Dictionary *thread_dict)
{
static ConstString g_key_tid("tid");
static ConstString g_key_name("name");
static ConstString g_key_reason("reason");
static ConstString g_key_metype("metype");
static ConstString g_key_medata("medata");
static ConstString g_key_qaddr("qaddr");
static ConstString g_key_registers("registers");
static ConstString g_key_memory("memory");
static ConstString g_key_address("address");
static ConstString g_key_bytes("bytes");
static ConstString g_key_description("description");
// Stop with signal and thread info
lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
uint8_t signo = 0;
std::string value;
std::string thread_name;
std::string reason;
std::string description;
uint32_t exc_type = 0;
std::vector<addr_t> exc_data;
addr_t thread_dispatch_qaddr = LLDB_INVALID_ADDRESS;
StructuredData::Dictionary *registers_dict = nullptr;
// Iterate through all of the thread dictionary key/value pairs from the structured data dictionary
thread_dict->ForEach([this, &tid, &registers_dict, &thread_name, &signo, &reason, &description, &exc_type, &exc_data, &thread_dispatch_qaddr](ConstString key, StructuredData::Object* object) -> bool
{
if (key == g_key_tid)
{
// thread in big endian hex
tid = object->GetIntegerValue(LLDB_INVALID_THREAD_ID);
}
else if (key == g_key_metype)
{
// exception type in big endian hex
exc_type = object->GetIntegerValue(0);
}
else if (key == g_key_medata)
{
// exception data in big endian hex
StructuredData::Array *array = object->GetAsArray();
if (array)
{
array->ForEach([&exc_data](StructuredData::Object* object) -> bool {
exc_data.push_back(object->GetIntegerValue());
return true; // Keep iterating through all array items
});
}
}
else if (key == g_key_name)
{
thread_name = std::move(object->GetStringValue());
}
else if (key == g_key_qaddr)
{
thread_dispatch_qaddr = object->GetIntegerValue(LLDB_INVALID_ADDRESS);
}
else if (key == g_key_reason)
{
reason = std::move(object->GetStringValue());
}
else if (key == g_key_description)
{
description = std::move(object->GetStringValue());
}
else if (key == g_key_registers)
{
registers_dict = object->GetAsDictionary();
}
else if (key == g_key_memory)
{
StructuredData::Array *array = object->GetAsArray();
if (array)
{
array->ForEach([this](StructuredData::Object* object) -> bool {
StructuredData::Dictionary *mem_cache_dict = object->GetAsDictionary();
if (mem_cache_dict)
{
lldb::addr_t mem_cache_addr = LLDB_INVALID_ADDRESS;
if (mem_cache_dict->GetValueForKeyAsInteger<lldb::addr_t>("address", mem_cache_addr))
{
if (mem_cache_addr != LLDB_INVALID_ADDRESS)
{
StringExtractor bytes;
if (mem_cache_dict->GetValueForKeyAsString("bytes", bytes.GetStringRef()))
{
bytes.SetFilePos(0);
const size_t byte_size = bytes.GetStringRef().size()/2;
DataBufferSP data_buffer_sp(new DataBufferHeap(byte_size, 0));
const size_t bytes_copied = bytes.GetHexBytes (data_buffer_sp->GetBytes(), byte_size, 0);
if (bytes_copied == byte_size)
m_memory_cache.AddL1CacheData(mem_cache_addr, data_buffer_sp);
}
}
}
}
return true; // Keep iterating through all array items
});
}
}
return true; // Keep iterating through all dictionary key/value pairs
});
ThreadSP thread_sp = SetThreadStopInfo (tid, signo, thread_name, reason, description, exc_type, exc_data, thread_dispatch_qaddr);
if (thread_sp)
{
// Process any expedited register values so we don't have to read them with p/P or g/G packets
if (registers_dict)
{
ThreadGDBRemote *gdb_thread = static_cast<ThreadGDBRemote *> (thread_sp.get());
registers_dict->ForEach([gdb_thread](ConstString key, StructuredData::Object* object) -> bool {
const uint32_t reg = StringConvert::ToUInt32 (key.GetCString(), UINT32_MAX, 10);
if (reg != UINT32_MAX)
{
StringExtractor reg_value_extractor;
reg_value_extractor.GetStringRef() = std::move(object->GetStringValue());
gdb_thread->PrivateSetRegisterValue (reg, reg_value_extractor);
}
return true; // Keep iterating through all array items
});
}
return eStateStopped;
}
return eStateExited;
}
StateType
ProcessGDBRemote::SetThreadStopInfo (StringExtractor& stop_packet)
@ -1814,8 +2165,9 @@ ProcessGDBRemote::SetThreadStopInfo (StringExtractor& stop_packet)
BuildDynamicRegisterInfo (true);
}
// Stop with signal and thread info
lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
const uint8_t signo = stop_packet.GetHexU8();
std::string name;
std::string key;
std::string value;
std::string thread_name;
std::string reason;
@ -1823,48 +2175,26 @@ ProcessGDBRemote::SetThreadStopInfo (StringExtractor& stop_packet)
uint32_t exc_type = 0;
std::vector<addr_t> exc_data;
addr_t thread_dispatch_qaddr = LLDB_INVALID_ADDRESS;
ThreadSP thread_sp;
ThreadGDBRemote *gdb_thread = NULL;
while (stop_packet.GetNameColonValue(name, value))
typedef std::map<uint32_t, std::string> ExpeditedRegisterMap;
ExpeditedRegisterMap expedited_register_map;
while (stop_packet.GetNameColonValue(key, value))
{
if (name.compare("metype") == 0)
if (key.compare("metype") == 0)
{
// exception type in big endian hex
exc_type = StringConvert::ToUInt32 (value.c_str(), 0, 16);
}
else if (name.compare("medata") == 0)
else if (key.compare("medata") == 0)
{
// exception data in big endian hex
exc_data.push_back(StringConvert::ToUInt64 (value.c_str(), 0, 16));
}
else if (name.compare("thread") == 0)
else if (key.compare("thread") == 0)
{
// thread in big endian hex
lldb::tid_t tid = StringConvert::ToUInt64 (value.c_str(), LLDB_INVALID_THREAD_ID, 16);
// m_thread_list_real does have its own mutex, but we need to
// hold onto the mutex between the call to m_thread_list_real.FindThreadByID(...)
// and the m_thread_list_real.AddThread(...) so it doesn't change on us
Mutex::Locker locker (m_thread_list_real.GetMutex ());
thread_sp = m_thread_list_real.FindThreadByProtocolID(tid, false);
if (!thread_sp)
{
// Create the thread if we need to
thread_sp.reset (new ThreadGDBRemote (*this, tid));
Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_THREAD));
if (log && log->GetMask().Test(GDBR_LOG_VERBOSE))
log->Printf ("ProcessGDBRemote::%s Adding new thread: %p for thread ID: 0x%" PRIx64 ".\n",
__FUNCTION__,
static_cast<void*>(thread_sp.get()),
thread_sp->GetID());
m_thread_list_real.AddThread(thread_sp);
}
gdb_thread = static_cast<ThreadGDBRemote *> (thread_sp.get());
tid = StringConvert::ToUInt64 (value.c_str(), LLDB_INVALID_THREAD_ID, 16);
}
else if (name.compare("threads") == 0)
else if (key.compare("threads") == 0)
{
Mutex::Locker locker(m_thread_list_real.GetMutex());
m_thread_ids.clear();
@ -1886,7 +2216,7 @@ ProcessGDBRemote::SetThreadStopInfo (StringExtractor& stop_packet)
if (tid != LLDB_INVALID_THREAD_ID)
m_thread_ids.push_back (tid);
}
else if (name.compare("hexname") == 0)
else if (key.compare("hexname") == 0)
{
StringExtractor name_extractor;
// Swap "value" over into "name_extractor"
@ -1895,19 +2225,19 @@ ProcessGDBRemote::SetThreadStopInfo (StringExtractor& stop_packet)
name_extractor.GetHexByteString (value);
thread_name.swap (value);
}
else if (name.compare("name") == 0)
else if (key.compare("name") == 0)
{
thread_name.swap (value);
}
else if (name.compare("qaddr") == 0)
else if (key.compare("qaddr") == 0)
{
thread_dispatch_qaddr = StringConvert::ToUInt64 (value.c_str(), 0, 16);
}
else if (name.compare("reason") == 0)
else if (key.compare("reason") == 0)
{
reason.swap(value);
}
else if (name.compare("description") == 0)
else if (key.compare("description") == 0)
{
StringExtractor desc_extractor;
// Swap "value" over into "name_extractor"
@ -1916,34 +2246,49 @@ ProcessGDBRemote::SetThreadStopInfo (StringExtractor& stop_packet)
desc_extractor.GetHexByteString (value);
description.swap(value);
}
else if (name.size() == 2 && ::isxdigit(name[0]) && ::isxdigit(name[1]))
else if (key.compare("memory") == 0)
{
// We have a register number that contains an expedited
// register value. Lets supply this register to our thread
// so it won't have to go and read it.
if (gdb_thread)
{
uint32_t reg = StringConvert::ToUInt32 (name.c_str(), UINT32_MAX, 16);
// Expedited memory. GDB servers can choose to send back expedited memory
// that can populate the L1 memory cache in the process so that things like
// the frame pointer backchain can be expedited. This will help stack
// backtracing be more efficient by not having to send as many memory read
// requests down the remote GDB server.
if (reg != UINT32_MAX)
// Key/value pair format: memory:<addr>=<bytes>;
// <addr> is a number whose base will be interpreted by the prefix:
// "0x[0-9a-fA-F]+" for hex
// "0[0-7]+" for octal
// "[1-9]+" for decimal
// <bytes> is native endian ASCII hex bytes just like the register values
llvm::StringRef value_ref(value);
std::pair<llvm::StringRef, llvm::StringRef> pair;
pair = value_ref.split('=');
if (!pair.first.empty() && !pair.second.empty())
{
std::string addr_str(pair.first.str());
const lldb::addr_t mem_cache_addr = StringConvert::ToUInt64(addr_str.c_str(), LLDB_INVALID_ADDRESS, 0);
if (mem_cache_addr != LLDB_INVALID_ADDRESS)
{
StringExtractor reg_value_extractor;
// Swap "value" over into "reg_value_extractor"
reg_value_extractor.GetStringRef().swap(value);
if (!gdb_thread->PrivateSetRegisterValue (reg, reg_value_extractor))
{
Host::SetCrashDescriptionWithFormat("Setting thread register '%s' (decoded to %u (0x%x)) with value '%s' for stop packet: '%s'",
name.c_str(),
reg,
reg,
reg_value_extractor.GetStringRef().c_str(),
stop_packet.GetStringRef().c_str());
}
StringExtractor bytes;
bytes.GetStringRef() = std::move(pair.second.str());
const size_t byte_size = bytes.GetStringRef().size()/2;
DataBufferSP data_buffer_sp(new DataBufferHeap(byte_size, 0));
const size_t bytes_copied = bytes.GetHexBytes (data_buffer_sp->GetBytes(), byte_size, 0);
if (bytes_copied == byte_size)
m_memory_cache.AddL1CacheData(mem_cache_addr, data_buffer_sp);
}
}
}
else if (key.size() == 2 && ::isxdigit(key[0]) && ::isxdigit(key[1]))
{
uint32_t reg = StringConvert::ToUInt32 (key.c_str(), UINT32_MAX, 16);
if (reg != UINT32_MAX)
expedited_register_map[reg] = std::move(value);
}
}
ThreadSP thread_sp = SetThreadStopInfo (tid, signo, thread_name, reason, description, exc_type, exc_data, thread_dispatch_qaddr);
// If the response is old style 'S' packet which does not provide us with thread information
// then update the thread list and choose the first one.
if (!thread_sp)
@ -1954,156 +2299,23 @@ ProcessGDBRemote::SetThreadStopInfo (StringExtractor& stop_packet)
{
Mutex::Locker locker (m_thread_list_real.GetMutex ());
thread_sp = m_thread_list_real.FindThreadByProtocolID (m_thread_ids.front (), false);
if (thread_sp)
gdb_thread = static_cast<ThreadGDBRemote *> (thread_sp.get ());
}
}
if (thread_sp)
if (thread_sp && !expedited_register_map.empty())
{
// Clear the stop info just in case we don't set it to anything
thread_sp->SetStopInfo (StopInfoSP());
gdb_thread->SetThreadDispatchQAddr (thread_dispatch_qaddr);
gdb_thread->SetName (thread_name.empty() ? NULL : thread_name.c_str());
if (exc_type != 0)
// Process any expedited register values so we don't have to read them with p/P or g/G packets
ThreadGDBRemote *gdb_thread = static_cast<ThreadGDBRemote *> (thread_sp.get());
for (const auto &pair : expedited_register_map)
{
const size_t exc_data_size = exc_data.size();
thread_sp->SetStopInfo (StopInfoMachException::CreateStopReasonWithMachException (*thread_sp,
exc_type,
exc_data_size,
exc_data_size >= 1 ? exc_data[0] : 0,
exc_data_size >= 2 ? exc_data[1] : 0,
exc_data_size >= 3 ? exc_data[2] : 0));
}
else
{
bool handled = false;
bool did_exec = false;
if (!reason.empty())
StringExtractor reg_value_extractor;
reg_value_extractor.GetStringRef() = pair.second;
if (!gdb_thread->PrivateSetRegisterValue (pair.first, reg_value_extractor))
{
if (reason.compare("trace") == 0)
{
thread_sp->SetStopInfo (StopInfo::CreateStopReasonToTrace (*thread_sp));
handled = true;
}
else if (reason.compare("breakpoint") == 0)
{
addr_t pc = thread_sp->GetRegisterContext()->GetPC();
lldb::BreakpointSiteSP bp_site_sp = thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc);
if (bp_site_sp)
{
// If the breakpoint is for this thread, then we'll report the hit, but if it is for another thread,
// we can just report no reason. We don't need to worry about stepping over the breakpoint here, that
// will be taken care of when the thread resumes and notices that there's a breakpoint under the pc.
handled = true;
if (bp_site_sp->ValidForThisThread (thread_sp.get()))
{
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithBreakpointSiteID (*thread_sp, bp_site_sp->GetID()));
}
else
{
StopInfoSP invalid_stop_info_sp;
thread_sp->SetStopInfo (invalid_stop_info_sp);
}
}
}
else if (reason.compare("trap") == 0)
{
// Let the trap just use the standard signal stop reason below...
}
else if (reason.compare("watchpoint") == 0)
{
StringExtractor desc_extractor(description.c_str());
addr_t wp_addr = desc_extractor.GetU64(LLDB_INVALID_ADDRESS);
uint32_t wp_index = desc_extractor.GetU32(LLDB_INVALID_INDEX32);
watch_id_t watch_id = LLDB_INVALID_WATCH_ID;
if (wp_addr != LLDB_INVALID_ADDRESS)
{
WatchpointSP wp_sp = GetTarget().GetWatchpointList().FindByAddress(wp_addr);
if (wp_sp)
{
wp_sp->SetHardwareIndex(wp_index);
watch_id = wp_sp->GetID();
}
}
if (watch_id == LLDB_INVALID_WATCH_ID)
{
Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_WATCHPOINTS));
if (log) log->Printf ("failed to find watchpoint");
}
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithWatchpointID (*thread_sp, watch_id));
handled = true;
}
else if (reason.compare("exception") == 0)
{
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithException(*thread_sp, description.c_str()));
handled = true;
}
else if (reason.compare("exec") == 0)
{
did_exec = true;
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithExec(*thread_sp));
handled = true;
}
}
if (!handled && signo && did_exec == false)
{
if (signo == SIGTRAP)
{
// Currently we are going to assume SIGTRAP means we are either
// hitting a breakpoint or hardware single stepping.
handled = true;
addr_t pc = thread_sp->GetRegisterContext()->GetPC() + m_breakpoint_pc_offset;
lldb::BreakpointSiteSP bp_site_sp = thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc);
if (bp_site_sp)
{
// If the breakpoint is for this thread, then we'll report the hit, but if it is for another thread,
// we can just report no reason. We don't need to worry about stepping over the breakpoint here, that
// will be taken care of when the thread resumes and notices that there's a breakpoint under the pc.
if (bp_site_sp->ValidForThisThread (thread_sp.get()))
{
if(m_breakpoint_pc_offset != 0)
thread_sp->GetRegisterContext()->SetPC(pc);
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithBreakpointSiteID (*thread_sp, bp_site_sp->GetID()));
}
else
{
StopInfoSP invalid_stop_info_sp;
thread_sp->SetStopInfo (invalid_stop_info_sp);
}
}
else
{
// If we were stepping then assume the stop was the result of the trace. If we were
// not stepping then report the SIGTRAP.
// FIXME: We are still missing the case where we single step over a trap instruction.
if (thread_sp->GetTemporaryResumeState() == eStateStepping)
thread_sp->SetStopInfo (StopInfo::CreateStopReasonToTrace (*thread_sp));
else
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithSignal(*thread_sp, signo, description.c_str()));
}
}
if (!handled)
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithSignal (*thread_sp, signo, description.c_str()));
}
if (!description.empty())
{
lldb::StopInfoSP stop_info_sp (thread_sp->GetStopInfo ());
if (stop_info_sp)
{
const char *stop_info_desc = stop_info_sp->GetDescription();
if (!stop_info_desc || !stop_info_desc[0])
stop_info_sp->SetDescription (description.c_str());
}
else
{
thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithException (*thread_sp, description.c_str()));
}
Host::SetCrashDescriptionWithFormat("Setting thread register '0x%2.2x' with value '%s' for stop packet: '%s'",
pair.first,
reg_value_extractor.GetStringRef().c_str(),
stop_packet.GetStringRef().c_str());
}
}
}
@ -2163,6 +2375,11 @@ ProcessGDBRemote::RefreshStateAfterStop ()
m_initial_tid = LLDB_INVALID_THREAD_ID;
}
// Fetch the threads via an efficient packet that gets stop infos for all threads
// only if we have more than one thread
if (m_thread_ids.size() > 1)
m_threads_info_sp = m_gdb_comm.GetThreadsInfo();
// Let all threads recover from stopping and do any clean up based
// on the previous thread state (if any).
m_thread_list_real.RefreshStateAfterStop();

View File

@ -323,6 +323,9 @@ protected:
void
GetMaxMemorySize();
bool
CalculateThreadStopInfo (ThreadGDBRemote *thread);
//------------------------------------------------------------------
/// Broadcaster event bits definitions.
//------------------------------------------------------------------
@ -346,6 +349,7 @@ protected:
typedef std::vector< std::pair<lldb::tid_t,int> > tid_sig_collection;
typedef std::map<lldb::addr_t, lldb::addr_t> MMapMap;
tid_collection m_thread_ids; // Thread IDs for all threads. This list gets updated after stopping
StructuredData::ObjectSP m_threads_info_sp; // Stop info for all threads if "jThreadsInfo" packet is supported
tid_collection m_continue_c_tids; // 'c' for continue
tid_sig_collection m_continue_C_tids; // 'C' for continue with signal
tid_collection m_continue_s_tids; // 's' for step
@ -379,6 +383,19 @@ protected:
lldb::StateType
SetThreadStopInfo (StringExtractor& stop_packet);
lldb::StateType
SetThreadStopInfo (StructuredData::Dictionary *thread_dict);
lldb::ThreadSP
SetThreadStopInfo (lldb::tid_t tid,
uint8_t signo,
const std::string &thread_name,
const std::string &reason,
const std::string &description,
uint32_t exc_type,
const std::vector<lldb::addr_t> &exc_data,
lldb::addr_t thread_dispatch_qaddr);
void
HandleStopReplySequence ();

View File

@ -284,12 +284,7 @@ ThreadGDBRemote::CalculateStopInfo ()
{
ProcessSP process_sp (GetProcess());
if (process_sp)
{
StringExtractorGDBRemote stop_packet;
ProcessGDBRemote *gdb_process = static_cast<ProcessGDBRemote *>(process_sp.get());
if (gdb_process->GetGDBRemote().GetThreadStopInfo(GetProtocolID(), stop_packet))
return gdb_process->SetThreadStopInfo (stop_packet) == eStateStopped;
}
return static_cast<ProcessGDBRemote *>(process_sp.get())->CalculateThreadStopInfo(this);
return false;
}

View File

@ -14,8 +14,9 @@
// Other libraries and framework includes
// Project includes
#include "lldb/Core/DataBufferHeap.h"
#include "lldb/Core/State.h"
#include "lldb/Core/Log.h"
#include "lldb/Core/RangeMap.h"
#include "lldb/Core/State.h"
#include "lldb/Target/Process.h"
using namespace lldb;
@ -25,11 +26,12 @@ using namespace lldb_private;
// MemoryCache constructor
//----------------------------------------------------------------------
MemoryCache::MemoryCache(Process &process) :
m_process (process),
m_cache_line_byte_size (process.GetMemoryCacheLineSize()),
m_mutex (Mutex::eMutexTypeRecursive),
m_cache (),
m_invalid_ranges ()
m_L1_cache (),
m_L2_cache (),
m_invalid_ranges (),
m_process (process),
m_L2_cache_line_byte_size (process.GetMemoryCacheLineSize())
{
}
@ -44,10 +46,24 @@ void
MemoryCache::Clear(bool clear_invalid_ranges)
{
Mutex::Locker locker (m_mutex);
m_cache.clear();
m_L1_cache.clear();
m_L2_cache.clear();
if (clear_invalid_ranges)
m_invalid_ranges.Clear();
m_cache_line_byte_size = m_process.GetMemoryCacheLineSize();
m_L2_cache_line_byte_size = m_process.GetMemoryCacheLineSize();
}
void
MemoryCache::AddL1CacheData(lldb::addr_t addr, const void *src, size_t src_len)
{
AddL1CacheData(addr,DataBufferSP (new DataBufferHeap(DataBufferHeap(src, src_len))));
}
void
MemoryCache::AddL1CacheData(lldb::addr_t addr, const DataBufferSP &data_buffer_sp)
{
Mutex::Locker locker (m_mutex);
m_L1_cache[addr] = data_buffer_sp;
}
void
@ -57,29 +73,44 @@ MemoryCache::Flush (addr_t addr, size_t size)
return;
Mutex::Locker locker (m_mutex);
if (m_cache.empty())
return;
const uint32_t cache_line_byte_size = m_cache_line_byte_size;
const addr_t end_addr = (addr + size - 1);
const addr_t first_cache_line_addr = addr - (addr % cache_line_byte_size);
const addr_t last_cache_line_addr = end_addr - (end_addr % cache_line_byte_size);
// Watch for overflow where size will cause us to go off the end of the
// 64 bit address space
uint32_t num_cache_lines;
if (last_cache_line_addr >= first_cache_line_addr)
num_cache_lines = ((last_cache_line_addr - first_cache_line_addr)/cache_line_byte_size) + 1;
else
num_cache_lines = (UINT64_MAX - first_cache_line_addr + 1)/cache_line_byte_size;
uint32_t cache_idx = 0;
for (addr_t curr_addr = first_cache_line_addr;
cache_idx < num_cache_lines;
curr_addr += cache_line_byte_size, ++cache_idx)
// Erase any blocks from the L1 cache that intersect with the flush range
if (!m_L1_cache.empty())
{
BlockMap::iterator pos = m_cache.find (curr_addr);
if (pos != m_cache.end())
m_cache.erase(pos);
AddrRange flush_range(addr, size);
BlockMap::iterator pos = m_L1_cache.lower_bound(addr);
while (pos != m_L1_cache.end())
{
AddrRange chunk_range(pos->first, pos->second->GetByteSize());
if (!chunk_range.DoesIntersect(flush_range))
break;
pos = m_L1_cache.erase(pos);
}
}
if (!m_L2_cache.empty())
{
const uint32_t cache_line_byte_size = m_L2_cache_line_byte_size;
const addr_t end_addr = (addr + size - 1);
const addr_t first_cache_line_addr = addr - (addr % cache_line_byte_size);
const addr_t last_cache_line_addr = end_addr - (end_addr % cache_line_byte_size);
// Watch for overflow where size will cause us to go off the end of the
// 64 bit address space
uint32_t num_cache_lines;
if (last_cache_line_addr >= first_cache_line_addr)
num_cache_lines = ((last_cache_line_addr - first_cache_line_addr)/cache_line_byte_size) + 1;
else
num_cache_lines = (UINT64_MAX - first_cache_line_addr + 1)/cache_line_byte_size;
uint32_t cache_idx = 0;
for (addr_t curr_addr = first_cache_line_addr;
cache_idx < num_cache_lines;
curr_addr += cache_line_byte_size, ++cache_idx)
{
BlockMap::iterator pos = m_L2_cache.find (curr_addr);
if (pos != m_L2_cache.end())
m_L2_cache.erase(pos);
}
}
}
@ -122,6 +153,39 @@ MemoryCache::Read (addr_t addr,
{
size_t bytes_left = dst_len;
// Check the L1 cache for a range that contain the entire memory read.
// If we find a range in the L1 cache that does, we use it. Else we fall
// back to reading memory in m_L2_cache_line_byte_size byte sized chunks.
// The L1 cache contains chunks of memory that are not required to be
// m_L2_cache_line_byte_size bytes in size, so we don't try anything
// tricky when reading from them (no partial reads from the L1 cache).
Mutex::Locker locker(m_mutex);
if (!m_L1_cache.empty())
{
AddrRange read_range(addr, dst_len);
BlockMap::iterator pos = m_L1_cache.lower_bound(addr);
if (pos != m_L1_cache.end())
{
AddrRange chunk_range(pos->first, pos->second->GetByteSize());
bool match = chunk_range.Contains(read_range);
if (!match && pos != m_L1_cache.begin())
{
--pos;
chunk_range.SetRangeBase(pos->first);
chunk_range.SetByteSize(pos->second->GetByteSize());
match = chunk_range.Contains(read_range);
}
if (match)
{
memcpy(dst, pos->second->GetBytes() + addr - chunk_range.GetRangeBase(), dst_len);
return dst_len;
}
}
}
// If this memory read request is larger than the cache line size, then
// we (1) try to read as much of it at once as possible, and (2) don't
// add the data to the memory cache. We don't want to split a big read
@ -129,19 +193,22 @@ MemoryCache::Read (addr_t addr,
// request, it is unlikely that the caller function will ask for the next
// 4 bytes after the large memory read - so there's little benefit to saving
// it in the cache.
if (dst && dst_len > m_cache_line_byte_size)
if (dst && dst_len > m_L2_cache_line_byte_size)
{
return m_process.ReadMemoryFromInferior (addr, dst, dst_len, error);
size_t bytes_read = m_process.ReadMemoryFromInferior (addr, dst, dst_len, error);
// Add this non block sized range to the L1 cache if we actually read anything
if (bytes_read > 0)
AddL1CacheData(addr, dst, bytes_read);
return bytes_read;
}
if (dst && bytes_left > 0)
{
const uint32_t cache_line_byte_size = m_cache_line_byte_size;
const uint32_t cache_line_byte_size = m_L2_cache_line_byte_size;
uint8_t *dst_buf = (uint8_t *)dst;
addr_t curr_addr = addr - (addr % cache_line_byte_size);
addr_t cache_offset = addr - curr_addr;
Mutex::Locker locker (m_mutex);
while (bytes_left > 0)
{
if (m_invalid_ranges.FindEntryThatContains(curr_addr))
@ -150,8 +217,8 @@ MemoryCache::Read (addr_t addr,
return dst_len - bytes_left;
}
BlockMap::const_iterator pos = m_cache.find (curr_addr);
BlockMap::const_iterator end = m_cache.end ();
BlockMap::const_iterator pos = m_L2_cache.find (curr_addr);
BlockMap::const_iterator end = m_L2_cache.end ();
if (pos != end)
{
@ -208,7 +275,7 @@ MemoryCache::Read (addr_t addr,
if (process_bytes_read != cache_line_byte_size)
data_buffer_heap_ap->SetByteSize (process_bytes_read);
m_cache[curr_addr] = DataBufferSP (data_buffer_heap_ap.release());
m_L2_cache[curr_addr] = DataBufferSP (data_buffer_heap_ap.release());
// We have read data and put it into the cache, continue through the
// loop again to get the data out of the cache...
}

View File

@ -55,6 +55,7 @@
26203D1D1641EFB200A662F7 /* com.apple.debugserver.internal.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.debugserver.internal.plist; sourceTree = "<group>"; };
26242C390DDBD33C0054A4CC /* debugserver-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "debugserver-entitlements.plist"; sourceTree = "<group>"; };
264D5D571293835600ED4C01 /* DNBArch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DNBArch.cpp; sourceTree = "<group>"; };
264F679A1B2F9EB200140093 /* JSONGenerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSONGenerator.h; sourceTree = "<group>"; };
26593A060D4931CC001C9FE3 /* ChangeLog */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ChangeLog; sourceTree = "<group>"; };
2660D9CC1192280900958FBD /* StringExtractor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StringExtractor.cpp; path = ../../source/Utility/StringExtractor.cpp; sourceTree = SOURCE_ROOT; };
2660D9CD1192280900958FBD /* StringExtractor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StringExtractor.h; path = ../../source/Utility/StringExtractor.h; sourceTree = SOURCE_ROOT; };
@ -232,6 +233,7 @@
26C637E20C71334A0024798E /* DNBRegisterInfo.cpp */,
260E7332114BFFE600D1DFB3 /* DNBThreadResumeActions.h */,
260E7331114BFFE600D1DFB3 /* DNBThreadResumeActions.cpp */,
264F679A1B2F9EB200140093 /* JSONGenerator.h */,
AF67AC000D34604D0022D128 /* PseudoTerminal.h */,
AF67ABFF0D34604D0022D128 /* PseudoTerminal.cpp */,
26C637FD0C71334A0024798E /* PThreadCondition.h */,

View File

@ -0,0 +1,490 @@
//===-- JSONGenerator.h ----------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef __JSONGenerator_h_
#define __JSONGenerator_h_
// C Includes
// C++ Includes
#include <iomanip>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
//----------------------------------------------------------------------
/// @class JSONGenerator JSONGenerator.h
/// @brief A class which can construct structured data for the sole purpose
/// of printing it in JSON format.
///
/// A stripped down version of lldb's StructuredData objects which are much
/// general purpose. This variant is intended only for assembling information
/// and printing it as a JSON string.
//----------------------------------------------------------------------
class JSONGenerator
{
public:
class Object;
class Array;
class Integer;
class Float;
class Boolean;
class String;
class Dictionary;
class Generic;
typedef std::shared_ptr<Object> ObjectSP;
typedef std::shared_ptr<Array> ArraySP;
typedef std::shared_ptr<Integer> IntegerSP;
typedef std::shared_ptr<Float> FloatSP;
typedef std::shared_ptr<Boolean> BooleanSP;
typedef std::shared_ptr<String> StringSP;
typedef std::shared_ptr<Dictionary> DictionarySP;
typedef std::shared_ptr<Generic> GenericSP;
enum class Type
{
eTypeInvalid = -1,
eTypeNull = 0,
eTypeGeneric,
eTypeArray,
eTypeInteger,
eTypeFloat,
eTypeBoolean,
eTypeString,
eTypeDictionary
};
class Object :
public std::enable_shared_from_this<Object>
{
public:
Object (Type t = Type::eTypeInvalid) :
m_type (t)
{
}
virtual ~Object ()
{
}
virtual bool
IsValid() const
{
return true;
}
virtual void
Clear ()
{
m_type = Type::eTypeInvalid;
}
Type
GetType () const
{
return m_type;
}
void
SetType (Type t)
{
m_type = t;
}
Array *
GetAsArray ()
{
if (m_type == Type::eTypeArray)
return (Array *)this;
return NULL;
}
Dictionary *
GetAsDictionary ()
{
if (m_type == Type::eTypeDictionary)
return (Dictionary *)this;
return NULL;
}
Integer *
GetAsInteger ()
{
if (m_type == Type::eTypeInteger)
return (Integer *)this;
return NULL;
}
Float *
GetAsFloat ()
{
if (m_type == Type::eTypeFloat)
return (Float *)this;
return NULL;
}
Boolean *
GetAsBoolean ()
{
if (m_type == Type::eTypeBoolean)
return (Boolean *)this;
return NULL;
}
String *
GetAsString ()
{
if (m_type == Type::eTypeString)
return (String *)this;
return NULL;
}
Generic *
GetAsGeneric()
{
if (m_type == Type::eTypeGeneric)
return (Generic *)this;
return NULL;
}
virtual void
Dump (std::ostream &s) const = 0;
private:
Type m_type;
};
class Array : public Object
{
public:
Array () :
Object (Type::eTypeArray)
{
}
virtual
~Array()
{
}
void
AddItem(ObjectSP item)
{
m_items.push_back(item);
}
void Dump(std::ostream &s) const override
{
s << "[";
const size_t arrsize = m_items.size();
for (size_t i = 0; i < arrsize; ++i)
{
m_items[i]->Dump(s);
if (i + 1 < arrsize)
s << ",";
}
s << "]";
}
protected:
typedef std::vector<ObjectSP> collection;
collection m_items;
};
class Integer : public Object
{
public:
Integer (uint64_t value = 0) :
Object (Type::eTypeInteger),
m_value (value)
{
}
virtual ~Integer()
{
}
void
SetValue (uint64_t value)
{
m_value = value;
}
void Dump(std::ostream &s) const override
{
s << m_value;
}
protected:
uint64_t m_value;
};
class Float : public Object
{
public:
Float (double d = 0.0) :
Object (Type::eTypeFloat),
m_value (d)
{
}
virtual ~Float()
{
}
void
SetValue (double value)
{
m_value = value;
}
void Dump(std::ostream &s) const override
{
s << m_value;
}
protected:
double m_value;
};
class Boolean : public Object
{
public:
Boolean (bool b = false) :
Object (Type::eTypeBoolean),
m_value (b)
{
}
virtual ~Boolean()
{
}
void
SetValue (bool value)
{
m_value = value;
}
void Dump(std::ostream &s) const override
{
if (m_value == true)
s << "true";
else
s << "false";
}
protected:
bool m_value;
};
class String : public Object
{
public:
String () :
Object (Type::eTypeString),
m_value ()
{
}
String (const std::string &s) :
Object (Type::eTypeString),
m_value (s)
{
}
String (const std::string &&s) :
Object (Type::eTypeString),
m_value (s)
{
}
void
SetValue (const std::string &string)
{
m_value = string;
}
void Dump(std::ostream &s) const override
{
std::string quoted;
const size_t strsize = m_value.size();
for (size_t i = 0; i < strsize ; ++i)
{
char ch = m_value[i];
if (ch == '"')
quoted.push_back ('\\');
quoted.push_back (ch);
}
s << '"' << quoted.c_str() << '"';
}
protected:
std::string m_value;
};
class Dictionary : public Object
{
public:
Dictionary () :
Object (Type::eTypeDictionary),
m_dict ()
{
}
virtual ~Dictionary()
{
}
void
AddItem (std::string key, ObjectSP value)
{
m_dict.push_back(Pair(key, value));
}
void
AddIntegerItem (std::string key, uint64_t value)
{
AddItem (key, ObjectSP (new Integer(value)));
}
void
AddFloatItem (std::string key, double value)
{
AddItem (key, ObjectSP (new Float(value)));
}
void
AddStringItem (std::string key, std::string value)
{
AddItem (key, ObjectSP (new String(std::move(value))));
}
void
AddBytesAsHexASCIIString (std::string key, const uint8_t *src, size_t src_len)
{
if (src && src_len)
{
std::ostringstream strm;
for (size_t i = 0; i < src_len; i++)
strm << std::setfill('0') << std::hex << std::right << std::setw(2) << ((uint32_t)(src[i]));
AddItem (key, ObjectSP (new String(std::move(strm.str()))));
}
else
{
AddItem (key, ObjectSP (new String()));
}
}
void
AddBooleanItem (std::string key, bool value)
{
AddItem (key, ObjectSP (new Boolean(value)));
}
void Dump(std::ostream &s) const override
{
bool have_printed_one_elem = false;
s << "{";
for (collection::const_iterator iter = m_dict.begin(); iter != m_dict.end(); ++iter)
{
if (have_printed_one_elem == false)
{
have_printed_one_elem = true;
}
else
{
s << ",";
}
s << "\"" << iter->first.c_str() << "\":";
iter->second->Dump(s);
}
s << "}";
}
protected:
// Keep the dictionary as a vector so the dictionary doesn't reorder itself when you dump it
// We aren't accessing keys by name, so this won't affect performance
typedef std::pair<std::string, ObjectSP> Pair;
typedef std::vector<Pair> collection;
collection m_dict;
};
class Null : public Object
{
public:
Null () :
Object (Type::eTypeNull)
{
}
virtual ~Null()
{
}
bool
IsValid() const override
{
return false;
}
void Dump(std::ostream &s) const override
{
s << "null";
}
protected:
};
class Generic : public Object
{
public:
explicit Generic(void *object = nullptr)
: Object(Type::eTypeGeneric)
, m_object(object)
{
}
void
SetValue(void *value)
{
m_object = value;
}
void *
GetValue() const
{
return m_object;
}
bool
IsValid() const override
{
return m_object != nullptr;
}
void Dump(std::ostream &s) const override;
private:
void *m_object;
};
}; // class JSONGenerator
#endif // __JSONGenerator_h_

View File

@ -29,6 +29,7 @@
#include "DNBDataRef.h"
#include "DNBLog.h"
#include "DNBThreadResumeActions.h"
#include "JSONGenerator.h"
#include "RNBContext.h"
#include "RNBServices.h"
#include "RNBSocket.h"
@ -195,6 +196,7 @@ RNBRemote::CreatePacketTable ()
t.push_back (Packet (query_process_info, &RNBRemote::HandlePacket_qProcessInfo, NULL, "qProcessInfo", "Replies with multiple 'key:value;' tuples appended to each other."));
// t.push_back (Packet (query_symbol_lookup, &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "qSymbol", "Notify that host debugger is ready to do symbol lookups"));
t.push_back (Packet (json_query_thread_extended_info, &RNBRemote::HandlePacket_jThreadExtendedInfo, NULL, "jThreadExtendedInfo", "Replies with JSON data of thread extended information."));
t.push_back (Packet (json_query_threads_info, &RNBRemote::HandlePacket_jThreadsInfo , NULL, "jThreadsInfo", "Replies with JSON data with information about all threads."));
t.push_back (Packet (start_noack_mode, &RNBRemote::HandlePacket_QStartNoAckMode , NULL, "QStartNoAckMode", "Request that " DEBUGSERVER_PROGRAM_NAME " stop acking remote protocol packets"));
t.push_back (Packet (prefix_reg_packets_with_tid, &RNBRemote::HandlePacket_QThreadSuffixSupported , NULL, "QThreadSuffixSupported", "Check if thread specific packets (register packets 'g', 'G', 'p', and 'P') support having the thread ID appended to the end of the command"));
t.push_back (Packet (set_logging_mode, &RNBRemote::HandlePacket_QSetLogging , NULL, "QSetLogging:", "Check if register packets ('g', 'G', 'p', and 'P' support having the thread ID prefix"));
@ -2452,6 +2454,52 @@ gdb_regnum_with_fixed_width_hex_register_value (std::ostream& ostrm,
}
}
struct StackMemory
{
uint8_t bytes[2*sizeof(nub_addr_t)];
nub_size_t length;
};
typedef std::map<nub_addr_t, StackMemory> StackMemoryMap;
static void
ReadStackMemory (nub_process_t pid, nub_thread_t tid, StackMemoryMap &stack_mmap)
{
DNBRegisterValue reg_value;
if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC, GENERIC_REGNUM_FP, &reg_value))
{
uint32_t frame_count = 0;
uint64_t fp = 0;
if (reg_value.info.size == 4)
fp = reg_value.value.uint32;
else
fp = reg_value.value.uint64;
while (fp != 0)
{
// Make sure we never recurse more than 256 times so we don't recurse too far or
// store up too much memory in the expedited cache
if (++frame_count > 256)
break;
const nub_size_t read_size = reg_value.info.size*2;
StackMemory stack_memory;
stack_memory.length = read_size;
if (DNBProcessMemoryRead(pid, fp, read_size, stack_memory.bytes) != read_size)
break;
// Make sure we don't try to put the same stack memory in more than once
if (stack_mmap.find(fp) != stack_mmap.end())
break;
// Put the entry into the cache
stack_mmap[fp] = stack_memory;
// Dereference the frame pointer to get to the previous frame pointer
if (reg_value.info.size == 4)
fp = ((uint32_t *)stack_memory.bytes)[0];
else
fp = ((uint64_t *)stack_memory.bytes)[0];
}
}
}
rnb_err_t
RNBRemote::SendStopReplyPacketForThread (nub_thread_t tid)
{
@ -2578,11 +2626,26 @@ RNBRemote::SendStopReplyPacketForThread (nub_thread_t tid)
}
else if (tid_stop_info.details.exception.type)
{
ostrm << "metype:" << std::hex << tid_stop_info.details.exception.type << ";";
ostrm << "mecount:" << std::hex << tid_stop_info.details.exception.data_count << ";";
ostrm << "metype:" << std::hex << tid_stop_info.details.exception.type << ';';
ostrm << "mecount:" << std::hex << tid_stop_info.details.exception.data_count << ';';
for (int i = 0; i < tid_stop_info.details.exception.data_count; ++i)
ostrm << "medata:" << std::hex << tid_stop_info.details.exception.data[i] << ";";
ostrm << "medata:" << std::hex << tid_stop_info.details.exception.data[i] << ';';
}
// Add expedited stack memory so stack backtracing doesn't need to read anything from the
// frame pointer chain.
StackMemoryMap stack_mmap;
ReadStackMemory (pid, tid, stack_mmap);
if (!stack_mmap.empty())
{
for (const auto &stack_memory : stack_mmap)
{
ostrm << "memory:" << HEXBASE << stack_memory.first << '=';
append_hex_value (ostrm, stack_memory.second.bytes, stack_memory.second.length, false);
ostrm << ';';
}
}
return SendPacket (ostrm.str ());
}
return SendPacket("E51");
@ -4761,12 +4824,137 @@ get_integer_value_for_key_name_from_json (const char *key, const char *json_stri
}
rnb_err_t
RNBRemote::HandlePacket_jThreadsInfo (const char *p)
{
JSONGenerator::Array threads_array;
std::ostringstream json;
std::ostringstream reply_strm;
// If we haven't run the process yet, return an error.
if (m_ctx.HasValidProcessID())
{
nub_process_t pid = m_ctx.ProcessID();
nub_size_t numthreads = DNBProcessGetNumThreads (pid);
for (nub_size_t i = 0; i < numthreads; ++i)
{
nub_thread_t tid = DNBProcessGetThreadAtIndex (pid, i);
struct DNBThreadStopInfo tid_stop_info;
JSONGenerator::DictionarySP thread_dict_sp(new JSONGenerator::Dictionary());
thread_dict_sp->AddIntegerItem("tid", tid);
std::string reason_value("none");
if (DNBThreadGetStopReason (pid, tid, &tid_stop_info))
{
switch (tid_stop_info.reason)
{
case eStopTypeInvalid:
break;
case eStopTypeSignal:
if (tid_stop_info.details.signal.signo != 0)
reason_value = "signal";
break;
case eStopTypeException:
if (tid_stop_info.details.exception.type != 0)
reason_value = "exception";
break;
case eStopTypeExec:
reason_value = "exec";
break;
}
if (tid_stop_info.reason == eStopTypeSignal)
{
thread_dict_sp->AddIntegerItem("signal", tid_stop_info.details.signal.signo);
}
else if (tid_stop_info.reason == eStopTypeException && tid_stop_info.details.exception.type != 0)
{
thread_dict_sp->AddIntegerItem("metype", tid_stop_info.details.exception.type);
JSONGenerator::ArraySP medata_array_sp(new JSONGenerator::Array());
for (nub_size_t i=0; i<tid_stop_info.details.exception.data_count; ++i)
{
medata_array_sp->AddItem(JSONGenerator::IntegerSP(new JSONGenerator::Integer(tid_stop_info.details.exception.data[i])));
}
thread_dict_sp->AddItem("medata", medata_array_sp);
}
}
thread_dict_sp->AddStringItem("reason", reason_value);
const char *thread_name = DNBThreadGetName (pid, tid);
if (thread_name && thread_name[0])
thread_dict_sp->AddStringItem("name", thread_name);
thread_identifier_info_data_t thread_ident_info;
if (DNBThreadGetIdentifierInfo (pid, tid, &thread_ident_info))
{
if (thread_ident_info.dispatch_qaddr != 0)
thread_dict_sp->AddIntegerItem("qaddr", thread_ident_info.dispatch_qaddr);
}
DNBRegisterValue reg_value;
if (g_reg_entries != NULL)
{
JSONGenerator::DictionarySP registers_dict_sp(new JSONGenerator::Dictionary());
for (uint32_t reg = 0; reg < g_num_reg_entries; reg++)
{
// Expedite all registers in the first register set that aren't
// contained in other registers
if (g_reg_entries[reg].nub_info.set == 1 &&
g_reg_entries[reg].nub_info.value_regs == NULL)
{
if (!DNBThreadGetRegisterValueByID (pid, tid, g_reg_entries[reg].nub_info.set, g_reg_entries[reg].nub_info.reg, &reg_value))
continue;
std::ostringstream reg_num;
reg_num << std::dec << g_reg_entries[reg].gdb_regnum;
// Encode native byte ordered bytes as hex ascii
registers_dict_sp->AddBytesAsHexASCIIString(reg_num.str(), reg_value.value.v_uint8, g_reg_entries[reg].nub_info.size);
}
}
thread_dict_sp->AddItem("registers", registers_dict_sp);
}
// Add expedited stack memory so stack backtracing doesn't need to read anything from the
// frame pointer chain.
StackMemoryMap stack_mmap;
ReadStackMemory (pid, tid, stack_mmap);
if (!stack_mmap.empty())
{
JSONGenerator::ArraySP memory_array_sp(new JSONGenerator::Array());
for (const auto &stack_memory : stack_mmap)
{
JSONGenerator::DictionarySP stack_memory_sp(new JSONGenerator::Dictionary());
stack_memory_sp->AddIntegerItem("address", stack_memory.first);
stack_memory_sp->AddBytesAsHexASCIIString("bytes", stack_memory.second.bytes, stack_memory.second.length);
memory_array_sp->AddItem(stack_memory_sp);
}
thread_dict_sp->AddItem("memory", memory_array_sp);
}
threads_array.AddItem(thread_dict_sp);
}
std::ostringstream strm;
threads_array.Dump (strm);
std::string binary_packet = binary_encode_string (strm.str());
if (!binary_packet.empty())
return SendPacket (binary_packet.c_str());
}
return SendPacket ("E85");
}
rnb_err_t
RNBRemote::HandlePacket_jThreadExtendedInfo (const char *p)
{
nub_process_t pid;
std::ostringstream json;
std::ostringstream reply_strm;
// If we haven't run the process yet, return an error.
if (!m_ctx.HasValidProcessID())
{
@ -4996,8 +5184,7 @@ RNBRemote::HandlePacket_jThreadExtendedInfo (const char *p)
json << "}";
std::string json_quoted = binary_encode_string (json.str());
reply_strm << json_quoted;
return SendPacket (reply_strm.str());
return SendPacket (json_quoted);
}
}
return SendPacket ("OK");

View File

@ -103,6 +103,7 @@ public:
query_gdb_server_version, // 'qGDBServerVersion'
query_process_info, // 'qProcessInfo'
json_query_thread_extended_info,// 'jThreadExtendedInfo'
json_query_threads_info, // 'jThreadsInfo'
pass_signals_to_inferior, // 'QPassSignals'
start_noack_mode, // 'QStartNoAckMode'
prefix_reg_packets_with_tid, // 'QPrefixRegisterPacketsWithThreadID
@ -190,6 +191,7 @@ public:
rnb_err_t HandlePacket_qSyncThreadStateSupported (const char *p);
rnb_err_t HandlePacket_qThreadInfo (const char *p);
rnb_err_t HandlePacket_jThreadExtendedInfo (const char *p);
rnb_err_t HandlePacket_jThreadsInfo (const char *p);
rnb_err_t HandlePacket_qThreadExtraInfo (const char *p);
rnb_err_t HandlePacket_qThreadStopInfo (const char *p);
rnb_err_t HandlePacket_qHostInfo (const char *p);