254 changes: 249 additions & 5 deletions lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@
# define DEBUGSERVER_BASENAME "lldb-server"
#endif

#if defined (HAVE_LIBCOMPRESSION)
#include <compression.h>
#endif

#if defined (HAVE_LIBZ)
#include <zlib.h>
#endif

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_gdb_remote;
Expand Down Expand Up @@ -158,6 +166,7 @@ GDBRemoteCommunication::GDBRemoteCommunication(const char *comm_name,
m_private_is_running (false),
m_history (512),
m_send_acks (true),
m_compression_type (CompressionType::None),
m_listen_url ()
{
}
Expand Down Expand Up @@ -546,6 +555,226 @@ GDBRemoteCommunication::WaitForPacketWithTimeoutMicroSecondsNoLock (StringExtrac
return PacketResult::ErrorReplyFailed;
}

bool
GDBRemoteCommunication::DecompressPacket ()
{
Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PACKETS));

if (!CompressionIsEnabled())
return true;

size_t pkt_size = m_bytes.size();
if (pkt_size < 6)
return true;
if (m_bytes[0] != '$' && m_bytes[0] != '%')
return true;
if (m_bytes[1] != 'C' && m_bytes[1] != 'N')
return true;
if (m_bytes[pkt_size - 3] != '#')
return true;
if (!::isxdigit (m_bytes[pkt_size - 2]) || !::isxdigit (m_bytes[pkt_size - 1]))
return true;

size_t content_length = pkt_size - 5; // not counting '$', 'C' | 'N', '#', & the two hex checksum chars
size_t content_start = 2; // The first character of the compressed/not-compressed text of the packet
size_t hash_mark_idx = pkt_size - 3; // The '#' character marking the end of the packet
size_t checksum_idx = pkt_size - 2; // The first character of the two hex checksum characters

// Compressed packets ("$C") start with a base10 number which is the size of the uncompressed payload,
// then a : and then the compressed data. e.g. $C1024:<binary>#00
// Update content_start and content_length to only include the <binary> part of the packet.

uint64_t decompressed_bufsize = ULONG_MAX;
if (m_bytes[1] == 'C')
{
size_t i = content_start;
while (i < hash_mark_idx && isdigit(m_bytes[i]))
i++;
if (i < hash_mark_idx && m_bytes[i] == ':')
{
i++;
content_start = i;
content_length = hash_mark_idx - content_start;
std::string bufsize_str (m_bytes.data() + 2, i - 2 - 1);
errno = 0;
decompressed_bufsize = ::strtoul (bufsize_str.c_str(), NULL, 10);
if (errno != 0 || decompressed_bufsize == ULONG_MAX)
{
m_bytes.erase (0, pkt_size);
return false;
}
}
}

if (GetSendAcks ())
{
char packet_checksum_cstr[3];
packet_checksum_cstr[0] = m_bytes[checksum_idx];
packet_checksum_cstr[1] = m_bytes[checksum_idx + 1];
packet_checksum_cstr[2] = '\0';
long packet_checksum = strtol (packet_checksum_cstr, NULL, 16);

long actual_checksum = CalculcateChecksum (m_bytes.data() + 1, hash_mark_idx - 1);
bool success = packet_checksum == actual_checksum;
if (!success)
{
if (log)
log->Printf ("error: checksum mismatch: %.*s expected 0x%2.2x, got 0x%2.2x",
(int)(pkt_size),
m_bytes.c_str(),
(uint8_t)packet_checksum,
(uint8_t)actual_checksum);
}
// Send the ack or nack if needed
if (!success)
{
SendNack();
m_bytes.erase (0, pkt_size);
return false;
}
else
{
SendAck();
}
}

if (m_bytes[1] == 'N')
{
// This packet was not compressed -- delete the 'N' character at the
// start and the packet may be processed as-is.
m_bytes.erase(1, 1);
return true;
}

// Reverse the gdb-remote binary escaping that was done to the compressed text to
// guard characters like '$', '#', '}', etc.
std::vector<uint8_t> unescaped_content;
unescaped_content.reserve (content_length);
size_t i = content_start;
while (i < hash_mark_idx)
{
if (m_bytes[i] == '}')
{
i++;
unescaped_content.push_back (m_bytes[i] ^ 0x20);
}
else
{
unescaped_content.push_back (m_bytes[i]);
}
i++;
}

uint8_t *decompressed_buffer = nullptr;
size_t decompressed_bytes = 0;

if (decompressed_bufsize != ULONG_MAX)
{
decompressed_buffer = (uint8_t *) malloc (decompressed_bufsize + 1);
if (decompressed_buffer == nullptr)
{
m_bytes.erase (0, pkt_size);
return false;
}

}

