mirror of
https://github.com/Gericom/teak-llvm.git
synced 2025-06-27 07:19:03 -04:00

qEcho:%s where '%s' is any valid string. The response to this packet is the exact packet itself with no changes, just reply with what you received! This will help us to recover from packets timing out much more gracefully. Currently if a packet times out, LLDB quickly will hose up the debug session. For example, if we send a "abc" packet and we expect "ABC" back in response, but the "abc" command takes longer than the current timeout value this will happen: --> "abc" <-- <<<error: timeout>>> Now we want to send "def" and get "DEF" back: --> "def" <-- "ABC" We got the wrong response for the "def" packet because we didn't sync up with the server to clear any current responses from previously issues commands. The fix is to modify GDBRemoteCommunication::WaitForPacketWithTimeoutMicroSecondsNoLock() so that when it gets a timeout, it syncs itself up with the client by sending a "qEcho:%u" where %u is an increasing integer, one for each time we timeout. We then wait for 3 timeout periods to sync back up. So the above "abc" session would look like: --> "abc" <-- <<<error: timeout>>> 1 second --> "qEcho:1" <-- <<<error: timeout>>> 1 second <-- <<<error: timeout>>> 1 second <-- "abc" <-- "qEcho:1" The first timeout is from trying to get the response, then we know we timed out and we send the "qEcho:1" packet and wait for 3 timeout periods to get back in sync knowing that we might actually get the response for the "abc" packet in the mean time... In this case we would actually succeed in getting the response for "abc". But lets say the remote GDB server is deadlocked and will never response, it would look like: --> "abc" <-- <<<error: timeout>>> 1 second --> "qEcho:1" <-- <<<error: timeout>>> 1 second <-- <<<error: timeout>>> 1 second <-- <<<error: timeout>>> 1 second We then disconnect and say we lost connection. We might also have a bad GDB server that just dropped the "abc" packet on the floor. We can still recover in this case and it would look like: --> "abc" <-- <<<error: timeout>>> 1 second --> "qEcho:1" <-- "qEcho:1" Then we know our remote GDB server is still alive and well, and it just dropped the "abc" response on the floor and we can continue to debug. <rdar://problem/21082939> llvm-svn: 238530
1097 lines
42 KiB
C++
1097 lines
42 KiB
C++
//===-- 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
|
|
|
|
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_listen_url ()
|
|
{
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Destructor
|
|
//----------------------------------------------------------------------
|
|
GDBRemoteCommunication::~GDBRemoteCommunication()
|
|
{
|
|
if (IsConnected())
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
|
|
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 = WaitForPacketWithTimeoutMicroSecondsNoLock (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::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;
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
const 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...
|
|
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
|
|
{
|
|
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);
|
|
}
|