diff options
Diffstat (limited to 'lldb')
9 files changed, 948 insertions, 19 deletions
diff --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt index b471b245f85..d8f22fe604a 100644 --- a/lldb/docs/lldb-gdb-remote.txt +++ b/lldb/docs/lldb-gdb-remote.txt @@ -1384,3 +1384,66 @@ for this region. // // on the wire. //---------------------------------------------------------------------- + +//---------------------------------------------------------------------- +// "QEnableCompression" +// +// BRIEF +// This packet enables compression of the packets that the debug stub sends to lldb. +// If the debug stub can support compression, it indictes this in the reply of the +// "qSupported" packet. e.g. +// LLDB SENDS: qSupported:xmlRegisters=i386,arm,mips +// STUB REPLIES: qXfer:features:read+;SupportedCompressions=lzfse,zlib-deflate,lz4,lzma;DefaultCompressionMinSize=384 +// +// If lldb knows how to use any of these compression algorithms, it can ask that this +// compression mode be enabled. It may optionally change the minimum packet size +// where compression is used. Typically small packets do not benefit from compression, +// as well as compression headers -- compression is most beneficial with larger packets. +// +// QEnableCompression:type:zlib-deflate; +// or +// QEnableCompression:type:zlib-deflate;minsize:512; +// +// The debug stub should reply with an uncompressed "OK" packet to indicate that the +// request was accepted. All further packets the stub sends will use this compression. +// +// Packets are compressed as the last step before they are sent from the stub, and +// decompressed as the first step after they are received. The packet format in compressed +// mode becomes one of two: +// +// $N<uncompressed payload>#00 +// +// $C<size of uncompressed payload in base10>:<compressed payload>#00 +// +// Where "#00" is the actual checksum value if noack mode is not enabled. The checksum +// value is for the "N<uncompressed payload>" or +// "C<size of uncompressed payload in base10>:<compressed payload>" bytes in the packet. +// +// The size of the uncompressed payload in base10 is provided because it will simplify +// decompression if the final buffer size needed is known ahead of time. +// +// Compression on low-latency connections is unlikely to be an improvement. Particularly +// when the debug stub and lldb are running on the same host. It should only be used +// for slow connections, and likely only for larger packets. +// +// Example compression algorithsm that may be used include +// +// zlib-deflate +// The raw DEFLATE format as described in IETF RFC 1951. With the ZLIB library, you +// can compress to this format with an initialization like +// deflateInit2 (&stream, 5, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) +// and you can decompress with an initialization like +// inflateInit2 (&stream, -15) +// +// lz4 +// https://en.wikipedia.org/wiki/LZ4_(compression_algorithm) +// https://github.com/Cyan4973/lz4 +// The libcompression APIs on darwin systems call this COMPRESSION_LZ4_RAW. +// +// lzfse +// An Apple proprietary compression algorithm implemented in libcompression. +// +// lzma +// libcompression implements "LZMA level 6", the default compression for the +// open source LZMA implementation. +//---------------------------------------------------------------------- diff --git a/lldb/lldb.xcodeproj/project.pbxproj b/lldb/lldb.xcodeproj/project.pbxproj index 7e839152d1e..aa0d397e25c 100644 --- a/lldb/lldb.xcodeproj/project.pbxproj +++ b/lldb/lldb.xcodeproj/project.pbxproj @@ -6900,10 +6900,16 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_VALUE = 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_DISABLE_PYTHON = 0; "LLDB_DISABLE_PYTHON[sdk=iphoneos*]" = 1; LLDB_FRAMEWORK_INSTALL_DIR = /Applications/Xcode.app/Contents/SharedFrameworks; LLDB_TOOLS_INSTALL_DIR = /usr/bin; + LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1"; + LLDB_ZLIB_LDFLAGS = "-lz"; LLVM_BUILD_DIR = "$(SRCROOT)/llvm-build/$(LLVM_CONFIGURATION)"; LLVM_BUILD_DIR_ARCH = "$(CURRENT_ARCH)/"; LLVM_CONFIGURATION = "Release+Asserts"; @@ -6913,6 +6919,12 @@ OTHER_CFLAGS = ( "-flimit-debug-info", "-Wparentheses", + "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_COMPRESSION_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "$(LLDB_COMPRESSION_LDFLAGS)", + "$(LLDB_ZLIB_LDFLAGS)", ); SDKROOT = ""; STRIP_INSTALLED_PRODUCT = NO; @@ -6969,10 +6981,16 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_VALUE = 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_DISABLE_PYTHON = 0; "LLDB_DISABLE_PYTHON[sdk=iphoneos*]" = 1; LLDB_FRAMEWORK_INSTALL_DIR = /Applications/Xcode.app/Contents/SharedFrameworks; LLDB_TOOLS_INSTALL_DIR = /usr/bin; + LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1"; + LLDB_ZLIB_LDFLAGS = "-lz"; LLVM_BUILD_DIR = "$(SRCROOT)/llvm-build/$(LLVM_CONFIGURATION)"; LLVM_BUILD_DIR_ARCH = "$(CURRENT_ARCH)/"; LLVM_CONFIGURATION = "Release+Asserts"; @@ -6982,6 +7000,12 @@ OTHER_CFLAGS = ( "-flimit-debug-info", "-Wparentheses", + "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_COMPRESSION_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "$(LLDB_COMPRESSION_LDFLAGS)", + "$(LLDB_ZLIB_LDFLAGS)", ); SDKROOT = ""; STRIP_INSTALLED_PRODUCT = NO; @@ -7212,6 +7236,12 @@ "$(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)", "$(inherited)", ); + 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_CPLUSPLUSFLAGS = ( "-I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7", "-fno-rtti", @@ -7278,6 +7308,12 @@ "$(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)", "$(inherited)", ); + 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_CPLUSPLUSFLAGS = ( "-I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7", "-fno-rtti", @@ -7386,6 +7422,12 @@ ); GCC_INLINES_ARE_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = /usr/include/libxml2; + 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"; MACH_O_TYPE = staticlib; OTHER_CPLUSPLUSFLAGS = ( "-I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7", @@ -7425,6 +7467,12 @@ ); GCC_INLINES_ARE_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = /usr/include/libxml2; + 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"; MACH_O_TYPE = staticlib; OTHER_CPLUSPLUSFLAGS = ( "-I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7", @@ -7464,6 +7512,12 @@ ); GCC_INLINES_ARE_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = /usr/include/libxml2; + 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"; MACH_O_TYPE = staticlib; OTHER_CPLUSPLUSFLAGS = ( "-I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7", @@ -7541,12 +7595,18 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_VALUE = 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_DISABLE_PYTHON = 0; "LLDB_DISABLE_PYTHON[sdk=iphoneos*]" = 1; LLDB_FRAMEWORK_INSTALL_DIR = /Applications/Xcode.app/Contents/SharedFrameworks; "LLDB_FRAMEWORK_INSTALL_DIR[sdk=iphoneos*]" = /System/Library/PrivateFrameworks; LLDB_TOOLS_INSTALL_DIR = /Applications/Xcode.app/Contents/Developer/usr/bin; "LLDB_TOOLS_INSTALL_DIR[sdk=iphoneos*]" = /usr/local/bin; + LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1"; + LLDB_ZLIB_LDFLAGS = "-lz"; LLVM_BUILD_DIR = "$(OBJROOT)/llvm"; LLVM_BUILD_DIR_ARCH = "$(CURRENT_ARCH)/"; LLVM_CONFIGURATION = Release; @@ -7555,6 +7615,12 @@ OTHER_CFLAGS = ( "-flimit-debug-info", "-Wparentheses", + "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_COMPRESSION_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "$(LLDB_COMPRESSION_LDFLAGS)", + "$(LLDB_ZLIB_LDFLAGS)", ); SDKROOT = ""; STRIP_INSTALLED_PRODUCT = NO; @@ -7632,6 +7698,12 @@ "$(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)", "$(inherited)", ); + 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_CPLUSPLUSFLAGS = ( "-I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7", "-fno-rtti", @@ -8221,10 +8293,16 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VARIABLE = YES; + LLDB_COMPRESSION_CFLAGS = ""; + "LLDB_COMPRESSION_CFLAGS[sdk=macosx10.11internal]" = "-DHAVE_LIBCOMPRESSION=1"; + LLDB_COMPRESSION_LDFLAGS = ""; + "LLDB_COMPRESSION_LDFLAGS[sdk=macosx10.11]" = "-weak-lcompression"; LLDB_DISABLE_PYTHON = 0; "LLDB_DISABLE_PYTHON[sdk=iphoneos*]" = 1; LLDB_FRAMEWORK_INSTALL_DIR = /Applications/Xcode.app/Contents/SharedFrameworks; LLDB_TOOLS_INSTALL_DIR = /usr/bin; + LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1"; + LLDB_ZLIB_LDFLAGS = "-lz"; LLVM_BUILD_DIR = "$(SRCROOT)/llvm-build/$(LLVM_CONFIGURATION)"; LLVM_BUILD_DIR_ARCH = "$(CURRENT_ARCH)/"; LLVM_CONFIGURATION = "Debug+Asserts"; @@ -8234,6 +8312,12 @@ OTHER_CFLAGS = ( "-flimit-debug-info", "-Wparentheses", + "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_COMPRESSION_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "$(LLDB_COMPRESSION_LDFLAGS)", + "$(LLDB_ZLIB_LDFLAGS)", ); SDKROOT = ""; STRIP_INSTALLED_PRODUCT = NO; @@ -8325,6 +8409,12 @@ "$(LLVM_BUILD_DIR)/$(LLVM_BUILD_DIR_ARCH)", "$(inherited)", ); + 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_CPLUSPLUSFLAGS = ( "-I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7", "-fno-rtti", @@ -8390,6 +8480,12 @@ ); GCC_INLINES_ARE_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = /usr/include/libxml2; + 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"; MACH_O_TYPE = staticlib; OTHER_CPLUSPLUSFLAGS = ( "-I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7", diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp index da04f46c25a..1af3947a75f 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp @@ -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; @@ -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 () { } @@ -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) { @@ -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 @@ -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 @@ -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... @@ -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()); } } diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h index c13352781b3..7379bb3aa09 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h @@ -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 @@ -296,6 +305,22 @@ protected: 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 //------------------------------------------------------------------ @@ -315,6 +340,7 @@ protected: // 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); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp index 21a538ced17..d2a15b3152e 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -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; @@ -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 @@ -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() { diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h index 726bc577a1b..ba34a313090 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -564,6 +564,11 @@ protected: 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 //------------------------------------------------------------------ @@ -643,6 +648,7 @@ protected: 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, diff --git a/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj b/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj index 42565ef4cd0..f7cbc1338fb 100644 --- a/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj +++ b/lldb/tools/debugserver/debugserver.xcodeproj/project.pbxproj @@ -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)"; @@ -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"; @@ -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"; @@ -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", @@ -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, @@ -645,6 +667,8 @@ "-framework", Foundation, "-llockdown", + "$(LLDB_COMPRESSION_LDFLAGS)", + "$(LLDB_ZLIB_LDFLAGS)", ); "OTHER_LDFLAGS[sdk=macosx*]" = ( "-sectcreate", @@ -652,6 +676,8 @@ __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; @@ -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", @@ -712,6 +738,8 @@ "-framework", Foundation, "-llockdown", + "$(LLDB_COMPRESSION_LDFLAGS)", + "$(LLDB_ZLIB_LDFLAGS)", ); "OTHER_LDFLAGS[sdk=macosx*]" = ( "-sectcreate", @@ -719,6 +747,8 @@ __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; @@ -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", @@ -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, @@ -778,6 +809,8 @@ "-llockdown", "-framework", Foundation, + "$(LLDB_COMPRESSION_LDFLAGS)", + "$(LLDB_ZLIB_LDFLAGS)", ); "OTHER_LDFLAGS[sdk=macosx*]" = ( "-sectcreate", @@ -785,6 +818,8 @@ __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; @@ -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)"; @@ -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", @@ -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, @@ -881,6 +924,8 @@ "-llockdown", "-framework", Foundation, + "$(LLDB_COMPRESSION_LDFLAGS)", + "$(LLDB_ZLIB_LDFLAGS)", ); "OTHER_LDFLAGS[sdk=macosx*]" = ( "-sectcreate", @@ -888,6 +933,8 @@ __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; diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp index a82179dcb78..2385246a200 100644 --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -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> @@ -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 (); @@ -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.")); @@ -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]; @@ -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; } @@ -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); } @@ -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) @@ -4450,6 +4694,14 @@ RNBRemote::HandlePacket_qXfer (const char *command) } } } + else + { + SendPacket ("E85"); + } + } + else + { + SendPacket ("E86"); } } return SendPacket ("E82"); @@ -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; +} diff --git a/lldb/tools/debugserver/source/RNBRemote.h b/lldb/tools/debugserver/source/RNBRemote.h index c769a045cdd..1f4883ab9e0 100644 --- a/lldb/tools/debugserver/source/RNBRemote.h +++ b/lldb/tools/debugserver/source/RNBRemote.h @@ -1,4 +1,4 @@ -//===-- RNBRemote.h ---------------------------------------------*- C++ -*-===// + // // The LLVM Compiler Infrastructure // @@ -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: @@ -120,6 +122,7 @@ public: 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' @@ -235,6 +238,7 @@ public: 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); @@ -309,6 +313,7 @@ protected: 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 &); @@ -316,6 +321,12 @@ protected: 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; @@ -336,6 +347,11 @@ protected: // "$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 |