#if defined (HAVE_LIBCOMPRESSION)
// libcompression is weak linked so check that compression_decode_buffer() is available
if (compression_decode_buffer != NULL &&
(m_compression_type == CompressionType::ZlibDeflate
|| m_compression_type == CompressionType::LZFSE
|| m_compression_type == CompressionType::LZ4))
{
compression_algorithm compression_type;
if (m_compression_type == CompressionType::ZlibDeflate)
compression_type = COMPRESSION_ZLIB;
else if (m_compression_type == CompressionType::LZFSE)
compression_type = COMPRESSION_LZFSE;
else if (m_compression_type == CompressionType::LZ4)
compression_type = COMPRESSION_LZ4_RAW;
else if (m_compression_type == CompressionType::LZMA)
compression_type = COMPRESSION_LZMA;


// If we have the expected size of the decompressed payload, we can allocate
// the right-sized buffer and do it. If we don't have that information, we'll
// need to try decoding into a big buffer and if the buffer wasn't big enough,
// increase it and try again.

if (decompressed_bufsize != ULONG_MAX && decompressed_buffer != nullptr)
{
decompressed_bytes = compression_decode_buffer (decompressed_buffer, decompressed_bufsize + 10 ,
(uint8_t*) unescaped_content.data(),
unescaped_content.size(),
NULL,
compression_type);
}
}
#endif

#if defined (HAVE_LIBZ)
if (decompressed_bytes == 0
&& decompressed_bufsize != ULONG_MAX
&& decompressed_buffer != nullptr
&& m_compression_type == CompressionType::ZlibDeflate)
{
z_stream stream;
memset (&stream, 0, sizeof (z_stream));
stream.next_in = (Bytef *) unescaped_content.data();
stream.avail_in = (uInt) unescaped_content.size();
stream.total_in = 0;
stream.next_out = (Bytef *) decompressed_buffer;
stream.avail_out = decompressed_bufsize;
stream.total_out = 0;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;

if (inflateInit2 (&stream, -15) == Z_OK)
{
int status = inflate (&stream, Z_NO_FLUSH);
inflateEnd (&stream);
if (status == Z_STREAM_END)
{
decompressed_bytes = stream.total_out;
}
}
}
#endif

if (decompressed_bytes == 0 || decompressed_buffer == nullptr)
{
if (decompressed_buffer)
free (decompressed_buffer);
m_bytes.erase (0, pkt_size);
return false;
}

std::string new_packet;
new_packet.reserve (decompressed_bytes + 6);
new_packet.push_back (m_bytes[0]);
new_packet.append ((const char *) decompressed_buffer, decompressed_bytes);
new_packet.push_back ('#');
if (GetSendAcks ())
{
uint8_t decompressed_checksum = CalculcateChecksum ((const char *) decompressed_buffer, decompressed_bytes);
char decompressed_checksum_str[3];
snprintf (decompressed_checksum_str, 3, "%02x", decompressed_checksum);
new_packet.append (decompressed_checksum_str);
}
else
{
new_packet.push_back ('0');
new_packet.push_back ('0');
}

m_bytes = new_packet;

free (decompressed_buffer);
return true;
}

