| //===-- GDBRemoteCommunication.h --------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef liblldb_GDBRemoteCommunication_h_ |
| #define liblldb_GDBRemoteCommunication_h_ |
| |
| // C Includes |
| // C++ Includes |
| #include <condition_variable> |
| #include <mutex> |
| #include <queue> |
| #include <string> |
| #include <vector> |
| |
| // Other libraries and framework includes |
| // Project includes |
| #include "lldb/Core/Communication.h" |
| #include "lldb/Core/Listener.h" |
| #include "lldb/Host/HostThread.h" |
| #include "lldb/Host/Predicate.h" |
| #include "lldb/Utility/Args.h" |
| #include "lldb/lldb-public.h" |
| |
| #include "lldb/Utility/StringExtractorGDBRemote.h" |
| |
| namespace lldb_private { |
| namespace process_gdb_remote { |
| |
| typedef enum { |
| eStoppointInvalid = -1, |
| eBreakpointSoftware = 0, |
| eBreakpointHardware, |
| eWatchpointWrite, |
| eWatchpointRead, |
| 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 { |
| public: |
| enum { |
| eBroadcastBitRunPacketSent = kLoUserBroadcastBit, |
| eBroadcastBitGdbReadThreadGotNotify = |
| kLoUserBroadcastBit << 1 // Sent when we received a notify packet. |
| }; |
| |
| enum class PacketType { Invalid = 0, Standard, Notify }; |
| |
| enum class PacketResult { |
| Success = 0, // Success |
| ErrorSendFailed, // Status sending the packet |
| ErrorSendAck, // Didn't get an ack back after sending a packet |
| ErrorReplyFailed, // Status getting the reply |
| ErrorReplyTimeout, // Timed out waiting for reply |
| ErrorReplyInvalid, // Got a reply but it wasn't valid for the packet that |
| // was sent |
| ErrorReplyAck, // Sending reply ack failed |
| ErrorDisconnected, // We were disconnected |
| ErrorNoSequenceLock // We couldn't get the sequence lock for a multi-packet |
| // request |
| }; |
| |
| // Class to change the timeout for a given scope and restore it to the |
| // original value when the |
| // created ScopedTimeout object got out of scope |
| class ScopedTimeout { |
| public: |
| ScopedTimeout(GDBRemoteCommunication &gdb_comm, |
| std::chrono::seconds timeout); |
| ~ScopedTimeout(); |
| |
| private: |
| GDBRemoteCommunication &m_gdb_comm; |
| std::chrono::seconds m_saved_timeout; |
| // Don't ever reduce the timeout for a packet, only increase it. If the |
| // requested timeout if less than the current timeout, we don't set it |
| // and won't need to restore it. |
| bool m_timeout_modified; |
| }; |
| |
| GDBRemoteCommunication(const char *comm_name, const char *listener_name); |
| |
| ~GDBRemoteCommunication() override; |
| |
| PacketResult GetAck(); |
| |
| size_t SendAck(); |
| |
| size_t SendNack(); |
| |
| char CalculcateChecksum(llvm::StringRef payload); |
| |
| PacketType CheckForPacket(const uint8_t *src, size_t src_len, |
| StringExtractorGDBRemote &packet); |
| |
| bool GetSendAcks() { return m_send_acks; } |
| |
| //------------------------------------------------------------------ |
| // Set the global packet timeout. |
| // |
| // For clients, this is the timeout that gets used when sending |
| // packets and waiting for responses. For servers, this is used when waiting |
| // for ACKs. |
| //------------------------------------------------------------------ |
| std::chrono::seconds SetPacketTimeout(std::chrono::seconds packet_timeout) { |
| const auto old_packet_timeout = m_packet_timeout; |
| m_packet_timeout = packet_timeout; |
| return old_packet_timeout; |
| } |
| |
| std::chrono::seconds GetPacketTimeout() const { return m_packet_timeout; } |
| |
| //------------------------------------------------------------------ |
| // Start a debugserver instance on the current host using the |
| // supplied connection URL. |
| //------------------------------------------------------------------ |
| Status StartDebugserverProcess( |
| const char *url, |
| Platform *platform, // If non nullptr, then check with the platform for |
| // the GDB server binary if it can't be located |
| ProcessLaunchInfo &launch_info, uint16_t *port, const Args *inferior_args, |
| int pass_comm_fd); // Communication file descriptor to pass during |
| // fork/exec to avoid having to connect/accept |
| |
| void DumpHistory(Stream &strm); |
| |
| protected: |
| class History { |
| public: |
| enum PacketType { |
| ePacketTypeInvalid = 0, |
| ePacketTypeSend, |
| ePacketTypeRecv |
| }; |
| |
| struct Entry { |
| Entry() |
| : packet(), type(ePacketTypeInvalid), bytes_transmitted(0), |
| packet_idx(0), tid(LLDB_INVALID_THREAD_ID) {} |
| |
| void Clear() { |
| packet.clear(); |
| type = ePacketTypeInvalid; |
| bytes_transmitted = 0; |
| packet_idx = 0; |
| tid = LLDB_INVALID_THREAD_ID; |
| } |
| std::string packet; |
| PacketType type; |
| uint32_t bytes_transmitted; |
| uint32_t packet_idx; |
| lldb::tid_t tid; |
| }; |
| |
| History(uint32_t size); |
| |
| ~History(); |
| |
| // For single char packets for ack, nack and /x03 |
| void AddPacket(char packet_char, PacketType type, |
| uint32_t bytes_transmitted); |
| |
| void AddPacket(const std::string &src, uint32_t src_len, PacketType type, |
| uint32_t bytes_transmitted); |
| |
| void Dump(Stream &strm) const; |
| |
| void Dump(Log *log) const; |
| |
| bool DidDumpToLog() const { return m_dumped_to_log; } |
| |
| protected: |
| uint32_t GetFirstSavedPacketIndex() const { |
| if (m_total_packet_count < m_packets.size()) |
| return 0; |
| else |
| return m_curr_idx + 1; |
| } |
| |
| uint32_t GetNumPacketsInHistory() const { |
| if (m_total_packet_count < m_packets.size()) |
| return m_total_packet_count; |
| else |
| return (uint32_t)m_packets.size(); |
| } |
| |
| uint32_t GetNextIndex() { |
| ++m_total_packet_count; |
| const uint32_t idx = m_curr_idx; |
| m_curr_idx = NormalizeIndex(idx + 1); |
| return idx; |
| } |
| |
| uint32_t NormalizeIndex(uint32_t i) const { return i % m_packets.size(); } |
| |
| std::vector<Entry> m_packets; |
| uint32_t m_curr_idx; |
| uint32_t m_total_packet_count; |
| mutable bool m_dumped_to_log; |
| }; |
| |
| std::chrono::seconds m_packet_timeout; |
| uint32_t m_echo_number; |
| LazyBool m_supports_qEcho; |
| History m_history; |
| bool m_send_acks; |
| bool m_is_platform; // Set to true if this class represents a platform, |
| // false if this class represents a debug session for |
| // a single process |
| |
| CompressionType m_compression_type; |
| |
| PacketResult SendPacketNoLock(llvm::StringRef payload); |
| |
| PacketResult ReadPacket(StringExtractorGDBRemote &response, |
| Timeout<std::micro> timeout, bool sync_on_timeout); |
| |
| PacketResult ReadPacketWithOutputSupport( |
| StringExtractorGDBRemote &response, Timeout<std::micro> timeout, |
| bool sync_on_timeout, |
| llvm::function_ref<void(llvm::StringRef)> output_callback); |
| |
| // Pop a packet from the queue in a thread safe manner |
| PacketResult PopPacketFromQueue(StringExtractorGDBRemote &response, |
| Timeout<std::micro> timeout); |
| |
| PacketResult WaitForPacketNoLock(StringExtractorGDBRemote &response, |
| Timeout<std::micro> timeout, |
| bool sync_on_timeout); |
| |
| 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(); |
| |
| Status StartListenThread(const char *hostname = "127.0.0.1", |
| uint16_t port = 0); |
| |
| bool JoinListenThread(); |
| |
| static lldb::thread_result_t ListenThread(lldb::thread_arg_t arg); |
| |
| // GDB-Remote read thread |
| // . this thread constantly tries to read from the communication |
| // class and stores all packets received in a queue. The usual |
| // threads read requests simply pop packets off the queue in the |
| // usual order. |
| // This setup allows us to intercept and handle async packets, such |
| // as the notify packet. |
| |
| // This method is defined as part of communication.h |
| // when the read thread gets any bytes it will pass them on to this function |
| void AppendBytesToCache(const uint8_t *bytes, size_t len, bool broadcast, |
| lldb::ConnectionStatus status) override; |
| |
| private: |
| std::queue<StringExtractorGDBRemote> m_packet_queue; // The packet queue |
| std::mutex m_packet_queue_mutex; // Mutex for accessing queue |
| std::condition_variable |
| m_condition_queue_not_empty; // Condition variable to wait for packets |
| |
| HostThread m_listen_thread; |
| std::string m_listen_url; |
| |
| DISALLOW_COPY_AND_ASSIGN(GDBRemoteCommunication); |
| }; |
| |
| } // namespace process_gdb_remote |
| } // namespace lldb_private |
| |
| namespace llvm { |
| template <> |
| struct format_provider< |
| lldb_private::process_gdb_remote::GDBRemoteCommunication::PacketResult> { |
| static void format(const lldb_private::process_gdb_remote:: |
| GDBRemoteCommunication::PacketResult &state, |
| raw_ostream &Stream, StringRef Style); |
| }; |
| } // namespace llvm |
| |
| #endif // liblldb_GDBRemoteCommunication_h_ |