| //===-- GDBRemoteCommunication.cpp ------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| |
| #include "GDBRemoteCommunication.h" |
| |
| // C Includes |
| #include <limits.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| // C++ Includes |
| // Other libraries and framework includes |
| #include "lldb/Core/Log.h" |
| #include "lldb/Core/RegularExpression.h" |
| #include "lldb/Core/StreamFile.h" |
| #include "lldb/Core/StreamString.h" |
| #include "lldb/Host/ConnectionFileDescriptor.h" |
| #include "lldb/Host/FileSpec.h" |
| #include "lldb/Host/Host.h" |
| #include "lldb/Host/HostInfo.h" |
| #include "lldb/Host/Pipe.h" |
| #include "lldb/Host/Socket.h" |
| #include "lldb/Host/StringConvert.h" |
| #include "lldb/Host/ThreadLauncher.h" |
| #include "lldb/Host/TimeValue.h" |
| #include "lldb/Target/Process.h" |
| #include "llvm/ADT/SmallString.h" |
| |
| // Project includes |
| #include "ProcessGDBRemoteLog.h" |
| |
| #if defined(__APPLE__) |
| # define DEBUGSERVER_BASENAME "debugserver" |
| #else |
| # 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; |
| |
| GDBRemoteCommunication::History::History (uint32_t size) : |
| m_packets(), |
| m_curr_idx (0), |
| m_total_packet_count (0), |
| m_dumped_to_log (false) |
| { |
| m_packets.resize(size); |
| } |
| |
| GDBRemoteCommunication::History::~History () |
| { |
| } |
| |
| void |
| GDBRemoteCommunication::History::AddPacket (char packet_char, |
| PacketType type, |
| uint32_t bytes_transmitted) |
| { |
| const size_t size = m_packets.size(); |
| if (size > 0) |
| { |
| const uint32_t idx = GetNextIndex(); |
| m_packets[idx].packet.assign (1, packet_char); |
| m_packets[idx].type = type; |
| m_packets[idx].bytes_transmitted = bytes_transmitted; |
| m_packets[idx].packet_idx = m_total_packet_count; |
| m_packets[idx].tid = Host::GetCurrentThreadID(); |
| } |
| } |
| |
| void |
| GDBRemoteCommunication::History::AddPacket (const std::string &src, |
| uint32_t src_len, |
| PacketType type, |
| uint32_t bytes_transmitted) |
| { |
| const size_t size = m_packets.size(); |
| if (size > 0) |
| { |
| const uint32_t idx = GetNextIndex(); |
| m_packets[idx].packet.assign (src, 0, src_len); |
| m_packets[idx].type = type; |
| m_packets[idx].bytes_transmitted = bytes_transmitted; |
| m_packets[idx].packet_idx = m_total_packet_count; |
| m_packets[idx].tid = Host::GetCurrentThreadID(); |
| } |
| } |
| |
| void |
| GDBRemoteCommunication::History::Dump (Stream &strm) const |
| { |
| const uint32_t size = GetNumPacketsInHistory (); |
| const uint32_t first_idx = GetFirstSavedPacketIndex (); |
| const uint32_t stop_idx = m_curr_idx + size; |
| for (uint32_t i = first_idx; i < stop_idx; ++i) |
| { |
| const uint32_t idx = NormalizeIndex (i); |
| const Entry &entry = m_packets[idx]; |
| if (entry.type == ePacketTypeInvalid || entry.packet.empty()) |
| break; |
| strm.Printf ("history[%u] tid=0x%4.4" PRIx64 " <%4u> %s packet: %s\n", |
| entry.packet_idx, |
| entry.tid, |
| entry.bytes_transmitted, |
| (entry.type == ePacketTypeSend) ? "send" : "read", |
| entry.packet.c_str()); |
| } |
| } |
| |
| void |
| GDBRemoteCommunication::History::Dump (Log *log) const |
| { |
| if (log && !m_dumped_to_log) |
| { |
| m_dumped_to_log = true; |
| const uint32_t size = GetNumPacketsInHistory (); |
| const uint32_t first_idx = GetFirstSavedPacketIndex (); |
| const uint32_t stop_idx = m_curr_idx + size; |
| for (uint32_t i = first_idx; i < stop_idx; ++i) |
| { |
| const uint32_t idx = NormalizeIndex (i); |
| const Entry &entry = m_packets[idx]; |
| if (entry.type == ePacketTypeInvalid || entry.packet.empty()) |
| break; |
| log->Printf ("history[%u] tid=0x%4.4" PRIx64 " <%4u> %s packet: %s", |
| entry.packet_idx, |
| entry.tid, |
| entry.bytes_transmitted, |
| (entry.type == ePacketTypeSend) ? "send" : "read", |
| entry.packet.c_str()); |
| } |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // GDBRemoteCommunication constructor |
| //---------------------------------------------------------------------- |
| GDBRemoteCommunication::GDBRemoteCommunication(const char *comm_name, |
| const char *listener_name) : |
| Communication(comm_name), |
| #ifdef LLDB_CONFIGURATION_DEBUG |
| m_packet_timeout (1000), |
| #else |
| m_packet_timeout (1), |
| #endif |
| m_echo_number(0), |
| m_supports_qEcho (eLazyBoolCalculate), |
| m_sequence_mutex (Mutex::eMutexTypeRecursive), |
| m_public_is_running (false), |
| m_private_is_running (false), |
| m_history (512), |
| m_send_acks (true), |
| m_compression_type (CompressionType::None), |
| m_listen_url () |
| { |
| } |
| |
| //---------------------------------------------------------------------- |
| // Destructor |
| //---------------------------------------------------------------------- |
| GDBRemoteCommunication::~GDBRemoteCommunication() |
| { |
| if (IsConnected()) |
| { |
| Disconnect(); |
| } |
| |
| // Stop the communications read thread which is used to parse all |
| // incoming packets. This function will block until the read |
| // thread returns. |
| if (m_read_thread_enabled) |
| StopReadThread(); |
| } |
| |
| char |
| GDBRemoteCommunication::CalculcateChecksum (const char *payload, size_t payload_length) |
| { |
| int checksum = 0; |
| |
| for (size_t i = 0; i < payload_length; ++i) |
| checksum += payload[i]; |
| |
| return checksum & 255; |
| } |
| |
| size_t |
| GDBRemoteCommunication::SendAck () |
| { |
| Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PACKETS)); |
| ConnectionStatus status = eConnectionStatusSuccess; |
| char ch = '+'; |
| const size_t bytes_written = Write (&ch, 1, status, NULL); |
| if (log) |
| log->Printf ("<%4" PRIu64 "> send packet: %c", (uint64_t)bytes_written, ch); |
| m_history.AddPacket (ch, History::ePacketTypeSend, bytes_written); |
| return bytes_written; |
| } |
| |
| size_t |
| GDBRemoteCommunication::SendNack () |
| { |
| Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PACKETS)); |
| ConnectionStatus status = eConnectionStatusSuccess; |
| char ch = '-'; |
| const size_t bytes_written = Write (&ch, 1, status, NULL); |
| if (log) |
| log->Printf("<%4" PRIu64 "> send packet: %c", (uint64_t)bytes_written, ch); |
| m_history.AddPacket (ch, History::ePacketTypeSend, bytes_written); |
| return bytes_written; |
| } |
| |
| GDBRemoteCommunication::PacketResult |
| GDBRemoteCommunication::SendPacket (const char *payload, size_t payload_length) |
| { |
| Mutex::Locker locker(m_sequence_mutex); |
| return SendPacketNoLock (payload, payload_length); |
| } |
| |
| GDBRemoteCommunication::PacketResult |
| GDBRemoteCommunication::SendPacketNoLock (const char *payload, size_t payload_length) |
| { |
| if (IsConnected()) |
| { |
| StreamString packet(0, 4, eByteOrderBig); |
| |
| packet.PutChar('$'); |
| packet.Write (payload, payload_length); |
| packet.PutChar('#'); |
| packet.PutHex8(CalculcateChecksum (payload, payload_length)); |
| |
| Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PACKETS)); |
| ConnectionStatus status = eConnectionStatusSuccess; |
| const char *packet_data = packet.GetData(); |
| const size_t packet_length = packet.GetSize(); |
| size_t bytes_written = Write (packet_data, packet_length, status, NULL); |
| if (log) |
| { |
| size_t binary_start_offset = 0; |
| if (strncmp(packet_data, "$vFile:pwrite:", strlen("$vFile:pwrite:")) == 0) |
| { |
| const char *first_comma = strchr(packet_data, ','); |
| if (first_comma) |
| { |
| const char *second_comma = strchr(first_comma + 1, ','); |
| if (second_comma) |
| binary_start_offset = second_comma - packet_data + 1; |
| } |
| } |
| |
| // If logging was just enabled and we have history, then dump out what |
| // we have to the log so we get the historical context. The Dump() call that |
| // logs all of the packet will set a boolean so that we don't dump this more |
| // than once |
| if (!m_history.DidDumpToLog ()) |
| m_history.Dump (log); |
| |
| if (binary_start_offset) |
| { |
| StreamString strm; |
| // Print non binary data header |
| strm.Printf("<%4" PRIu64 "> send packet: %.*s", (uint64_t)bytes_written, (int)binary_start_offset, packet_data); |
| const uint8_t *p; |
| // Print binary data exactly as sent |
| for (p = (const uint8_t*)packet_data + binary_start_offset; *p != '#'; ++p) |
| strm.Printf("\\x%2.2x", *p); |
| // Print the checksum |
| strm.Printf("%*s", (int)3, p); |
| log->PutCString(strm.GetString().c_str()); |
| } |
| else |
| log->Printf("<%4" PRIu64 "> send packet: %.*s", (uint64_t)bytes_written, (int)packet_length, packet_data); |
| } |
| |
| m_history.AddPacket (packet.GetString(), packet_length, History::ePacketTypeSend, bytes_written); |
| |
| |
| if (bytes_written == packet_length) |
| { |
| if (GetSendAcks ()) |
| return GetAck (); |
| else |
| return PacketResult::Success; |
| } |
| else |
| { |
| if (log) |
| log->Printf ("error: failed to send packet: %.*s", (int)packet_length, packet_data); |
| } |
| } |
| return PacketResult::ErrorSendFailed; |
| } |
| |
| GDBRemoteCommunication::PacketResult |
| GDBRemoteCommunication::GetAck () |
| { |
| StringExtractorGDBRemote packet; |
| PacketResult result = ReadPacket (packet, GetPacketTimeoutInMicroSeconds (), false); |
| if (result == PacketResult::Success) |
| { |
| if (packet.GetResponseType() == StringExtractorGDBRemote::ResponseType::eAck) |
| return PacketResult::Success; |
| else |
| return PacketResult::ErrorSendAck; |
| } |
| return result; |
| } |
| |
| bool |
| GDBRemoteCommunication::GetSequenceMutex (Mutex::Locker& locker, const char *failure_message) |
| { |
| if (IsRunning()) |
| return locker.TryLock (m_sequence_mutex, failure_message); |
| |
| locker.Lock (m_sequence_mutex); |
| return true; |
| } |
| |
| |
| bool |
| GDBRemoteCommunication::WaitForNotRunningPrivate (const TimeValue *timeout_ptr) |
| { |
| return m_private_is_running.WaitForValueEqualTo (false, timeout_ptr, NULL); |
| } |
| |
| GDBRemoteCommunication::PacketResult |
| GDBRemoteCommunication::ReadPacket (StringExtractorGDBRemote &response, uint32_t timeout_usec, bool sync_on_timeout) |
| { |
| if (m_read_thread_enabled) |
| return PopPacketFromQueue (response, timeout_usec); |
| else |
| return WaitForPacketWithTimeoutMicroSecondsNoLock (response, timeout_usec, sync_on_timeout); |
| } |
| |
| |
| // This function is called when a packet is requested. |
| // A whole packet is popped from the packet queue and returned to the caller. |
| // Packets are placed into this queue from the communication read thread. |
| // See GDBRemoteCommunication::AppendBytesToCache. |
| GDBRemoteCommunication::PacketResult |
| GDBRemoteCommunication::PopPacketFromQueue (StringExtractorGDBRemote &response, uint32_t timeout_usec) |
| { |
| // Calculate absolute timeout value |
| TimeValue timeout = TimeValue::Now(); |
| timeout.OffsetWithMicroSeconds(timeout_usec); |
| |
| do |
| { |
| // scope for the mutex |
| { |
| // lock down the packet queue |
| Mutex::Locker locker(m_packet_queue_mutex); |
| |
| // Wait on condition variable. |
| if (m_packet_queue.size() == 0) |
| m_condition_queue_not_empty.Wait(m_packet_queue_mutex, &timeout); |
| |
| if (m_packet_queue.size() > 0) |
| { |
| // get the front element of the queue |
| response = m_packet_queue.front(); |
| |
| // remove the front element |
| m_packet_queue.pop(); |
| |
| // we got a packet |
| return PacketResult::Success; |
| } |
| } |
| |
| // Disconnected |
| if (!IsConnected()) |
| return PacketResult::ErrorDisconnected; |
| |
| // Loop while not timed out |
| } while (TimeValue::Now() < timeout); |
| |
| return PacketResult::ErrorReplyTimeout; |
| } |
| |
| |
| GDBRemoteCommunication::PacketResult |
| GDBRemoteCommunication::WaitForPacketWithTimeoutMicroSecondsNoLock (StringExtractorGDBRemote &packet, uint32_t timeout_usec, bool sync_on_timeout) |
| { |
| uint8_t buffer[8192]; |
| Error error; |
| |
| Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PACKETS | GDBR_LOG_VERBOSE)); |
| |
| // Check for a packet from our cache first without trying any reading... |
| if (CheckForPacket(NULL, 0, packet) != PacketType::Invalid) |
| return PacketResult::Success; |
| |
| bool timed_out = false; |
| bool disconnected = false; |
| while (IsConnected() && !timed_out) |
| { |
| lldb::ConnectionStatus status = eConnectionStatusNoConnection; |
| size_t bytes_read = Read (buffer, sizeof(buffer), timeout_usec, status, &error); |
| |
| if (log) |
| log->Printf ("%s: Read (buffer, (sizeof(buffer), timeout_usec = 0x%x, status = %s, error = %s) => bytes_read = %" PRIu64, |
| __PRETTY_FUNCTION__, |
| timeout_usec, |
| Communication::ConnectionStatusAsCString (status), |
| error.AsCString(), |
| (uint64_t)bytes_read); |
| |
| if (bytes_read > 0) |
| { |
| if (CheckForPacket(buffer, bytes_read, packet) != PacketType::Invalid) |
| return PacketResult::Success; |
| } |
| else |
| { |
| switch (status) |
| { |
| case eConnectionStatusTimedOut: |
| case eConnectionStatusInterrupted: |
| if (sync_on_timeout) |
| { |
| //------------------------------------------------------------------ |
| /// Sync the remote GDB server and make sure we get a response that |
| /// corresponds to what we send. |
| /// |
| /// Sends a "qEcho" packet and makes sure it gets the exact packet |
| /// echoed back. If the qEcho packet isn't supported, we send a qC |
| /// packet and make sure we get a valid thread ID back. We use the |
| /// "qC" packet since its response if very unique: is responds with |
| /// "QC%x" where %x is the thread ID of the current thread. This |
| /// makes the response unique enough from other packet responses to |
| /// ensure we are back on track. |
| /// |
| /// This packet is needed after we time out sending a packet so we |
| /// can ensure that we are getting the response for the packet we |
| /// are sending. There are no sequence IDs in the GDB remote |
| /// protocol (there used to be, but they are not supported anymore) |
| /// so if you timeout sending packet "abc", you might then send |
| /// packet "cde" and get the response for the previous "abc" packet. |
| /// Many responses are "OK" or "" (unsupported) or "EXX" (error) so |
| /// many responses for packets can look like responses for other |
| /// packets. So if we timeout, we need to ensure that we can get |
| /// back on track. If we can't get back on track, we must |
| /// disconnect. |
| //------------------------------------------------------------------ |
| bool sync_success = false; |
| bool got_actual_response = false; |
| // We timed out, we need to sync back up with the |
| char echo_packet[32]; |
| int echo_packet_len = 0; |
| RegularExpression response_regex; |
| |
| if (m_supports_qEcho == eLazyBoolYes) |
| { |
| echo_packet_len = ::snprintf (echo_packet, sizeof(echo_packet), "qEcho:%u", ++m_echo_number); |
| std::string regex_str = "^"; |
| regex_str += echo_packet; |
| regex_str += "$"; |
| response_regex.Compile(regex_str.c_str()); |
| } |
| else |
| { |
| echo_packet_len = ::snprintf (echo_packet, sizeof(echo_packet), "qC"); |
| response_regex.Compile("^QC[0-9A-Fa-f]+$"); |
| } |
| |
| PacketResult echo_packet_result = SendPacketNoLock (echo_packet, echo_packet_len); |
| if (echo_packet_result == PacketResult::Success) |
| { |
| const uint32_t max_retries = 3; |
| uint32_t successful_responses = 0; |
| for (uint32_t i=0; i<max_retries; ++i) |
| { |
| StringExtractorGDBRemote echo_response; |
| echo_packet_result = WaitForPacketWithTimeoutMicroSecondsNoLock (echo_response, timeout_usec, false); |
| if (echo_packet_result == PacketResult::Success) |
| { |
| ++successful_responses; |
| if (response_regex.Execute(echo_response.GetStringRef().c_str())) |
| { |
| sync_success = true; |
| break; |
| } |
| else if (successful_responses == 1) |
| { |
| // We got something else back as the first successful response, it probably is |
| // the response to the packet we actually wanted, so copy it over if this |
| // is the first success and continue to try to get the qEcho response |
| packet = echo_response; |
| got_actual_response = true; |
| } |
| } |
| else if (echo_packet_result == PacketResult::ErrorReplyTimeout) |
| continue; // Packet timed out, continue waiting for a response |
| else |
| break; // Something else went wrong getting the packet back, we failed and are done trying |
| } |
| } |
| |
| // We weren't able to sync back up with the server, we must abort otherwise |
| // all responses might not be from the right packets... |
| if (sync_success) |
| { |
| // We timed out, but were able to recover |
| if (got_actual_response) |
| { |
| // We initially timed out, but we did get a response that came in before the successful |
| // reply to our qEcho packet, so lets say everything is fine... |
| return PacketResult::Success; |
| } |
| } |
| else |
| { |
| disconnected = true; |
| Disconnect(); |
| } |
| } |
| timed_out = true; |
| break; |
| case eConnectionStatusSuccess: |
| //printf ("status = success but error = %s\n", error.AsCString("<invalid>")); |
| break; |
| |
| case eConnectionStatusEndOfFile: |
| case eConnectionStatusNoConnection: |
| case eConnectionStatusLostConnection: |
| case eConnectionStatusError: |
| disconnected = true; |
| Disconnect(); |
| break; |
| } |
| } |
| } |
| packet.Clear (); |
| if (disconnected) |
| return PacketResult::ErrorDisconnected; |
| if (timed_out) |
| return PacketResult::ErrorReplyTimeout; |
| else |
| return PacketResult::ErrorReplyFailed; |
| } |
| |
| bool |
| GDBRemoteCommunication::DecompressPacket () |
| { |
| Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PACKETS)); |
| |
| if (!CompressionIsEnabled()) |
| return true; |
| |
| size_t pkt_size = m_bytes.size(); |
| |
| // Smallest possible compressed packet is $N#00 - an uncompressed empty reply, most commonly indicating |
| // an unsupported packet. Anything less than 5 characters, it's definitely not a compressed packet. |
| if (pkt_size < 5) |
| 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) |
| { |
| // Put the packet data into the buffer in a thread safe fashion |
| Mutex::Locker locker(m_bytes_mutex); |
| |
| Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PACKETS)); |
| |
| if (src && src_len > 0) |
| { |
| if (log && log->GetVerbose()) |
| { |
| StreamString s; |
| log->Printf ("GDBRemoteCommunication::%s adding %u bytes: %.*s", |
| __FUNCTION__, |
| (uint32_t)src_len, |
| (uint32_t)src_len, |
| src); |
| } |
| m_bytes.append ((const char *)src, src_len); |
| } |
| |
| bool isNotifyPacket = false; |
| |
| // Parse up the packets into gdb remote packets |
| if (!m_bytes.empty()) |
| { |
| // end_idx must be one past the last valid packet byte. Start |
| // it off with an invalid value that is the same as the current |
| // index. |
| size_t content_start = 0; |
| size_t content_length = 0; |
| 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 |
| case '-': // Look for cancel |
| case '\x03': // ^C to halt target |
| content_length = total_length = 1; // The command is one byte long... |
| break; |
| |
| case '%': // Async notify packet |
| isNotifyPacket = true; |
| // Intentional fall through |
| |
| case '$': |
| // Look for a standard gdb packet? |
| { |
| size_t hash_pos = m_bytes.find('#'); |
| if (hash_pos != std::string::npos) |
| { |
| if (hash_pos + 2 < m_bytes.size()) |
| { |
| checksum_idx = hash_pos + 1; |
| // Skip the dollar sign |
| content_start = 1; |
| // Don't include the # in the content or the $ in the content length |
| content_length = hash_pos - 1; |
| |
| total_length = hash_pos + 3; // Skip the # and the two hex checksum bytes |
| } |
| else |
| { |
| // Checksum bytes aren't all here yet |
| content_length = std::string::npos; |
| } |
| } |
| } |
| break; |
| |
| default: |
| { |
| // We have an unexpected byte and we need to flush all bad |
| // data that is in m_bytes, so we need to find the first |
| // byte that is a '+' (ACK), '-' (NACK), \x03 (CTRL+C interrupt), |
| // or '$' character (start of packet header) or of course, |
| // the end of the data in m_bytes... |
| const size_t bytes_len = m_bytes.size(); |
| bool done = false; |
| uint32_t idx; |
| for (idx = 1; !done && idx < bytes_len; ++idx) |
| { |
| switch (m_bytes[idx]) |
| { |
| case '+': |
| case '-': |
| case '\x03': |
| case '%': |
| case '$': |
| done = true; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s tossing %u junk bytes: '%.*s'", |
| __FUNCTION__, idx - 1, idx - 1, m_bytes.c_str()); |
| m_bytes.erase(0, idx - 1); |
| } |
| break; |
| } |
| |
| if (content_length == std::string::npos) |
| { |
| packet.Clear(); |
| return GDBRemoteCommunication::PacketType::Invalid; |
| } |
| else if (total_length > 0) |
| { |
| |
| // We have a valid packet... |
| assert (content_length <= m_bytes.size()); |
| assert (total_length <= m_bytes.size()); |
| assert (content_length <= total_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 |
| // we have to the log so we get the historical context. The Dump() call that |
| // logs all of the packet will set a boolean so that we don't dump this more |
| // than once |
| if (!m_history.DidDumpToLog ()) |
| m_history.Dump (log); |
| |
| bool binary = false; |
| // Only detect binary for packets that start with a '$' and have a '#CC' checksum |
| if (m_bytes[0] == '$' && total_length > 4) |
| { |
| for (size_t i=0; !binary && i<total_length; ++i) |
| { |
| if (isprint(m_bytes[i]) == 0) |
| binary = true; |
| } |
| } |
| if (binary) |
| { |
| StreamString strm; |
| // Packet header... |
| 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... |
| const char ch = m_bytes[i]; |
| if (ch == 0x7d) |
| { |
| // 0x7d is the escape character. The next character is to |
| // be XOR'd with 0x20. |
| const char escapee = m_bytes[++i] ^ 0x20; |
| strm.Printf("%2.2x", escapee); |
| } |
| else |
| { |
| strm.Printf("%2.2x", (uint8_t)ch); |
| } |
| } |
| // Packet footer... |
| strm.Printf("%c%c%c", m_bytes[total_length-3], m_bytes[total_length-2], m_bytes[total_length-1]); |
| log->PutCString(strm.GetString().c_str()); |
| } |
| else |
| { |
| 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()); |
| } |
| } |
| |
| m_history.AddPacket (m_bytes.c_str(), total_length, History::ePacketTypeRecv, total_length); |
| |
| // Clear packet_str in case there is some existing data in it. |
| packet_str.clear(); |
| // Copy the packet from m_bytes to packet_str expanding the |
| // run-length encoding in the process. |
| // Reserve enough byte for the most common case (no RLE used) |
| packet_str.reserve(m_bytes.length()); |
| for (std::string::const_iterator c = m_bytes.begin() + content_start; c != m_bytes.begin() + content_end; ++c) |
| { |
| if (*c == '*') |
| { |
| // '*' indicates RLE. Next character will give us the |
| // repeat count and previous character is what is to be |
| // repeated. |
| char char_to_repeat = packet_str.back(); |
| // Number of time the previous character is repeated |
| int repeat_count = *++c + 3 - ' '; |
| // We have the char_to_repeat and repeat_count. Now push |
| // it in the packet. |
| for (int i = 0; i < repeat_count; ++i) |
| packet_str.push_back(char_to_repeat); |
| } |
| else if (*c == 0x7d) |
| { |
| // 0x7d is the escape character. The next character is to |
| // be XOR'd with 0x20. |
| char escapee = *++c ^ 0x20; |
| packet_str.push_back(escapee); |
| } |
| else |
| { |
| packet_str.push_back(*c); |
| } |
| } |
| |
| if (m_bytes[0] == '$' || m_bytes[0] == '%') |
| { |
| assert (checksum_idx < m_bytes.size()); |
| if (::isxdigit (m_bytes[checksum_idx+0]) || |
| ::isxdigit (m_bytes[checksum_idx+1])) |
| { |
| if (GetSendAcks ()) |
| { |
| const char *packet_checksum_cstr = &m_bytes[checksum_idx]; |
| char packet_checksum = strtol (packet_checksum_cstr, NULL, 16); |
| char actual_checksum = CalculcateChecksum (packet_str.c_str(), packet_str.size()); |
| success = packet_checksum == actual_checksum; |
| if (!success) |
| { |
| if (log) |
| log->Printf ("error: checksum mismatch: %.*s expected 0x%2.2x, got 0x%2.2x", |
| (int)(total_length), |
| m_bytes.c_str(), |
| (uint8_t)packet_checksum, |
| (uint8_t)actual_checksum); |
| } |
| // Send the ack or nack if needed |
| if (!success) |
| SendNack(); |
| else |
| SendAck(); |
| } |
| } |
| else |
| { |
| success = false; |
| if (log) |
| log->Printf ("error: invalid checksum in packet: '%s'\n", m_bytes.c_str()); |
| } |
| } |
| |
| m_bytes.erase(0, total_length); |
| packet.SetFilePos(0); |
| |
| if (isNotifyPacket) |
| return GDBRemoteCommunication::PacketType::Notify; |
| else |
| return GDBRemoteCommunication::PacketType::Standard; |
| } |
| } |
| packet.Clear(); |
| return GDBRemoteCommunication::PacketType::Invalid; |
| } |
| |
| Error |
| GDBRemoteCommunication::StartListenThread (const char *hostname, uint16_t port) |
| { |
| Error error; |
| if (m_listen_thread.IsJoinable()) |
| { |
| error.SetErrorString("listen thread already running"); |
| } |
| else |
| { |
| char listen_url[512]; |
| if (hostname && hostname[0]) |
| snprintf(listen_url, sizeof(listen_url), "listen://%s:%i", hostname, port); |
| else |
| snprintf(listen_url, sizeof(listen_url), "listen://%i", port); |
| m_listen_url = listen_url; |
| SetConnection(new ConnectionFileDescriptor()); |
| m_listen_thread = ThreadLauncher::LaunchThread(listen_url, GDBRemoteCommunication::ListenThread, this, &error); |
| } |
| return error; |
| } |
| |
| bool |
| GDBRemoteCommunication::JoinListenThread () |
| { |
| if (m_listen_thread.IsJoinable()) |
| m_listen_thread.Join(nullptr); |
| return true; |
| } |
| |
| lldb::thread_result_t |
| GDBRemoteCommunication::ListenThread (lldb::thread_arg_t arg) |
| { |
| GDBRemoteCommunication *comm = (GDBRemoteCommunication *)arg; |
| Error error; |
| ConnectionFileDescriptor *connection = (ConnectionFileDescriptor *)comm->GetConnection (); |
| |
| if (connection) |
| { |
| // Do the listen on another thread so we can continue on... |
| if (connection->Connect(comm->m_listen_url.c_str(), &error) != eConnectionStatusSuccess) |
| comm->SetConnection(NULL); |
| } |
| return NULL; |
| } |
| |
| Error |
| GDBRemoteCommunication::StartDebugserverProcess (const char *hostname, |
| uint16_t in_port, |
| ProcessLaunchInfo &launch_info, |
| uint16_t &out_port) |
| { |
| Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PROCESS)); |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s(hostname=%s, in_port=%" PRIu16 ", out_port=%" PRIu16, __FUNCTION__, hostname ? hostname : "<empty>", in_port, out_port); |
| |
| out_port = in_port; |
| Error error; |
| // If we locate debugserver, keep that located version around |
| static FileSpec g_debugserver_file_spec; |
| |
| char debugserver_path[PATH_MAX]; |
| FileSpec &debugserver_file_spec = launch_info.GetExecutableFile(); |
| |
| // Always check to see if we have an environment override for the path |
| // to the debugserver to use and use it if we do. |
| const char *env_debugserver_path = getenv("LLDB_DEBUGSERVER_PATH"); |
| if (env_debugserver_path) |
| { |
| debugserver_file_spec.SetFile (env_debugserver_path, false); |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s() gdb-remote stub exe path set from environment variable: %s", __FUNCTION__, env_debugserver_path); |
| } |
| else |
| debugserver_file_spec = g_debugserver_file_spec; |
| bool debugserver_exists = debugserver_file_spec.Exists(); |
| if (!debugserver_exists) |
| { |
| // The debugserver binary is in the LLDB.framework/Resources |
| // directory. |
| if (HostInfo::GetLLDBPath(ePathTypeSupportExecutableDir, debugserver_file_spec)) |
| { |
| debugserver_file_spec.AppendPathComponent (DEBUGSERVER_BASENAME); |
| debugserver_exists = debugserver_file_spec.Exists(); |
| if (debugserver_exists) |
| { |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s() found gdb-remote stub exe '%s'", __FUNCTION__, debugserver_file_spec.GetPath ().c_str ()); |
| |
| g_debugserver_file_spec = debugserver_file_spec; |
| } |
| else |
| { |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s() could not find gdb-remote stub exe '%s'", __FUNCTION__, debugserver_file_spec.GetPath ().c_str ()); |
| |
| g_debugserver_file_spec.Clear(); |
| debugserver_file_spec.Clear(); |
| } |
| } |
| } |
| |
| if (debugserver_exists) |
| { |
| debugserver_file_spec.GetPath (debugserver_path, sizeof(debugserver_path)); |
| |
| Args &debugserver_args = launch_info.GetArguments(); |
| debugserver_args.Clear(); |
| char arg_cstr[PATH_MAX]; |
| |
| // Start args with "debugserver /file/path -r --" |
| debugserver_args.AppendArgument(debugserver_path); |
| |
| #if !defined(__APPLE__) |
| // First argument to lldb-server must be mode in which to run. |
| debugserver_args.AppendArgument("gdbserver"); |
| #endif |
| |
| // If a host and port is supplied then use it |
| char host_and_port[128]; |
| if (hostname) |
| { |
| snprintf (host_and_port, sizeof(host_and_port), "%s:%u", hostname, in_port); |
| debugserver_args.AppendArgument(host_and_port); |
| } |
| else |
| { |
| host_and_port[0] = '\0'; |
| } |
| |
| // use native registers, not the GDB registers |
| debugserver_args.AppendArgument("--native-regs"); |
| |
| if (launch_info.GetLaunchInSeparateProcessGroup()) |
| { |
| debugserver_args.AppendArgument("--setsid"); |
| } |
| |
| llvm::SmallString<PATH_MAX> named_pipe_path; |
| Pipe port_pipe; |
| |
| if (host_and_port[0] && in_port == 0) |
| { |
| // Create a temporary file to get the stdout/stderr and redirect the |
| // output of the command into this file. We will later read this file |
| // if all goes well and fill the data into "command_output_ptr" |
| |
| // Binding to port zero, we need to figure out what port it ends up |
| // using using a named pipe... |
| error = port_pipe.CreateWithUniqueName("debugserver-named-pipe", false, named_pipe_path); |
| if (error.Success()) |
| { |
| debugserver_args.AppendArgument("--named-pipe"); |
| debugserver_args.AppendArgument(named_pipe_path.c_str()); |
| } |
| else |
| { |
| if (log) |
| log->Printf("GDBRemoteCommunication::%s() " |
| "named pipe creation failed: %s", |
| __FUNCTION__, error.AsCString()); |
| // let's try an unnamed pipe |
| error = port_pipe.CreateNew(true); |
| if (error.Fail()) |
| { |
| if (log) |
| log->Printf("GDBRemoteCommunication::%s() " |
| "unnamed pipe creation failed: %s", |
| __FUNCTION__, error.AsCString()); |
| return error; |
| } |
| int write_fd = port_pipe.GetWriteFileDescriptor(); |
| debugserver_args.AppendArgument("--pipe"); |
| debugserver_args.AppendArgument(std::to_string(write_fd).c_str()); |
| launch_info.AppendCloseFileAction(port_pipe.GetReadFileDescriptor()); |
| } |
| } |
| else |
| { |
| // No host and port given, so lets listen on our end and make the debugserver |
| // connect to us.. |
| error = StartListenThread ("127.0.0.1", 0); |
| if (error.Fail()) |
| { |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s() unable to start listen thread: %s", __FUNCTION__, error.AsCString()); |
| return error; |
| } |
| |
| ConnectionFileDescriptor *connection = (ConnectionFileDescriptor *)GetConnection (); |
| // Wait for 10 seconds to resolve the bound port |
| out_port = connection->GetListeningPort(10); |
| if (out_port > 0) |
| { |
| char port_cstr[32]; |
| snprintf(port_cstr, sizeof(port_cstr), "127.0.0.1:%i", out_port); |
| // Send the host and port down that debugserver and specify an option |
| // so that it connects back to the port we are listening to in this process |
| debugserver_args.AppendArgument("--reverse-connect"); |
| debugserver_args.AppendArgument(port_cstr); |
| } |
| else |
| { |
| error.SetErrorString ("failed to bind to port 0 on 127.0.0.1"); |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s() failed: %s", __FUNCTION__, error.AsCString()); |
| return error; |
| } |
| } |
| |
| const char *env_debugserver_log_file = getenv("LLDB_DEBUGSERVER_LOG_FILE"); |
| if (env_debugserver_log_file) |
| { |
| ::snprintf (arg_cstr, sizeof(arg_cstr), "--log-file=%s", env_debugserver_log_file); |
| debugserver_args.AppendArgument(arg_cstr); |
| } |
| |
| #if defined(__APPLE__) |
| const char *env_debugserver_log_flags = getenv("LLDB_DEBUGSERVER_LOG_FLAGS"); |
| if (env_debugserver_log_flags) |
| { |
| ::snprintf (arg_cstr, sizeof(arg_cstr), "--log-flags=%s", env_debugserver_log_flags); |
| debugserver_args.AppendArgument(arg_cstr); |
| } |
| #else |
| const char *env_debugserver_log_channels = getenv("LLDB_SERVER_LOG_CHANNELS"); |
| if (env_debugserver_log_channels) |
| { |
| ::snprintf (arg_cstr, sizeof(arg_cstr), "--log-channels=%s", env_debugserver_log_channels); |
| debugserver_args.AppendArgument(arg_cstr); |
| } |
| #endif |
| |
| // Add additional args, starting with LLDB_DEBUGSERVER_EXTRA_ARG_1 until an env var doesn't come back. |
| uint32_t env_var_index = 1; |
| bool has_env_var; |
| do |
| { |
| char env_var_name[64]; |
| snprintf (env_var_name, sizeof (env_var_name), "LLDB_DEBUGSERVER_EXTRA_ARG_%" PRIu32, env_var_index++); |
| const char *extra_arg = getenv(env_var_name); |
| has_env_var = extra_arg != nullptr; |
| |
| if (has_env_var) |
| { |
| debugserver_args.AppendArgument (extra_arg); |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s adding env var %s contents to stub command line (%s)", __FUNCTION__, env_var_name, extra_arg); |
| } |
| } while (has_env_var); |
| |
| // Close STDIN, STDOUT and STDERR. |
| launch_info.AppendCloseFileAction (STDIN_FILENO); |
| launch_info.AppendCloseFileAction (STDOUT_FILENO); |
| launch_info.AppendCloseFileAction (STDERR_FILENO); |
| |
| // Redirect STDIN, STDOUT and STDERR to "/dev/null". |
| launch_info.AppendSuppressFileAction (STDIN_FILENO, true, false); |
| launch_info.AppendSuppressFileAction (STDOUT_FILENO, false, true); |
| launch_info.AppendSuppressFileAction (STDERR_FILENO, false, true); |
| |
| error = Host::LaunchProcess(launch_info); |
| |
| if (error.Success() && launch_info.GetProcessID() != LLDB_INVALID_PROCESS_ID) |
| { |
| if (named_pipe_path.size() > 0) |
| { |
| error = port_pipe.OpenAsReader(named_pipe_path, false); |
| if (error.Fail()) |
| if (log) |
| log->Printf("GDBRemoteCommunication::%s() " |
| "failed to open named pipe %s for reading: %s", |
| __FUNCTION__, named_pipe_path.c_str(), error.AsCString()); |
| } |
| |
| if (port_pipe.CanWrite()) |
| port_pipe.CloseWriteFileDescriptor(); |
| if (port_pipe.CanRead()) |
| { |
| char port_cstr[256]; |
| port_cstr[0] = '\0'; |
| size_t num_bytes = sizeof(port_cstr); |
| // Read port from pipe with 10 second timeout. |
| error = port_pipe.ReadWithTimeout(port_cstr, num_bytes, |
| std::chrono::seconds{10}, num_bytes); |
| if (error.Success()) |
| { |
| assert(num_bytes > 0 && port_cstr[num_bytes-1] == '\0'); |
| out_port = StringConvert::ToUInt32(port_cstr, 0); |
| if (log) |
| log->Printf("GDBRemoteCommunication::%s() " |
| "debugserver listens %u port", |
| __FUNCTION__, out_port); |
| } |
| else |
| { |
| if (log) |
| log->Printf("GDBRemoteCommunication::%s() " |
| "failed to read a port value from pipe %s: %s", |
| __FUNCTION__, named_pipe_path.c_str(), error.AsCString()); |
| |
| } |
| port_pipe.Close(); |
| } |
| |
| if (named_pipe_path.size() > 0) |
| { |
| const auto err = port_pipe.Delete(named_pipe_path); |
| if (err.Fail()) |
| { |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s failed to delete pipe %s: %s", |
| __FUNCTION__, named_pipe_path.c_str(), err.AsCString()); |
| } |
| } |
| |
| // Make sure we actually connect with the debugserver... |
| JoinListenThread(); |
| } |
| } |
| else |
| { |
| error.SetErrorStringWithFormat ("unable to locate " DEBUGSERVER_BASENAME ); |
| } |
| |
| if (error.Fail()) |
| { |
| if (log) |
| log->Printf ("GDBRemoteCommunication::%s() failed: %s", __FUNCTION__, error.AsCString()); |
| } |
| |
| return error; |
| } |
| |
| void |
| GDBRemoteCommunication::DumpHistory(Stream &strm) |
| { |
| m_history.Dump (strm); |
| } |
| |
| GDBRemoteCommunication::ScopedTimeout::ScopedTimeout (GDBRemoteCommunication& gdb_comm, |
| uint32_t timeout) : |
| m_gdb_comm (gdb_comm) |
| { |
| m_saved_timeout = m_gdb_comm.SetPacketTimeout (timeout); |
| } |
| |
| GDBRemoteCommunication::ScopedTimeout::~ScopedTimeout () |
| { |
| m_gdb_comm.SetPacketTimeout (m_saved_timeout); |
| } |
| |
| // This function is called via the Communications class read thread when bytes become available |
| // for this connection. This function will consume all incoming bytes and try to parse whole |
| // packets as they become available. Full packets are placed in a queue, so that all packet |
| // requests can simply pop from this queue. Async notification packets will be dispatched |
| // immediately to the ProcessGDBRemote Async thread via an event. |
| void GDBRemoteCommunication::AppendBytesToCache (const uint8_t * bytes, size_t len, bool broadcast, lldb::ConnectionStatus status) |
| { |
| StringExtractorGDBRemote packet; |
| |
| while (true) |
| { |
| PacketType type = CheckForPacket(bytes, len, packet); |
| |
| // scrub the data so we do not pass it back to CheckForPacket |
| // on future passes of the loop |
| bytes = nullptr; |
| len = 0; |
| |
| // we may have received no packet so lets bail out |
| if (type == PacketType::Invalid) |
| break; |
| |
| if (type == PacketType::Standard) |
| { |
| // scope for the mutex |
| { |
| // lock down the packet queue |
| Mutex::Locker locker(m_packet_queue_mutex); |
| // push a new packet into the queue |
| m_packet_queue.push(packet); |
| // Signal condition variable that we have a packet |
| m_condition_queue_not_empty.Signal(); |
| |
| } |
| } |
| |
| if (type == PacketType::Notify) |
| { |
| // put this packet into an event |
| const char *pdata = packet.GetStringRef().c_str(); |
| |
| // as the communication class, we are a broadcaster and the |
| // async thread is tuned to listen to us |
| BroadcastEvent( |
| eBroadcastBitGdbReadThreadGotNotify, |
| new EventDataBytes(pdata)); |
| } |
| } |
| } |