GDBRemoteCommunication::PacketType
GDBRemoteCommunication::CheckForPacket (const uint8_t *src, size_t src_len, StringExtractorGDBRemote &packet)
{
Expand Down Expand Up @@ -581,6 +810,17 @@ GDBRemoteCommunication::CheckForPacket (const uint8_t *src, size_t src_len, Stri
size_t total_length = 0;
size_t checksum_idx = std::string::npos;

// Size of packet before it is decompressed, for logging purposes
size_t original_packet_size = m_bytes.size();
if (CompressionIsEnabled())
{
if (DecompressPacket() == false)
{
packet.Clear();
return GDBRemoteCommunication::PacketType::Standard;
}
}

switch (m_bytes[0])
{
case '+': // Look for ack
Expand Down Expand Up @@ -664,12 +904,10 @@ GDBRemoteCommunication::CheckForPacket (const uint8_t *src, size_t src_len, Stri
assert (content_length <= m_bytes.size());
assert (total_length <= m_bytes.size());
assert (content_length <= total_length);
const size_t content_end = content_start + content_length;
size_t content_end = content_start + content_length;

bool success = true;
std::string &packet_str = packet.GetStringRef();


if (log)
{
// If logging was just enabled and we have history, then dump out what
Expand All @@ -693,7 +931,10 @@ GDBRemoteCommunication::CheckForPacket (const uint8_t *src, size_t src_len, Stri
{
StreamString strm;
// Packet header...
strm.Printf("<%4" PRIu64 "> read packet: %c", (uint64_t)total_length, m_bytes[0]);
if (CompressionIsEnabled())
strm.Printf("<%4" PRIu64 ":%" PRIu64 "> read packet: %c", (uint64_t) original_packet_size, (uint64_t)total_length, m_bytes[0]);
else
strm.Printf("<%4" PRIu64 "> read packet: %c", (uint64_t)total_length, m_bytes[0]);
for (size_t i=content_start; i<content_end; ++i)
{
// Remove binary escaped bytes when displaying the packet...
Expand All @@ -716,7 +957,10 @@ GDBRemoteCommunication::CheckForPacket (const uint8_t *src, size_t src_len, Stri
}
else
{
log->Printf("<%4" PRIu64 "> read packet: %.*s", (uint64_t)total_length, (int)(total_length), m_bytes.c_str());
if (CompressionIsEnabled())
log->Printf("<%4" PRIu64 ":%" PRIu64 "> read packet: %.*s", (uint64_t) original_packet_size, (uint64_t)total_length, (int)(total_length), m_bytes.c_str());
else
log->Printf("<%4" PRIu64 "> read packet: %.*s", (uint64_t)total_length, (int)(total_length), m_bytes.c_str());
}
}

Expand Down
26 changes: 26 additions & 0 deletions lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ typedef enum
eWatchpointReadWrite
} GDBStoppointType;

enum class CompressionType
{
None = 0, // no compression
ZlibDeflate, // zlib's deflate compression scheme, requires zlib or Apple's libcompression
LZFSE, // an Apple compression scheme, requires Apple's libcompression
LZ4, // lz compression - called "lz4 raw" in libcompression terms, compat with https://code.google.com/p/lz4/
LZMA, // Lempel–Ziv–Markov chain algorithm
};

class ProcessGDBRemote;

class GDBRemoteCommunication : public Communication
Expand Down Expand Up @@ -296,6 +305,22 @@ class GDBRemoteCommunication : public Communication
bool
WaitForNotRunningPrivate (const TimeValue *timeout_ptr);

bool
CompressionIsEnabled ()
{
return m_compression_type != CompressionType::None;
}

// If compression is enabled, decompress the packet in m_bytes and update
// m_bytes with the uncompressed version.
// Returns 'true' packet was decompressed and m_bytes is the now-decompressed text.
// Returns 'false' if unable to decompress or if the checksum was invalid.
//
// NB: Once the packet has been decompressed, checksum cannot be computed based
// on m_bytes. The checksum was for the compressed packet.
bool
DecompressPacket ();

//------------------------------------------------------------------
// Classes that inherit from GDBRemoteCommunication can see and modify these
//------------------------------------------------------------------
Expand All @@ -315,6 +340,7 @@ class GDBRemoteCommunication : public Communication
// false if this class represents a debug session for
// a single process

CompressionType m_compression_type;

Error
StartListenThread (const char *hostname = "127.0.0.1", uint16_t port = 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
#include "ProcessGDBRemoteLog.h"
#include "lldb/Host/Config.h"

#if defined (HAVE_LIBCOMPRESSION)
#include <compression.h>
#endif

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_gdb_remote;
Expand Down Expand Up @@ -423,6 +427,59 @@ GDBRemoteCommunicationClient::GetRemoteQSupported ()
if (::strstr (response_cstr, "qXfer:features:read+"))
m_supports_qXfer_features_read = eLazyBoolYes;


// Look for a list of compressions in the features list e.g.
// qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib-deflate,lzma
const char *features_list = ::strstr (response_cstr, "qXfer:features:");
if (features_list)
{
const char *compressions = ::strstr (features_list, "SupportedCompressions=");
if (compressions)
{
std::vector<std::string> supported_compressions;
compressions += sizeof ("SupportedCompressions=") - 1;
const char *end_of_compressions = strchr (compressions, ';');
if (end_of_compressions == NULL)
{
end_of_compressions = strchr (compressions, '\0');
}
const char *current_compression = compressions;
while (current_compression < end_of_compressions)
{
const char *next_compression_name = strchr (current_compression, ',');
const char *end_of_this_word = next_compression_name;
if (next_compression_name == NULL || end_of_compressions < next_compression_name)
{
end_of_this_word = end_of_compressions;
}

if (end_of_this_word)
{
if (end_of_this_word == current_compression)
{
current_compression++;
}
else
{
std::string this_compression (current_compression, end_of_this_word - current_compression);
supported_compressions.push_back (this_compression);
current_compression = end_of_this_word + 1;
}
}
else
{
supported_compressions.push_back (current_compression);
current_compression = end_of_compressions;
}
}

if (supported_compressions.size() > 0)
{
MaybeEnableCompression (supported_compressions);
}
}
}

if (::strstr (response_cstr, "qEcho"))
m_supports_qEcho = eLazyBoolYes;
else
Expand Down Expand Up @@ -1629,6 +1686,105 @@ GDBRemoteCommunicationClient::GetGDBServerVersion()
return m_qGDBServerVersion_is_valid == eLazyBoolYes;
}

void
GDBRemoteCommunicationClient::MaybeEnableCompression (std::vector<std::string> supported_compressions)
{
CompressionType avail_type = CompressionType::None;
std::string avail_name;

#if defined (HAVE_LIBCOMPRESSION)
// libcompression is weak linked so test if compression_decode_buffer() is available
if (compression_decode_buffer != NULL && avail_type == CompressionType::None)
{
for (auto compression : supported_compressions)
{
if (compression == "lzfse")
{
avail_type = CompressionType::LZFSE;
avail_name = compression;
break;
}
}
}
#endif

#if defined (HAVE_LIBCOMPRESSION)
// libcompression is weak linked so test if compression_decode_buffer() is available
if (compression_decode_buffer != NULL && avail_type == CompressionType::None)
{
for (auto compression : supported_compressions)
{
if (compression == "zlib-deflate")
{
avail_type = CompressionType::ZlibDeflate;
avail_name = compression;
break;
}
}
}
#endif

#if defined (HAVE_LIBZ)
if (avail_type == CompressionType::None)
{
for (auto compression : supported_compressions)
{
if (compression == "zlib-deflate")
{
avail_type = CompressionType::ZlibDeflate;
avail_name = compression;
break;
}
}
}
#endif

#if defined (HAVE_LIBCOMPRESSION)
// libcompression is weak linked so test if compression_decode_buffer() is available
if (compression_decode_buffer != NULL && avail_type == CompressionType::None)
{
for (auto compression : supported_compressions)
{
if (compression == "lz4")
{
avail_type = CompressionType::LZ4;
avail_name = compression;
break;
}
}
}
#endif

#if defined (HAVE_LIBCOMPRESSION)
// libcompression is weak linked so test if compression_decode_buffer() is available
if (compression_decode_buffer != NULL && avail_type == CompressionType::None)
{
for (auto compression : supported_compressions)
{
if (compression == "lzma")
{
avail_type = CompressionType::LZMA;
avail_name = compression;
break;
}
}
}
#endif

if (avail_type != CompressionType::None)
{
StringExtractorGDBRemote response;
std::string packet = "QEnableCompression:type:" + avail_name + ";";
if (SendPacketAndWaitForResponse (packet.c_str(), response, false) != PacketResult::Success)
return;

if (response.IsOKResponse())
{
m_compression_type = avail_type;
}
}
}

const char *
GDBRemoteCommunicationClient::GetGDBServerProgramName()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,11 @@ class GDBRemoteCommunicationClient : public GDBRemoteCommunication
bool
GetGDBServerVersion();

// Given the list of compression types that the remote debug stub can support,
// possibly enable compression if we find an encoding we can handle.
void
MaybeEnableCompression (std::vector<std::string> supported_compressions);

//------------------------------------------------------------------
// Classes that inherit from GDBRemoteCommunicationClient can see and modify these
//------------------------------------------------------------------
Expand Down Expand Up @@ -643,6 +648,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteCommunication
uint32_t m_gdb_server_version; // from reply to qGDBServerVersion, zero if qGDBServerVersion is not supported
uint32_t m_default_packet_timeout;
uint64_t m_max_packet_size; // as returned by qSupported


bool
DecodeProcessInfoResponse (StringExtractorGDBRemote &response,
Expand Down
63 changes: 55 additions & 8 deletions lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,14 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LLDB_COMPRESSION_CFLAGS = "";
"LLDB_COMPRESSION_CFLAGS[sdk=macosx10.11]" = "-DHAVE_LIBCOMPRESSION=1";
LLDB_COMPRESSION_LDFLAGS = "";
"LLDB_COMPRESSION_LDFLAGS[sdk=macosx10.11]" = "-weak-lcompression";
LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1";
LLDB_ZLIB_LDFLAGS = "-lz";
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "";
STRIP_INSTALLED_PRODUCT = NO;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_BUILDER = "$(USER)";
Expand Down Expand Up @@ -557,7 +564,14 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LLDB_COMPRESSION_CFLAGS = "";
"LLDB_COMPRESSION_CFLAGS[sdk=macosx10.11]" = "-DHAVE_LIBCOMPRESSION=1";
LLDB_COMPRESSION_LDFLAGS = "";
"LLDB_COMPRESSION_LDFLAGS[sdk=macosx10.11]" = "-weak-lcompression";
LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1";
LLDB_ZLIB_LDFLAGS = "-lz";
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "";
STRIPFLAGS = "-x";
STRIP_STYLE = debugging;
VERSIONING_SYSTEM = "apple-generic";
Expand Down Expand Up @@ -591,6 +605,13 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LLDB_COMPRESSION_CFLAGS = "";
"LLDB_COMPRESSION_CFLAGS[sdk=macosx10.11]" = "-DHAVE_LIBCOMPRESSION=1";
LLDB_COMPRESSION_LDFLAGS = "";
"LLDB_COMPRESSION_LDFLAGS[sdk=macosx10.11]" = "-weak-lcompression";
LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1";
LLDB_ZLIB_LDFLAGS = "-lz";
OTHER_CFLAGS = "";
STRIPFLAGS = "-x";
STRIP_STYLE = debugging;
VERSIONING_SYSTEM = "apple-generic";
Expand Down Expand Up @@ -627,8 +648,8 @@
"LLDB_ENERGY_LFLAGS[sdk=macosx10.10internal]" = "-weak-lpmenergy -weak-lpmsample";
MACOSX_DEPLOYMENT_TARGET = 10.9;
OTHER_CFLAGS = (
"-Wparentheses",
"$(LLDB_ENERGY_CFLAGS)",
"$(LLDB_COMPRESSION_CFLAGS)",
"$(LLDB_ZLIB_CFLAGS)",
);
"OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = (
"-Wparentheses",
Expand All @@ -637,6 +658,7 @@
"-DOS_OBJECT_USE_OBJC=0",
);
"OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)";
OTHER_LDFLAGS = "";
"OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = (
"-framework",
SpringBoardServices,
Expand All @@ -645,13 +667,17 @@
"-framework",
Foundation,
"-llockdown",
"$(LLDB_COMPRESSION_LDFLAGS)",
"$(LLDB_ZLIB_LDFLAGS)",
);
"OTHER_LDFLAGS[sdk=macosx*]" = (
"-sectcreate",
__TEXT,
__info_plist,
"$(PROJECT_DIR)/resources/lldb-debugserver-Info.plist",
"$(LLDB_ENERGY_LFLAGS)",
"$(LLDB_COMPRESSION_LDFLAGS)",
"$(LLDB_ZLIB_LDFLAGS)",
);
OTHER_MIGFLAGS = "-I$(DERIVED_FILE_DIR)";
PRODUCT_NAME = debugserver;
Expand Down Expand Up @@ -693,8 +719,8 @@
"LLDB_ENERGY_LFLAGS[sdk=macosx10.10internal]" = "-weak-lpmenergy -weak-lpmsample";
MACOSX_DEPLOYMENT_TARGET = 10.9;
OTHER_CFLAGS = (
"-Wparentheses",
"$(LLDB_ENERGY_CFLAGS)",
"$(LLDB_COMPRESSION_CFLAGS)",
"$(LLDB_ZLIB_CFLAGS)",
);
"OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = (
"-Wparentheses",
Expand All @@ -712,13 +738,17 @@
"-framework",
Foundation,
"-llockdown",
"$(LLDB_COMPRESSION_LDFLAGS)",
"$(LLDB_ZLIB_LDFLAGS)",
);
"OTHER_LDFLAGS[sdk=macosx*]" = (
"-sectcreate",
__TEXT,
__info_plist,
"$(PROJECT_DIR)/resources/lldb-debugserver-Info.plist",
"$(LLDB_ENERGY_LFLAGS)",
"$(LLDB_ZLIB_LDFLAGS)",
"$(LLDB_COMPRESSION_LDFLAGS)",
);
OTHER_MIGFLAGS = "-I$(DERIVED_FILE_DIR)";
PRODUCT_NAME = debugserver;
Expand Down Expand Up @@ -760,8 +790,8 @@
"LLDB_ENERGY_LFLAGS[sdk=macosx10.10internal]" = "-weak-lpmenergy -weak-lpmsample";
MACOSX_DEPLOYMENT_TARGET = 10.9;
OTHER_CFLAGS = (
"-Wparentheses",
"$(LLDB_ENERGY_CFLAGS)",
"$(LLDB_COMPRESSION_CFLAGS)",
"$(LLDB_ZLIB_CFLAGS)",
);
"OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = (
"-Wparentheses",
Expand All @@ -770,6 +800,7 @@
"-DOS_OBJECT_USE_OBJC=0",
);
"OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)";
OTHER_LDFLAGS = "";
"OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = (
"-framework",
SpringBoardServices,
Expand All @@ -778,13 +809,17 @@
"-llockdown",
"-framework",
Foundation,
"$(LLDB_COMPRESSION_LDFLAGS)",
"$(LLDB_ZLIB_LDFLAGS)",
);
"OTHER_LDFLAGS[sdk=macosx*]" = (
"-sectcreate",
__TEXT,
__info_plist,
"$(PROJECT_DIR)/resources/lldb-debugserver-Info.plist",
"$(LLDB_ENERGY_LFLAGS)",
"$(LLDB_COMPRESSION_LDFLAGS)",
"$(LLDB_ZLIB_LDFLAGS)",
);
OTHER_MIGFLAGS = "-I$(DERIVED_FILE_DIR)";
PRODUCT_NAME = debugserver;
Expand Down Expand Up @@ -826,7 +861,14 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LLDB_COMPRESSION_CFLAGS = "";
"LLDB_COMPRESSION_CFLAGS[sdk=macosx10.11]" = "-DHAVE_LIBCOMPRESSION=1";
LLDB_COMPRESSION_LDFLAGS = "";
"LLDB_COMPRESSION_LDFLAGS[sdk=macosx10.11]" = "-weak-lcompression";
LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1";
LLDB_ZLIB_LDFLAGS = "-lz";
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "";
STRIP_INSTALLED_PRODUCT = NO;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_BUILDER = "$(USER)";
Expand Down Expand Up @@ -863,8 +905,8 @@
"LLDB_ENERGY_LFLAGS[sdk=macosx10.10internal]" = "-weak-lpmenergy -weak-lpmsample";
MACOSX_DEPLOYMENT_TARGET = 10.9;
OTHER_CFLAGS = (
"-Wparentheses",
"$(LLDB_ENERGY_CFLAGS)",
"$(LLDB_COMPRESSION_CFLAGS)",
"$(LLDB_ZLIB_CFLAGS)",
);
"OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = (
"-Wparentheses",
Expand All @@ -873,6 +915,7 @@
"-DOS_OBJECT_USE_OBJC=0",
);
"OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)";
OTHER_LDFLAGS = "";
"OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = (
"-framework",
SpringBoardServices,
Expand All @@ -881,13 +924,17 @@
"-llockdown",
"-framework",
Foundation,
"$(LLDB_COMPRESSION_LDFLAGS)",
"$(LLDB_ZLIB_LDFLAGS)",
);
"OTHER_LDFLAGS[sdk=macosx*]" = (
"-sectcreate",
__TEXT,
__info_plist,
"$(PROJECT_DIR)/resources/lldb-debugserver-Info.plist",
"$(LLDB_ENERGY_LFLAGS)",
"$(LLDB_COMPRESSION_LDFLAGS)",
"$(LLDB_ZLIB_LDFLAGS)",
);
OTHER_MIGFLAGS = "-I$(DERIVED_FILE_DIR)";
PRODUCT_NAME = debugserver;
Expand Down
285 changes: 280 additions & 5 deletions lldb/tools/debugserver/source/RNBRemote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@
#include "Utility/StringExtractor.h"
#include "MacOSX/Genealogy.h"

#if defined (HAVE_LIBCOMPRESSION)
#include <compression.h>
#endif

#if defined (HAVE_LIBZ)
#include <zlib.h>
#endif

#include <iomanip>
#include <sstream>
#include <unordered_set>
Expand Down Expand Up @@ -83,7 +91,10 @@ RNBRemote::RNBRemote () :
m_extended_mode(false),
m_noack_mode(false),
m_thread_suffix_supported (false),
m_list_threads_in_stop_reply (false)
m_list_threads_in_stop_reply (false),
m_compression_minsize (384),
m_enable_compression_next_send_packet (false),
m_compression_mode (compression_types::none)
{
DNBLogThreadedIf (LOG_RNB_REMOTE, "%s", __PRETTY_FUNCTION__);
CreatePacketTable ();
Expand Down Expand Up @@ -207,6 +218,7 @@ RNBRemote::CreatePacketTable ()
t.push_back (Packet (memory_region_info, &RNBRemote::HandlePacket_MemoryRegionInfo, NULL, "qMemoryRegionInfo", "Return size and attributes of a memory region that contains the given address"));
t.push_back (Packet (get_profile_data, &RNBRemote::HandlePacket_GetProfileData, NULL, "qGetProfileData", "Return profiling data of the current target."));
t.push_back (Packet (set_enable_profiling, &RNBRemote::HandlePacket_SetEnableAsyncProfiling, NULL, "QSetEnableAsyncProfiling", "Enable or disable the profiling of current target."));
t.push_back (Packet (enable_compression, &RNBRemote::HandlePacket_QEnableCompression, NULL, "QEnableCompression:", "Enable compression for the remainder of the connection"));
t.push_back (Packet (watchpoint_support_info, &RNBRemote::HandlePacket_WatchpointSupportInfo, NULL, "qWatchpointSupportInfo", "Return the number of supported hardware watchpoints"));
t.push_back (Packet (set_process_event, &RNBRemote::HandlePacket_QSetProcessEvent, NULL, "QSetProcessEvent:", "Set a process event, to be passed to the process, can be set before the process is started, or after."));
t.push_back (Packet (set_detach_on_error, &RNBRemote::HandlePacket_QSetDetachOnError, NULL, "QSetDetachOnError:", "Set whether debugserver will detach (1) or kill (0) from the process it is controlling if it loses connection to lldb."));
Expand Down Expand Up @@ -310,11 +322,146 @@ RNBRemote::SendAsyncProfileDataPacket (char *buf, nub_size_t buf_size)
return SendPacket(packet);
}

// Given a std::string packet contents to send, possibly encode/compress it.
// If compression is enabled, the returned std::string will be in one of two
// forms:
//
// N<original packet contents uncompressed>
// C<size of original decompressed packet>:<packet compressed with the requested compression scheme>
//
// If compression is not requested, the original packet contents are returned

std::string
RNBRemote::CompressString (const std::string &orig)
{
std::string compressed;
compression_types compression_type = GetCompressionType();
if (compression_type != compression_types::none)
{
bool compress_this_packet = false;

if (orig.size() > m_compression_minsize)
{
compress_this_packet = true;
}

if (compress_this_packet)
{
const size_t encoded_data_buf_size = orig.size() + 128;
std::vector<uint8_t> encoded_data (encoded_data_buf_size);
size_t compressed_size = 0;

#if defined (HAVE_LIBCOMPRESSION)
if (compression_decode_buffer && compression_type == compression_types::lz4)
{
compressed_size = compression_encode_buffer (encoded_data.data(),
encoded_data_buf_size,
(uint8_t*) orig.c_str(),
orig.size(),
nullptr,
COMPRESSION_LZ4_RAW);
}
if (compression_decode_buffer && compression_type == compression_types::zlib_deflate)
{
compressed_size = compression_encode_buffer (encoded_data.data(),
encoded_data_buf_size,
(uint8_t*) orig.c_str(),
orig.size(),
nullptr,
COMPRESSION_ZLIB);
}
if (compression_decode_buffer && compression_type == compression_types::lzma)
{
compressed_size = compression_encode_buffer (encoded_data.data(),
encoded_data_buf_size,
(uint8_t*) orig.c_str(),
orig.size(),
nullptr,
COMPRESSION_LZMA);
}
if (compression_decode_buffer && compression_type == compression_types::lzfse)
{
compressed_size = compression_encode_buffer (encoded_data.data(),
encoded_data_buf_size,
(uint8_t*) orig.c_str(),
orig.size(),
nullptr,
COMPRESSION_LZFSE);
}
#endif

#if defined (HAVE_LIBZ)
if (compressed_size == 0 && compression_type == compression_types::zlib_deflate)
{
z_stream stream;
memset (&stream, 0, sizeof (z_stream));
stream.next_in = (Bytef *) orig.c_str();
stream.avail_in = (uInt) orig.size();
stream.next_out = (Bytef *) encoded_data.data();
stream.avail_out = (uInt) encoded_data_buf_size;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
deflateInit2 (&stream, 5, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
int compress_status = deflate (&stream, Z_FINISH);
deflateEnd (&stream);
if (compress_status == Z_STREAM_END && stream.total_out > 0)
{
compressed_size = stream.total_out;
}
}
#endif

if (compressed_size > 0)
{
compressed.clear ();
compressed.reserve (compressed_size);
compressed = "C";
char numbuf[16];
snprintf (numbuf, sizeof (numbuf), "%zu:", orig.size());
numbuf[sizeof (numbuf) - 1] = '\0';
compressed.append (numbuf);

for (size_t i = 0; i < compressed_size; i++)
{
uint8_t byte = encoded_data[i];
if (byte == '#' || byte == '$' || byte == '}' || byte == '*' || byte == '\0')
{
compressed.push_back (0x7d);
compressed.push_back (byte ^ 0x20);
}
else
{
compressed.push_back (byte);
}
}
}
else
{
compressed = "N" + orig;
}
}
else
{
compressed = "N" + orig;
}
}
else
{
compressed = orig;
}

return compressed;
}

rnb_err_t
RNBRemote::SendPacket (const std::string &s)
{
DNBLogThreadedIf (LOG_RNB_MAX, "%8d RNBRemote::%s (%s) called", (uint32_t)m_comm.Timer().ElapsedMicroSeconds(true), __FUNCTION__, s.c_str());
std::string sendpacket = "$" + s + "#";

std::string s_compressed = CompressString (s);

std::string sendpacket = "$" + s_compressed + "#";
int cksum = 0;
char hexbuf[5];

Expand All @@ -324,8 +471,8 @@ RNBRemote::SendPacket (const std::string &s)
}
else
{
for (int i = 0; i != s.size(); ++i)
cksum += s[i];
for (int i = 0; i != s_compressed.size(); ++i)
cksum += s_compressed[i];
snprintf (hexbuf, sizeof hexbuf, "%02x", cksum & 0xff);
sendpacket += hexbuf;
}
Expand Down Expand Up @@ -3096,8 +3243,39 @@ rnb_err_t
RNBRemote::HandlePacket_qSupported (const char *p)
{
uint32_t max_packet_size = 128 * 1024; // 128KBytes is a reasonable max packet size--debugger can always use less
char buf[64];
char buf[256];
snprintf (buf, sizeof(buf), "qXfer:features:read+;PacketSize=%x;qEcho+", max_packet_size);

// By default, don't enable compression. It's only worth doing when we are working
// with a low speed communication channel.
bool enable_compression = false;

// Enable compression when debugserver is running on a watchOS device where communication may be over Bluetooth.
#if defined (TARGET_OS_WATCH) && TARGET_OS_WATCH == 1
enable_compression = true;
#endif

#if defined (HAVE_LIBCOMPRESSION)
// libcompression is weak linked so test if compression_decode_buffer() is available
if (enable_compression && compression_decode_buffer != NULL)
{
strcat (buf, ";SupportedCompressions=lzfse,zlib-deflate,lz4,lzma;DefaultCompressionMinSize=");
char numbuf[16];
snprintf (numbuf, sizeof (numbuf), "%zu", m_compression_minsize);
numbuf[sizeof (numbuf) - 1] = '\0';
strcat (buf, numbuf);
}
#elif defined (HAVE_LIBZ)
if (enable_compression)
{
strcat (buf, ";SupportedCompressions=zlib-deflate;DefaultCompressionMinSize=");
char numbuf[16];
snprintf (numbuf, sizeof (numbuf), "%zu", m_compression_minsize);
numbuf[sizeof (numbuf) - 1] = '\0';
strcat (buf, numbuf);
}
#endif

return SendPacket (buf);
}

Expand Down Expand Up @@ -3765,6 +3943,72 @@ RNBRemote::HandlePacket_SetEnableAsyncProfiling (const char *p)
return SendPacket ("OK");
}

// QEnableCompression:type:<COMPRESSION-TYPE>;minsize:<MINIMUM PACKET SIZE TO COMPRESS>;
//
// type: must be a type previously reported by the qXfer:features: SupportedCompressions list
//
// minsize: is optional; by default the qXfer:features: DefaultCompressionMinSize value is used
// debugserver may have a better idea of what a good minimum packet size to compress is than lldb.

rnb_err_t
RNBRemote::HandlePacket_QEnableCompression (const char *p)
{
p += sizeof ("QEnableCompression:") - 1;

size_t new_compression_minsize = m_compression_minsize;
const char *new_compression_minsize_str = strstr (p, "minsize:");
if (new_compression_minsize_str)
{
new_compression_minsize_str += strlen ("minsize:");
errno = 0;
new_compression_minsize = strtoul (new_compression_minsize_str, NULL, 10);
if (errno != 0 || new_compression_minsize == ULONG_MAX)
{
new_compression_minsize = m_compression_minsize;
}
}

#if defined (HAVE_LIBCOMPRESSION)
if (compression_decode_buffer != NULL)
{
if (strstr (p, "type:zlib-deflate;") != nullptr)
{
EnableCompressionNextSendPacket (compression_types::zlib_deflate);
m_compression_minsize = new_compression_minsize;
return SendPacket ("OK");
}
else if (strstr (p, "type:lz4;") != nullptr)
{
EnableCompressionNextSendPacket (compression_types::lz4);
m_compression_minsize = new_compression_minsize;
return SendPacket ("OK");
}
else if (strstr (p, "type:lzma;") != nullptr)
{
EnableCompressionNextSendPacket (compression_types::lzma);
m_compression_minsize = new_compression_minsize;
return SendPacket ("OK");
}
else if (strstr (p, "type:lzfse;") != nullptr)
{
EnableCompressionNextSendPacket (compression_types::lzfse);
m_compression_minsize = new_compression_minsize;
return SendPacket ("OK");
}
}
#endif

#if defined (HAVE_LIBZ)
if (strstr (p, "type:zlib-deflate;") != nullptr)
{
EnableCompressionNextSendPacket (compression_types::zlib_deflate);
m_compression_minsize = new_compression_minsize;
return SendPacket ("OK");
}
#endif

return SendPacket ("E88");
}

rnb_err_t
RNBRemote::HandlePacket_qSpeedTest (const char *p)
Expand Down Expand Up @@ -4450,6 +4694,14 @@ RNBRemote::HandlePacket_qXfer (const char *command)
}
}
}
else
{
SendPacket ("E85");
}
}
else
{
SendPacket ("E86");
}
}
return SendPacket ("E82");
Expand Down Expand Up @@ -4941,3 +5193,26 @@ RNBRemote::HandlePacket_qProcessInfo (const char *p)
return SendPacket (rep.str());
}

void
RNBRemote::EnableCompressionNextSendPacket (compression_types type)
{
m_compression_mode = type;
m_enable_compression_next_send_packet = true;
}

compression_types
RNBRemote::GetCompressionType ()
{
// The first packet we send back to the debugger after a QEnableCompression request
// should be uncompressed -- so we can indicate whether the compression was enabled
// or not via OK / Enn returns. After that, all packets sent will be using the
// compression protocol.

if (m_enable_compression_next_send_packet)
{
// One time, we send back "None" as our compression type
m_enable_compression_next_send_packet = false;
return compression_types::none;
}
return m_compression_mode;
}
18 changes: 17 additions & 1 deletion lldb/tools/debugserver/source/RNBRemote.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//===-- RNBRemote.h ---------------------------------------------*- C++ -*-===//

//
// The LLVM Compiler Infrastructure
//
Expand Down Expand Up @@ -30,6 +30,8 @@ class PThreadEvents;

enum event_loop_mode { debug_nub, gdb_remote_protocol, done };

enum class compression_types { zlib_deflate, lz4, lzma, lzfse, none };

class RNBRemote
{
public:
Expand Down Expand Up @@ -120,6 +122,7 @@ class RNBRemote
memory_region_info, // 'qMemoryRegionInfo:'
get_profile_data, // 'qGetProfileData'
set_enable_profiling, // 'QSetEnableAsyncProfiling'
enable_compression, // 'QEnableCompression:'
watchpoint_support_info, // 'qWatchpointSupportInfo:'
allocate_memory, // '_M'
deallocate_memory, // '_m'
Expand Down Expand Up @@ -235,6 +238,7 @@ class RNBRemote
rnb_err_t HandlePacket_MemoryRegionInfo (const char *p);
rnb_err_t HandlePacket_GetProfileData(const char *p);
rnb_err_t HandlePacket_SetEnableAsyncProfiling(const char *p);
rnb_err_t HandlePacket_QEnableCompression(const char *p);
rnb_err_t HandlePacket_WatchpointSupportInfo (const char *p);
rnb_err_t HandlePacket_qSpeedTest (const char *p);
rnb_err_t HandlePacket_qXfer (const char *p);
Expand Down Expand Up @@ -309,13 +313,20 @@ class RNBRemote

rnb_err_t GetPacket (std::string &packet_data, RNBRemote::Packet& packet_info, bool wait);
rnb_err_t SendPacket (const std::string &);
std::string CompressString (const std::string &);

void CreatePacketTable ();
rnb_err_t GetPacketPayload (std::string &);

nub_thread_t
ExtractThreadIDFromThreadSuffix (const char *p);

void
EnableCompressionNextSendPacket (compression_types);

compression_types
GetCompressionType ();

RNBContext m_ctx; // process context
RNBSocket m_comm; // communication port
std::string m_arch;
Expand All @@ -336,6 +347,11 @@ class RNBRemote
// "$g;thread:TTTT" instead of "$g"
// "$GVVVVVVVVVVVVVV;thread:TTTT;#00 instead of "$GVVVVVVVVVVVVVV"
bool m_list_threads_in_stop_reply;

size_t m_compression_minsize; // only packets larger than this size will be compressed
bool m_enable_compression_next_send_packet;

compression_types m_compression_mode;
};

/* We translate the /usr/include/mach/exception_types.h exception types
Expand Down