Index: include/lldb/API/SBDebugger.h =================================================================== --- include/lldb/API/SBDebugger.h +++ include/lldb/API/SBDebugger.h @@ -109,7 +109,7 @@ const char *archname); lldb::SBTarget CreateTarget(const char *filename); - + lldb::SBTarget GetDummyTarget(); // Return true if target is deleted from the target list of the debugger. @@ -226,6 +226,10 @@ void SetPrompt(const char *prompt); + const char *GetReproducerPath() const; + + void SetReproducerPath(const char *reproducer); + lldb::ScriptLanguage GetScriptLanguage() const; void SetScriptLanguage(lldb::ScriptLanguage script_lang); Index: include/lldb/Core/Debugger.h =================================================================== --- include/lldb/Core/Debugger.h +++ include/lldb/Core/Debugger.h @@ -31,6 +31,7 @@ #include "lldb/Target/TargetList.h" #include "lldb/Utility/ConstString.h" // for ConstString #include "lldb/Utility/FileSpec.h" // for FileSpec +#include "lldb/Utility/Reproducer.h" #include "lldb/Utility/Status.h" // for Status #include "lldb/Utility/UserID.h" #include "lldb/lldb-defines.h" // for DISALLOW_COPY_AND_ASSIGN @@ -226,6 +227,8 @@ void SetLoggingCallback(lldb::LogOutputCallback log_callback, void *baton); + repro::Reproducer &GetReproducer() { return m_reproducer; } + //---------------------------------------------------------------------- // Properties Functions //---------------------------------------------------------------------- @@ -301,6 +304,14 @@ bool SetTabSize(uint32_t tab_size); + bool GetGenerateReproducer() const; + + bool SetGenerateReproducer(bool b); + + FileSpec GetReproducerPath() const; + + void SetReproducerPath(const FileSpec &p); + bool GetEscapeNonPrintables() const; bool GetNotifyVoid() const; @@ -407,6 +418,7 @@ Broadcaster m_sync_broadcaster; lldb::ListenerSP m_forward_listener_sp; llvm::once_flag m_clear_once; + repro::Reproducer m_reproducer; //---------------------------------------------------------------------- // Events for m_sync_broadcaster Index: include/lldb/Host/HostInfoBase.h =================================================================== --- include/lldb/Host/HostInfoBase.h +++ include/lldb/Host/HostInfoBase.h @@ -97,6 +97,12 @@ /// FileSpec is filled in. static FileSpec GetGlobalTempDir(); + /// Returns the reproducer temporary directory. This directory will **not** + /// be automatically cleaned up when this process exits, but might be removed + /// by the reproducer generator. Only the directory member of the FileSpec is + /// filled in. + static FileSpec GetReproducerTempDir(); + //--------------------------------------------------------------------------- /// If the triple does not specify the vendor, os, and environment parts, we /// "augment" these using information from the host and return the resulting @@ -109,6 +115,7 @@ static bool ComputeSupportExeDirectory(FileSpec &file_spec); static bool ComputeProcessTempFileDirectory(FileSpec &file_spec); static bool ComputeGlobalTempFileDirectory(FileSpec &file_spec); + static bool ComputeReproducerTempFileDirectory(FileSpec &file_spec); static bool ComputeTempFileBaseDirectory(FileSpec &file_spec); static bool ComputeHeaderDirectory(FileSpec &file_spec); static bool ComputeSystemPluginsDirectory(FileSpec &file_spec); Index: include/lldb/Utility/Reproducer.h =================================================================== --- /dev/null +++ include/lldb/Utility/Reproducer.h @@ -0,0 +1,164 @@ +//===-- Reproducer.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UTILITY_REPRODUCER_H +#define LLDB_UTILITY_REPRODUCER_H + +#include "lldb/Utility/FileSpec.h" + +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/YAMLTraits.h" + +#include +#include +#include + +namespace lldb_private { +namespace repro { + +class Reproducer; + +/// Abstraction for information associated with a provider. This information +/// is serialized into an index which is used by the loader. +struct ProviderInfo { + std::string name; + std::vector files; +}; + +/// The provider defines an interface for generating files needed for +/// reproducing. The provider must populate its ProviderInfo to communicate +/// its name and files to the index, before registering with the generator, +/// i.e. in the constructor. +/// +/// Different components will implement different providers. +class Provider { +public: + virtual ~Provider() = default; + + const ProviderInfo &GetInfo() { return m_info; } + const FileSpec &GetDirectory() { return m_directory; } + + /// The Keep method is called when it is decided that we need to keep the + /// data in order to provide a reproducer. + virtual void Keep(){}; + + /// The Discard method is called when it is decided that we do not need to + /// keep any information and will not generate a reproducer. + virtual void Discard(){}; + +protected: + Provider(FileSpec directory) : m_directory(directory) {} + + /// Every provider keeps track of its own files. + ProviderInfo m_info; + +private: + /// Every provider knows where to dump its potential files. + FileSpec m_directory; +}; + +/// The generator is responsible for the logic needed to generate a +/// reproducer. For doing so it relies on providers, who serialize data that +/// is necessary for reproducing a failure. +class Generator final { +public: + Generator(); + ~Generator(); + + /// Method to indicate we want to keep the reproducer. If reproducer + /// generation is disabled, this does nothing. + void Keep(); + + /// Method to indicate we do not want to keep the reproducer. This is + /// unaffected by whether or not generation reproduction is enabled, as we + /// might need to clean up files already written to disk. + void Discard(); + + /// Providers are registered at creating time. + template T &CreateProvider() { + std::unique_ptr provider = llvm::make_unique(m_directory); + return static_cast(Register(std::move(provider))); + } + + void ChangeDirectory(const FileSpec &directory); + +private: + friend Reproducer; + + void SetEnabled(bool enabled) { m_enabled = enabled; } + Provider &Register(std::unique_ptr provider); + void AddProviderToIndex(const ProviderInfo &provider_info); + + std::vector> m_providers; + std::mutex m_providers_mutex; + + /// The reproducer root directory. + FileSpec m_directory; + + /// Flag for controlling whether we generate a reproducer when Keep is + /// called. + bool m_enabled; + + /// Flag to ensure that we never call both keep and discard. + bool m_done; +}; + +class Loader final { +public: + Loader(); + + llvm::Optional GetProviderInfo(llvm::StringRef name); + llvm::Error LoadIndex(const FileSpec &directory); + + const FileSpec &GetDirectory() { return m_directory; } + +private: + llvm::StringMap m_provider_info; + FileSpec m_directory; + bool m_loaded; +}; + +/// The reproducer enables clients to obtain access to the Generator and +/// Loader. +class Reproducer final { + +public: + Generator *GetGenerator(); + Loader *GetLoader(); + + void SetGenerateReproducer(bool value); + void SetUseReproducer(bool value); + +private: + Generator m_generator; + Loader m_loader; + + bool m_generate_reproducer = false; + bool m_use_reproducer = false; +}; + +} // namespace repro +} // namespace lldb_private + +LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(lldb_private::repro::ProviderInfo) + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + static void mapping(IO &io, lldb_private::repro::ProviderInfo &info) { + io.mapRequired("name", info.name); + io.mapOptional("files", info.files); + } +}; +} // namespace yaml +} // namespace llvm + +#endif // LLDB_UTILITY_REPRODUCER_H Index: packages/Python/lldbsuite/test/functionalities/reproducer/gdb-remote/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/reproducer/gdb-remote/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/reproducer/gdb-remote/TestGdbRemoteReproducer.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/reproducer/gdb-remote/TestGdbRemoteReproducer.py @@ -0,0 +1,108 @@ +""" +Test the GDB remote reproducer. +""" + +from __future__ import print_function + +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestGdbRemoteReproducer(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def reset(self): + lldb.SBDebugger.Destroy(lldb.dbg) + lldb.dbg = lldb.SBDebugger.Create() + + def contains(self, retobject, substrs, message): + for string in substrs: + self.assertTrue(string in retobj.GetOutput(), message) + + def test(self): + """Test record and replay of gdb-remote packets.""" + self.build() + + debugger_1 = lldb.SBDebugger.Create(False) + interpreter_1 = debugger_1.GetCommandInterpreter() + retobj = lldb.SBCommandReturnObject() + + # Create temp directory for the reproducer. + temp_dir = tempfile.mkdtemp() + + # Run debug session and record gdb remote packets. + interpreter_1.HandleCommand("settings set log-reproducer true", retobj) + self.assertTrue(retobj.Succeeded()) + + interpreter_1.HandleCommand("settings set reproducer-path {}".format(temp_dir), retobj) + self.assertTrue(retobj.Succeeded()) + + exe = self.getBuildArtifact("a.out") + interpreter_1.HandleCommand("file {}".format(exe), retobj) + self.assertTrue(retobj.Succeeded()) + print(str(retobj)) + + interpreter_1.HandleCommand("breakpoint set -f main.c -l 13", retobj) + self.assertTrue(retobj.Succeeded()) + print(str(retobj)) + + interpreter_1.HandleCommand("run", retobj) + self.assertTrue(retobj.Succeeded()) + print(str(retobj)) + + interpreter_1.HandleCommand("bt", retobj) + self.assertTrue(retobj.Succeeded()) + print(str(retobj)) + + interpreter_1.HandleCommand("cont", retobj) + self.assertTrue(retobj.Succeeded()) + print(str(retobj)) + + lldb.SBDebugger.Destroy(debugger_1) + + return + + lldbutil.run_break_set_by_file_and_line( + self, "main.c", 13, loc_exact=True) + + self.runCmd("run", RUN_SUCCEEDED) + + self.expect( + "bt", + substrs=['a.out`foo', 'main.c:13', 'a.out`main', 'main.c:17']) + + self.expect('cont', 'testing') + + # Reset the debugger instance. + self.reset() + + # Ensure reproducer files are in our temp directory. + self.assertTrue(os.path.exists(os.path.join(temp_dir, 'index.yaml'))) + self.assertTrue( + os.path.exists(os.path.join(temp_dir, 'gdb-remote.yaml'))) + + exe = self.getBuildArtifact("a.out") + target = self.dbg.CreateTarget(exe) + + # Now replay the session from the reproducer. + self.runCmd("settings set reproducer-path {}".format(temp_dir)) + + lldbutil.run_break_set_by_file_and_line( + self, "main.c", 13, loc_exact=True) + + self.runCmd("run", RUN_SUCCEEDED) + + self.expect( + "bt", + substrs=['a.out`foo', 'main.c:13', 'a.out`main', 'main.c:17']) + + # Ensure we're looking at the replay and we don't seen the binary's + # output. + self.expect('cont', matching=False, substrs=['testing']) + + # Reset the debugger instance. + self.reset() Index: packages/Python/lldbsuite/test/functionalities/reproducer/gdb-remote/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/reproducer/gdb-remote/main.c @@ -0,0 +1,19 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include + +void foo() { + printf("testing\n"); +} + +int main (int argc, char const *argv[]) { + foo(); + return 0; +} Index: source/API/SBDebugger.cpp =================================================================== --- source/API/SBDebugger.cpp +++ source/API/SBDebugger.cpp @@ -1050,6 +1050,18 @@ m_opaque_sp->SetPrompt(llvm::StringRef::withNullAsEmpty(prompt)); } +const char *SBDebugger::GetReproducerPath() const { + return ( + m_opaque_sp + ? ConstString(m_opaque_sp->GetReproducerPath().GetPath()).GetCString() + : nullptr); +} + +void SBDebugger::SetReproducerPath(const char *p) { + if (m_opaque_sp) + m_opaque_sp->SetReproducerPath(FileSpec(p, true)); +} + ScriptLanguage SBDebugger::GetScriptLanguage() const { return (m_opaque_sp ? m_opaque_sp->GetScriptLanguage() : eScriptLanguageNone); } Index: source/Core/Debugger.cpp =================================================================== --- source/Core/Debugger.cpp +++ source/Core/Debugger.cpp @@ -285,6 +285,12 @@ DEFAULT_FRAME_FORMAT_NO_ARGS, nullptr, "The default frame format string to use when displaying stack frame" "information for threads from thread backtrace unique."}, + {"log-reproducer", OptionValue::eTypeBoolean, false, false, nullptr, + nullptr, + "If true, LLDB will gather and log data necessary to replay the current " + "session."}, + {"reproducer-path", OptionValue::eTypeFileSpec, false, 0, nullptr, nullptr, + "If set, LLDB will replay the session from the provided reproducer."}, {nullptr, OptionValue::eTypeInvalid, true, 0, nullptr, nullptr, nullptr}}; enum { @@ -313,6 +319,8 @@ ePropertyTabSize, ePropertyEscapeNonPrintables, ePropertyFrameFormatUnique, + ePropertyGenerateReproducer, + ePropertyReproducerPath, }; LoadPluginCallbackType Debugger::g_load_plugin_callback = nullptr; @@ -348,6 +356,11 @@ // use-color changed. Ping the prompt so it can reset the ansi terminal // codes. SetPrompt(GetPrompt()); + } else if (property_path == + g_properties[ePropertyGenerateReproducer].name) { + SetGenerateReproducer(GetGenerateReproducer()); + } else if (property_path == g_properties[ePropertyReproducerPath].name) { + SetReproducerPath(GetReproducerPath()); } else if (is_load_script && target_sp && load_script_old_value == eLoadScriptFromSymFileWarn) { if (target_sp->TargetProperties::GetLoadScriptFromSymbolFile() == @@ -556,6 +569,38 @@ return m_collection_sp->SetPropertyAtIndexAsUInt64(nullptr, idx, tab_size); } +bool Debugger::GetGenerateReproducer() const { + const uint32_t idx = ePropertyGenerateReproducer; + return m_collection_sp->GetPropertyAtIndexAsBoolean(nullptr, idx, true); +} + +bool Debugger::SetGenerateReproducer(bool b) { + const uint32_t idx = ePropertyGenerateReproducer; + m_reproducer.SetGenerateReproducer(b); + return m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, b); +} + +FileSpec Debugger::GetReproducerPath() const { + const uint32_t idx = ePropertyReproducerPath; + return m_collection_sp->GetPropertyAtIndexAsFileSpec(nullptr, idx); +} + +void Debugger::SetReproducerPath(const FileSpec &p) { + const uint32_t idx = ePropertyReproducerPath; + m_collection_sp->SetPropertyAtIndexAsFileSpec(nullptr, idx, p); + + if (!GetGenerateReproducer()) { + m_reproducer.SetUseReproducer(static_cast(p)); + if (auto loader = m_reproducer.GetLoader()) + if (auto e = loader->LoadIndex(p)) + llvm::consumeError(std::move(e)); + } else if (auto generator = m_reproducer.GetGenerator()) { + // If the reproducer path is set *after* we've already enabled logging of a + // reproducer, we use the given path as the reproducer root. + generator->ChangeDirectory(p); + } +} + #pragma mark Debugger // const DebuggerPropertiesSP & Index: source/Host/common/HostInfoBase.cpp =================================================================== --- source/Host/common/HostInfoBase.cpp +++ source/Host/common/HostInfoBase.cpp @@ -194,6 +194,19 @@ return success ? g_fields->m_lldb_global_tmp_dir : FileSpec(); } +FileSpec HostInfoBase::GetReproducerTempDir() { + static llvm::once_flag g_once_flag; + static bool success = false; + llvm::call_once(g_once_flag, []() { + success = HostInfo::ComputeReproducerTempFileDirectory( + g_fields->m_lldb_global_tmp_dir); + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + LLDB_LOG(log, "reproducer temp dir -> `{0}`", + g_fields->m_lldb_global_tmp_dir); + }); + return success ? g_fields->m_lldb_global_tmp_dir : FileSpec(); +} + ArchSpec HostInfoBase::GetAugmentedArchSpec(llvm::StringRef triple) { if (triple.empty()) return ArchSpec(); @@ -275,6 +288,26 @@ return true; } +bool HostInfoBase::ComputeReproducerTempFileDirectory(FileSpec &file_spec) { + file_spec.Clear(); + + FileSpec temp_file_spec; + if (!HostInfo::ComputeTempFileBaseDirectory(temp_file_spec)) + return false; + + temp_file_spec.AppendPathComponent("reproducer"); + if (llvm::sys::fs::create_directory(temp_file_spec.GetPath())) + return false; + + std::string pid_str{llvm::to_string(Host::GetCurrentProcessID())}; + temp_file_spec.AppendPathComponent(pid_str); + if (llvm::sys::fs::create_directory(temp_file_spec.GetPath())) + return false; + + file_spec.GetDirectory().SetCString(temp_file_spec.GetCString()); + return true; +} + bool HostInfoBase::ComputeHeaderDirectory(FileSpec &file_spec) { // TODO(zturner): Figure out how to compute the header directory for all // platforms. Index: source/Plugins/Process/gdb-remote/CMakeLists.txt =================================================================== --- source/Plugins/Process/gdb-remote/CMakeLists.txt +++ source/Plugins/Process/gdb-remote/CMakeLists.txt @@ -15,6 +15,8 @@ GDBRemoteClientBase.cpp GDBRemoteCommunication.cpp GDBRemoteCommunicationClient.cpp + GDBRemoteCommunicationHistory.cpp + GDBRemoteCommunicationReplayServer.cpp GDBRemoteCommunicationServer.cpp GDBRemoteCommunicationServerCommon.cpp GDBRemoteCommunicationServerLLGS.cpp Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h @@ -19,15 +19,16 @@ #include // Other libraries and framework includes -// Project includes #include "lldb/Core/Communication.h" #include "lldb/Core/Listener.h" #include "lldb/Host/HostThread.h" #include "lldb/Utility/Args.h" #include "lldb/Utility/Predicate.h" +#include "lldb/Utility/StringExtractorGDBRemote.h" #include "lldb/lldb-public.h" -#include "lldb/Utility/StringExtractorGDBRemote.h" +// Project includes +#include "GDBRemoteCommunicationHistory.h" namespace lldb_private { namespace process_gdb_remote { @@ -140,86 +141,16 @@ // fork/exec to avoid having to connect/accept void DumpHistory(Stream &strm); + void SetHistoryStream(std::unique_ptr 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 m_packets; - uint32_t m_curr_idx; - uint32_t m_total_packet_count; - mutable bool m_dumped_to_log; - }; + static llvm::Error ConnectLocally(GDBRemoteCommunication &client, + GDBRemoteCommunication &server); +protected: std::chrono::seconds m_packet_timeout; uint32_t m_echo_number; LazyBool m_supports_qEcho; - History m_history; + GDBRemoteCommunicationHistory 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 @@ -228,6 +159,8 @@ CompressionType m_compression_type; PacketResult SendPacketNoLock(llvm::StringRef payload); + PacketResult SendRawPacketNoLock(llvm::StringRef payload, + bool skip_ack = false); PacketResult ReadPacket(StringExtractorGDBRemote &response, Timeout timeout, bool sync_on_timeout); Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp @@ -10,6 +10,7 @@ #include "GDBRemoteCommunication.h" // C Includes +#include #include #include #include @@ -24,6 +25,8 @@ #include "lldb/Host/Socket.h" #include "lldb/Host/StringConvert.h" #include "lldb/Host/ThreadLauncher.h" +#include "lldb/Host/common/TCPSocket.h" +#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h" #include "lldb/Target/Platform.h" #include "lldb/Target/Process.h" #include "lldb/Utility/FileSpec.h" @@ -54,78 +57,6 @@ 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 = llvm::get_threadid(); - } -} - -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 = llvm::get_threadid(); - } -} - -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 //---------------------------------------------------------------------- @@ -172,7 +103,8 @@ 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); + m_history.AddPacket(ch, GDBRemoteCommunicationHistory::ePacketTypeSend, + bytes_written); return bytes_written; } @@ -183,26 +115,31 @@ 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); + m_history.AddPacket(ch, GDBRemoteCommunicationHistory::ePacketTypeSend, + bytes_written); return bytes_written; } GDBRemoteCommunication::PacketResult GDBRemoteCommunication::SendPacketNoLock(llvm::StringRef payload) { - if (IsConnected()) { StreamString packet(0, 4, eByteOrderBig); - packet.PutChar('$'); packet.Write(payload.data(), payload.size()); packet.PutChar('#'); packet.PutHex8(CalculcateChecksum(payload)); + std::string packet_str = packet.GetString(); + return SendRawPacketNoLock(packet_str); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunication::SendRawPacketNoLock(llvm::StringRef packet, + bool skip_ack) { + if (IsConnected()) { Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PACKETS)); ConnectionStatus status = eConnectionStatusSuccess; - // TODO: Don't shimmy through a std::string, just use StringRef. - std::string packet_str = packet.GetString(); - const char *packet_data = packet_str.c_str(); - const size_t packet_length = packet.GetSize(); + const char *packet_data = packet.data(); + const size_t packet_length = packet.size(); size_t bytes_written = Write(packet_data, packet_length, status, NULL); if (log) { size_t binary_start_offset = 0; @@ -241,11 +178,12 @@ (int)packet_length, packet_data); } - m_history.AddPacket(packet.GetString(), packet_length, - History::ePacketTypeSend, bytes_written); + m_history.AddPacket(packet.str(), packet_length, + GDBRemoteCommunicationHistory::ePacketTypeSend, + bytes_written); if (bytes_written == packet_length) { - if (GetSendAcks()) + if (!skip_ack && GetSendAcks()) return GetAck(); else return PacketResult::Success; @@ -860,7 +798,8 @@ } } - m_history.AddPacket(m_bytes, total_length, History::ePacketTypeRecv, + m_history.AddPacket(m_bytes, total_length, + GDBRemoteCommunicationHistory::ePacketTypeRecv, total_length); // Clear packet_str in case there is some existing data in it. @@ -1304,6 +1243,43 @@ void GDBRemoteCommunication::DumpHistory(Stream &strm) { m_history.Dump(strm); } +void GDBRemoteCommunication::SetHistoryStream( + std::unique_ptr strm) { + m_history.SetStream(std::move(strm)); +}; + +llvm::Error +GDBRemoteCommunication::ConnectLocally(GDBRemoteCommunication &client, + GDBRemoteCommunication &server) { + const bool child_processes_inherit = false; + const int backlog = 5; + TCPSocket listen_socket(true, child_processes_inherit); + if (llvm::Error error = + listen_socket.Listen("127.0.0.1:0", backlog).ToError()) + return error; + + Socket *accept_socket; + std::future accept_status = std::async( + std::launch::async, [&] { return listen_socket.Accept(accept_socket); }); + + llvm::SmallString<32> remote_addr; + llvm::raw_svector_ostream(remote_addr) + << "connect://localhost:" << listen_socket.GetLocalPortNumber(); + + std::unique_ptr conn_up( + new ConnectionFileDescriptor()); + if (conn_up->Connect(remote_addr, nullptr) != lldb::eConnectionStatusSuccess) + return llvm::make_error("Unable to connect", + llvm::inconvertibleErrorCode()); + + client.SetConnection(conn_up.release()); + if (llvm::Error error = accept_status.get().ToError()) + return error; + + server.SetConnection(new ConnectionFileDescriptor(accept_socket)); + return llvm::Error::success(); +} + GDBRemoteCommunication::ScopedTimeout::ScopedTimeout( GDBRemoteCommunication &gdb_comm, std::chrono::seconds timeout) : m_gdb_comm(gdb_comm), m_timeout_modified(false) { Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationHistory.h =================================================================== --- /dev/null +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationHistory.h @@ -0,0 +1,158 @@ +//===-- GDBRemoteCommunicationHistory.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_GDBRemoteCommunicationHistory_h_ +#define liblldb_GDBRemoteCommunicationHistory_h_ + +#include +#include + +#include "lldb/lldb-public.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +namespace lldb_private { +namespace process_gdb_remote { + +/// The history keeps a circular buffer of GDB remote packets. The history is +/// used for logging and replaying GDB remote packets. +class GDBRemoteCommunicationHistory { +public: + friend llvm::yaml::MappingTraits; + + enum PacketType { ePacketTypeInvalid = 0, ePacketTypeSend, ePacketTypeRecv }; + + /// Entry in the ring buffer containing the packet data, its type, size and + /// index. Entries can be serialized to file. + struct Entry { + Entry() + : packet(), type(ePacketTypeInvalid), bytes_transmitted(0), + packet_idx(0), tid(LLDB_INVALID_THREAD_ID) {} + + void Clear() { + packet.data.clear(); + type = ePacketTypeInvalid; + bytes_transmitted = 0; + packet_idx = 0; + tid = LLDB_INVALID_THREAD_ID; + } + + struct BinaryData { + std::string data; + }; + + void Serialize(llvm::raw_ostream &strm) const; + + BinaryData packet; + PacketType type; + uint32_t bytes_transmitted; + uint32_t packet_idx; + lldb::tid_t tid; + }; + + GDBRemoteCommunicationHistory(uint32_t size = 0); + + ~GDBRemoteCommunicationHistory(); + + // 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; } + + void SetStream(std::unique_ptr strm) { + m_stream_up = std::move(strm); + } + +private: + 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 m_packets.empty() ? 0 : i % m_packets.size(); + } + + std::vector m_packets; + uint32_t m_curr_idx; + uint32_t m_total_packet_count; + mutable bool m_dumped_to_log; + std::unique_ptr m_stream_up; +}; + +} // namespace process_gdb_remote +} // namespace lldb_private + +LLVM_YAML_IS_DOCUMENT_LIST_VECTOR( + lldb_private::process_gdb_remote::GDBRemoteCommunicationHistory::Entry) + +namespace llvm { +namespace yaml { + +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &io, + lldb_private::process_gdb_remote:: + GDBRemoteCommunicationHistory::PacketType &value); +}; + +template <> +struct ScalarTraits { + static void output(const lldb_private::process_gdb_remote:: + GDBRemoteCommunicationHistory::Entry::BinaryData &, + void *, raw_ostream &); + + static StringRef + input(StringRef, void *, + lldb_private::process_gdb_remote::GDBRemoteCommunicationHistory::Entry:: + BinaryData &); + + static QuotingType mustQuote(StringRef S) { return QuotingType::None; } +}; + +template <> +struct MappingTraits< + lldb_private::process_gdb_remote::GDBRemoteCommunicationHistory::Entry> { + static void + mapping(IO &io, + lldb_private::process_gdb_remote::GDBRemoteCommunicationHistory::Entry + &Entry); + + static StringRef validate( + IO &io, + lldb_private::process_gdb_remote::GDBRemoteCommunicationHistory::Entry &); +}; + +} // namespace yaml +} // namespace llvm + +#endif // liblldb_GDBRemoteCommunicationHistory_h_ Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationHistory.cpp =================================================================== --- /dev/null +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationHistory.cpp @@ -0,0 +1,142 @@ +//===-- GDBRemoteCommunicationHistory.cpp -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "GDBRemoteCommunicationHistory.h" + +// Other libraries and framework includes +#include "lldb/Core/StreamFile.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/Log.h" + +using namespace llvm; +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_gdb_remote; + +void GDBRemoteCommunicationHistory::Entry::Serialize(raw_ostream &strm) const { + yaml::Output yout(strm); + yout << const_cast(*this); +} + +GDBRemoteCommunicationHistory::GDBRemoteCommunicationHistory(uint32_t size) + : m_packets(), m_curr_idx(0), m_total_packet_count(0), + m_dumped_to_log(false) { + if (size) + m_packets.resize(size); +} + +GDBRemoteCommunicationHistory::~GDBRemoteCommunicationHistory() {} + +void GDBRemoteCommunicationHistory::AddPacket(char packet_char, PacketType type, + uint32_t bytes_transmitted) { + const size_t size = m_packets.size(); + if (size == 0) + return; + + const uint32_t idx = GetNextIndex(); + m_packets[idx].packet.data.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 = llvm::get_threadid(); + if (m_stream_up && type == ePacketTypeRecv) + m_packets[idx].Serialize(*m_stream_up); +} + +void GDBRemoteCommunicationHistory::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) + return; + + const uint32_t idx = GetNextIndex(); + m_packets[idx].packet.data.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 = llvm::get_threadid(); + if (m_stream_up && type == ePacketTypeRecv) + m_packets[idx].Serialize(*m_stream_up); +} + +void GDBRemoteCommunicationHistory::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.data.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.data.c_str()); + } +} + +void GDBRemoteCommunicationHistory::Dump(Log *log) const { + if (!log || m_dumped_to_log) + return; + + 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.data.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.data.c_str()); + } +} + +void yaml::ScalarEnumerationTraits:: + enumeration(IO &io, GDBRemoteCommunicationHistory::PacketType &value) { + io.enumCase(value, "Invalid", + GDBRemoteCommunicationHistory::ePacketTypeInvalid); + io.enumCase(value, "Send", GDBRemoteCommunicationHistory::ePacketTypeSend); + io.enumCase(value, "Recv", GDBRemoteCommunicationHistory::ePacketTypeRecv); +} + +void yaml::ScalarTraits:: + output(const GDBRemoteCommunicationHistory::Entry::BinaryData &Val, void *, + raw_ostream &Out) { + Out << toHex(Val.data); +} + +StringRef +yaml::ScalarTraits::input( + StringRef Scalar, void *, + GDBRemoteCommunicationHistory::Entry::BinaryData &Val) { + Val.data = fromHex(Scalar); + return {}; +} + +void yaml::MappingTraits::mapping( + IO &io, GDBRemoteCommunicationHistory::Entry &Entry) { + io.mapRequired("packet", Entry.packet); + io.mapRequired("type", Entry.type); + io.mapRequired("bytes", Entry.bytes_transmitted); + io.mapRequired("index", Entry.packet_idx); + io.mapRequired("tid", Entry.tid); +} + +StringRef yaml::MappingTraits::validate( + IO &io, GDBRemoteCommunicationHistory::Entry &Entry) { + if (Entry.bytes_transmitted != Entry.packet.data.size()) + return "BinaryData size doesn't match bytes transmitted"; + + return {}; +} Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationReplayServer.h =================================================================== --- /dev/null +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationReplayServer.h @@ -0,0 +1,83 @@ +//===-- GDBRemoteCommunicationReplayServer.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_GDBRemoteCommunicationReplayServer_h_ +#define liblldb_GDBRemoteCommunicationReplayServer_h_ + +// Other libraries and framework includes +#include "GDBRemoteCommunication.h" +#include "GDBRemoteCommunicationHistory.h" + +// Project includes +#include "lldb/Core/Broadcaster.h" +#include "lldb/Host/HostThread.h" +#include "lldb/lldb-private-forward.h" +#include "llvm/Support/Error.h" + +// C Includes +// C++ Includes +#include +#include +#include + +class StringExtractorGDBRemote; + +namespace lldb_private { +namespace process_gdb_remote { + +class ProcessGDBRemote; + +/// Dummy GDB server that replays packets from the GDB Remote Communication +/// history. This is used to replay GDB packets. +class GDBRemoteCommunicationReplayServer : public GDBRemoteCommunication { +public: + GDBRemoteCommunicationReplayServer(); + + ~GDBRemoteCommunicationReplayServer() override; + + PacketResult GetPacketAndSendResponse(Timeout timeout, + Status &error, bool &interrupt, + bool &quit); + + bool HandshakeWithClient() { return GetAck() == PacketResult::Success; } + + llvm::Error LoadReplayHistory(const FileSpec &path); + + bool StartAsyncThread(); + void StopAsyncThread(); + +protected: + enum { + eBroadcastBitAsyncContinue = (1 << 0), + eBroadcastBitAsyncThreadShouldExit = (1 << 1), + }; + + static void ReceivePacket(GDBRemoteCommunicationReplayServer &server, + bool &done); + static lldb::thread_result_t AsyncThread(void *arg); + + /// Replay history with the oldest packet at the end. + std::vector m_packet_history; + + /// Server thread. + Broadcaster m_async_broadcaster; + lldb::ListenerSP m_async_listener_sp; + HostThread m_async_thread; + std::recursive_mutex m_async_thread_state_mutex; + + bool m_skip_acks; + +private: + DISALLOW_COPY_AND_ASSIGN(GDBRemoteCommunicationReplayServer); +}; + +} // namespace process_gdb_remote +} // namespace lldb_private + +#endif // liblldb_GDBRemoteCommunicationReplayServer_h_ Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationReplayServer.cpp =================================================================== --- /dev/null +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationReplayServer.cpp @@ -0,0 +1,204 @@ +//===-- GDBRemoteCommunicationReplayServer.cpp ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include + +#include "lldb/Host/Config.h" + +#include "GDBRemoteCommunicationReplayServer.h" +#include "ProcessGDBRemoteLog.h" + +// C Includes +// C++ Includes +#include + +// Project includes +#include "lldb/Core/Event.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StringExtractorGDBRemote.h" + +using namespace llvm; +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_gdb_remote; + +GDBRemoteCommunicationReplayServer::GDBRemoteCommunicationReplayServer() + : GDBRemoteCommunication("gdb-remote.server", + "gdb-remote.server.rx_packet"), + m_async_broadcaster(nullptr, "lldb.gdb-remote.server.async-broadcaster"), + m_async_listener_sp( + Listener::MakeListener("lldb.gdb-remote.server.async-listener")), + m_async_thread_state_mutex(), m_skip_acks(false) { + m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue, + "async thread continue"); + m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit, + "async thread should exit"); + + const uint32_t async_event_mask = + eBroadcastBitAsyncContinue | eBroadcastBitAsyncThreadShouldExit; + m_async_listener_sp->StartListeningForEvents(&m_async_broadcaster, + async_event_mask); +} + +GDBRemoteCommunicationReplayServer::~GDBRemoteCommunicationReplayServer() { + StopAsyncThread(); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationReplayServer::GetPacketAndSendResponse( + Timeout timeout, Status &error, bool &interrupt, bool &quit) { + StringExtractorGDBRemote packet; + PacketResult packet_result = WaitForPacketNoLock(packet, timeout, false); + + if (packet_result != PacketResult::Success) { + if (!IsConnected()) { + error.SetErrorString("lost connection"); + quit = true; + } else { + error.SetErrorString("timeout"); + } + return packet_result; + } + + m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue); + + if (m_skip_acks) { + const StringExtractorGDBRemote::ServerPacketType packet_type = + packet.GetServerPacketType(); + switch (packet_type) { + case StringExtractorGDBRemote::eServerPacketType_nack: + case StringExtractorGDBRemote::eServerPacketType_ack: + return PacketResult::Success; + default: + break; + } + } else if (packet.GetStringRef() == "QStartNoAckMode") { + m_skip_acks = true; + m_send_acks = false; + } + + while (!m_packet_history.empty()) { + // Pop last packet from the history. + GDBRemoteCommunicationHistory::Entry entry = m_packet_history.back(); + m_packet_history.pop_back(); + + // We only care about what we received from the server. Skip everything + // the client sent. + if (entry.type != GDBRemoteCommunicationHistory::ePacketTypeRecv) + continue; + + return SendRawPacketNoLock(entry.packet.data, true); + } + + quit = true; + + return packet_result; +} + +LLVM_YAML_IS_DOCUMENT_LIST_VECTOR( + std::vector< + lldb_private::process_gdb_remote::GDBRemoteCommunicationHistory::Entry>) + +llvm::Error +GDBRemoteCommunicationReplayServer::LoadReplayHistory(const FileSpec &path) { + auto error_or_file = MemoryBuffer::getFile(path.GetPath()); + if (auto err = error_or_file.getError()) + return errorCodeToError(err); + + yaml::Input yin((*error_or_file)->getBuffer()); + yin >> m_packet_history; + + if (auto err = yin.error()) + return errorCodeToError(err); + + // We want to manipulate the vector like a stack so we need to reverse the + // order of the packets to have the oldest on at the back. + std::reverse(m_packet_history.begin(), m_packet_history.end()); + + return Error::success(); +} + +bool GDBRemoteCommunicationReplayServer::StartAsyncThread() { + std::lock_guard guard(m_async_thread_state_mutex); + if (!m_async_thread.IsJoinable()) { + // Create a thread that watches our internal state and controls which + // events make it to clients (into the DCProcess event queue). + m_async_thread = ThreadLauncher::LaunchThread( + "", + GDBRemoteCommunicationReplayServer::AsyncThread, this, nullptr); + } + + // Wait for handshake. + m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue); + + return m_async_thread.IsJoinable(); +} + +void GDBRemoteCommunicationReplayServer::StopAsyncThread() { + std::lock_guard guard(m_async_thread_state_mutex); + + if (!m_async_thread.IsJoinable()) + return; + + // Request thread to stop. + m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncThreadShouldExit); + + // Disconnect client. + Disconnect(); + + // Stop the thread. + m_async_thread.Join(nullptr); + m_async_thread.Reset(); +} + +void GDBRemoteCommunicationReplayServer::ReceivePacket( + GDBRemoteCommunicationReplayServer &server, bool &done) { + Status error; + bool interrupt; + auto packet_result = server.GetPacketAndSendResponse(std::chrono::seconds(1), + error, interrupt, done); + if (packet_result != GDBRemoteCommunication::PacketResult::Success && + packet_result != + GDBRemoteCommunication::PacketResult::ErrorReplyTimeout) { + done = true; + } else { + server.m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue); + } +} + +thread_result_t GDBRemoteCommunicationReplayServer::AsyncThread(void *arg) { + GDBRemoteCommunicationReplayServer *server = + (GDBRemoteCommunicationReplayServer *)arg; + + EventSP event_sp; + bool done = false; + + while (true) { + if (server->m_async_listener_sp->GetEvent(event_sp, llvm::None)) { + const uint32_t event_type = event_sp->GetType(); + if (event_sp->BroadcasterIs(&server->m_async_broadcaster)) { + switch (event_type) { + case eBroadcastBitAsyncContinue: + ReceivePacket(*server, done); + if (done) + return nullptr; + break; + case eBroadcastBitAsyncThreadShouldExit: + default: + return nullptr; + } + } + } + } + + return nullptr; +} Index: source/Plugins/Process/gdb-remote/ProcessGDBRemote.h =================================================================== --- source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -36,11 +36,15 @@ #include "lldb/lldb-private-forward.h" #include "GDBRemoteCommunicationClient.h" +#include "GDBRemoteCommunicationReplayServer.h" #include "GDBRemoteRegisterContext.h" #include "llvm/ADT/DenseMap.h" namespace lldb_private { +namespace repro { +class Loader; +} namespace process_gdb_remote { class ThreadGDBRemote; @@ -264,6 +268,7 @@ }; GDBRemoteCommunicationClient m_gdb_comm; + GDBRemoteCommunicationReplayServer m_gdb_replay_server; std::atomic m_debugserver_pid; std::vector m_stop_packet_stack; // The stop packet // stack replaces @@ -304,6 +309,7 @@ int64_t m_breakpoint_pc_offset; lldb::tid_t m_initial_tid; // The initial thread ID, given by stub on attach + bool m_replay_mode; bool m_allow_flash_writes; using FlashRangeVector = lldb_private::RangeVector; using FlashRange = FlashRangeVector::Entry; @@ -331,6 +337,8 @@ bool UpdateThreadList(ThreadList &old_thread_list, ThreadList &new_thread_list) override; + Status ConnectToReplayServer(repro::Loader *loader); + Status EstablishConnectionIfNeeded(const ProcessInfo &process_info); Status LaunchAndConnectToDebugserver(const ProcessInfo &process_info); Index: source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -67,6 +67,7 @@ #include "lldb/Utility/Args.h" #include "lldb/Utility/CleanUp.h" #include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Reproducer.h" #include "lldb/Utility/State.h" #include "lldb/Utility/StreamString.h" #include "lldb/Utility/Timer.h" @@ -88,6 +89,7 @@ #include "llvm/Support/raw_ostream.h" #define DEBUGSERVER_BASENAME "debugserver" +using namespace llvm; using namespace lldb; using namespace lldb_private; using namespace lldb_private::process_gdb_remote; @@ -106,7 +108,7 @@ if (error.Success()) ((ProcessGDBRemote *)p)->GetGDBRemote().DumpHistory(strm); } -} +} // namespace lldb namespace { @@ -158,7 +160,21 @@ return g_settings_sp; } -} // anonymous namespace end +class ProcessGDBRemoteProvider : public repro::Provider { +public: + ProcessGDBRemoteProvider(FileSpec directory) : Provider(directory) { + m_info.name = "gdb-remote"; + m_info.files.push_back("gdb-remote.yaml"); + } + + FileSpec GetHistoryFile() { + FileSpec history_file = GetDirectory(); + history_file.AppendPathComponent("gdb-remote.yaml"); + return history_file; + } +}; + +} // namespace // TODO Randomly assigning a port is unsafe. We should get an unused // ephemeral port from the kernel and make sure we reserve it before passing it @@ -259,8 +275,8 @@ m_addr_to_mmap_size(), m_thread_create_bp_sp(), m_waiting_for_attach(false), m_destroy_tried_resuming(false), m_command_sp(), m_breakpoint_pc_offset(0), - m_initial_tid(LLDB_INVALID_THREAD_ID), m_allow_flash_writes(false), - m_erased_flash_ranges() { + m_initial_tid(LLDB_INVALID_THREAD_ID), m_replay_mode(false), + m_allow_flash_writes(false), m_erased_flash_ranges() { m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadShouldExit, "async thread should exit"); m_async_broadcaster.SetEventName(eBroadcastBitAsyncContinue, @@ -268,6 +284,19 @@ m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadDidExit, "async thread did exit"); + repro::Generator *generator = + GetTarget().GetDebugger().GetReproducer().GetGenerator(); + if (generator) { + ProcessGDBRemoteProvider &provider = + generator->CreateProvider(); + + std::error_code EC; + auto file_out = make_unique( + provider.GetHistoryFile().GetPath(), EC, sys::fs::OpenFlags::F_None); + if (!EC) + m_gdb_comm.SetHistoryStream(std::move(file_out)); + } + Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_ASYNC)); const uint32_t async_event_mask = @@ -1058,9 +1087,10 @@ if (log) log->Printf("ProcessGDBRemote::%s gdb-remote had process architecture, " "using %s %s", - __FUNCTION__, process_arch.GetArchitectureName() - ? process_arch.GetArchitectureName() - : "", + __FUNCTION__, + process_arch.GetArchitectureName() + ? process_arch.GetArchitectureName() + : "", process_arch.GetTriple().getTriple().c_str() ? process_arch.GetTriple().getTriple().c_str() : ""); @@ -1069,9 +1099,10 @@ if (log) log->Printf("ProcessGDBRemote::%s gdb-remote did not have process " "architecture, using gdb-remote host architecture %s %s", - __FUNCTION__, process_arch.GetArchitectureName() - ? process_arch.GetArchitectureName() - : "", + __FUNCTION__, + process_arch.GetArchitectureName() + ? process_arch.GetArchitectureName() + : "", process_arch.GetTriple().getTriple().c_str() ? process_arch.GetTriple().getTriple().c_str() : ""); @@ -1083,9 +1114,10 @@ if (log) log->Printf( "ProcessGDBRemote::%s analyzing target arch, currently %s %s", - __FUNCTION__, target_arch.GetArchitectureName() - ? target_arch.GetArchitectureName() - : "", + __FUNCTION__, + target_arch.GetArchitectureName() + ? target_arch.GetArchitectureName() + : "", target_arch.GetTriple().getTriple().c_str() ? target_arch.GetTriple().getTriple().c_str() : ""); @@ -1105,9 +1137,10 @@ if (log) log->Printf("ProcessGDBRemote::%s remote process is ARM/Apple, " "setting target arch to %s %s", - __FUNCTION__, process_arch.GetArchitectureName() - ? process_arch.GetArchitectureName() - : "", + __FUNCTION__, + process_arch.GetArchitectureName() + ? process_arch.GetArchitectureName() + : "", process_arch.GetTriple().getTriple().c_str() ? process_arch.GetTriple().getTriple().c_str() : ""); @@ -1135,9 +1168,10 @@ if (log) log->Printf("ProcessGDBRemote::%s final target arch after " "adjustments for remote architecture: %s %s", - __FUNCTION__, target_arch.GetArchitectureName() - ? target_arch.GetArchitectureName() - : "", + __FUNCTION__, + target_arch.GetArchitectureName() + ? target_arch.GetArchitectureName() + : "", target_arch.GetTriple().getTriple().c_str() ? target_arch.GetTriple().getTriple().c_str() : ""); @@ -3378,6 +3412,43 @@ return error; } +Status ProcessGDBRemote::ConnectToReplayServer(repro::Loader *loader) { + if (!loader) + return Status("No loader provided."); + + auto provider_info = loader->GetProviderInfo("gdb-remote"); + if (!provider_info) + return Status("No provider for gdb-remote."); + + if (provider_info->files.empty()) + return Status("Provider for gdb-remote contains no files."); + + // Construct replay history path. + FileSpec history_file = loader->GetDirectory(); + history_file.AppendPathComponent(provider_info->files.front()); + + // Enable replay mode. + m_replay_mode = true; + + // Load replay history. + if (auto error = m_gdb_replay_server.LoadReplayHistory(history_file)) + return Status("Unable to load replay history"); + + // Make a local connection. + if (auto error = GDBRemoteCommunication::ConnectLocally(m_gdb_comm, + m_gdb_replay_server)) + return Status("Unable to connect to replay server"); + + // Start server thread. + m_gdb_replay_server.StartAsyncThread(); + + // Start client thread. + StartAsyncThread(); + + // Do the usual setup. + return ConnectToDebugserver(""); +} + Status ProcessGDBRemote::EstablishConnectionIfNeeded(const ProcessInfo &process_info) { // Make sure we aren't already connected? @@ -3388,6 +3459,10 @@ if (platform_sp && !platform_sp->IsHost()) return Status("Lost debug server connection"); + if (repro::Loader *loader = + GetTarget().GetDebugger().GetReproducer().GetLoader()) + return ConnectToReplayServer(loader); + auto error = LaunchAndConnectToDebugserver(process_info); if (error.Fail()) { const char *error_string = error.AsCString(); @@ -3497,7 +3572,7 @@ bool exited, // True if the process did exit int signo, // Zero for no signal int exit_status // Exit value of process if signal is zero - ) { +) { // "debugserver_pid" argument passed in is the process ID for debugserver // that we are tracking... Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); @@ -4269,8 +4344,9 @@ return false; feature_node.ForEachChildElementWithName( - "reg", [&target_info, &dyn_reg_info, &cur_reg_num, ®_offset, - &abi_sp](const XMLNode ®_node) -> bool { + "reg", + [&target_info, &dyn_reg_info, &cur_reg_num, ®_offset, + &abi_sp](const XMLNode ®_node) -> bool { std::string gdb_group; std::string gdb_type; ConstString reg_name; @@ -4432,7 +4508,7 @@ return true; } -} // namespace {} +} // namespace // query the target of gdb-remote for extended target information return: // 'true' on success @@ -4506,11 +4582,11 @@ // If the target.xml includes an architecture entry like // i386:x86-64 (seen from VMWare ESXi) - // arm (seen from Segger JLink on unspecified arm board) + // arm (seen from Segger JLink on + // unspecified arm board) // use that if we don't have anything better. if (!arch_to_use.IsValid() && !target_info.arch.empty()) { - if (target_info.arch == "i386:x86-64") - { + if (target_info.arch == "i386:x86-64") { // We don't have any information about vendor or OS. arch_to_use.SetTriple("x86_64--"); GetTarget().MergeArchitecture(arch_to_use); Index: source/Utility/CMakeLists.txt =================================================================== --- source/Utility/CMakeLists.txt +++ source/Utility/CMakeLists.txt @@ -61,6 +61,7 @@ Range.cpp RegisterValue.cpp RegularExpression.cpp + Reproducer.cpp Scalar.cpp SelectHelper.cpp SharingPtr.cpp Index: source/Utility/Reproducer.cpp =================================================================== --- /dev/null +++ source/Utility/Reproducer.cpp @@ -0,0 +1,148 @@ +//===-- Reproducer.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Utility/Reproducer.h" +#include "lldb/Host/HostInfo.h" + +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lldb_private; +using namespace lldb_private::repro; +using namespace llvm; +using namespace llvm::yaml; + +Generator *Reproducer::GetGenerator() { + if (m_generate_reproducer) + return &m_generator; + return nullptr; +} + +Loader *Reproducer::GetLoader() { + if (m_use_reproducer) + return &m_loader; + return nullptr; +} + +void Reproducer::SetGenerateReproducer(bool value) { + assert((!value || !m_use_reproducer) && + "Cannot generate reproducer when using one."); + m_generate_reproducer = value; + m_generator.SetEnabled(value); +} + +void Reproducer::SetUseReproducer(bool value) { + assert((!value || !m_generate_reproducer) && + "Cannot use reproducer when generating one."); + m_use_reproducer = value; +} + +Generator::Generator() : m_enabled(false), m_done(false) { + m_directory = HostInfo::GetReproducerTempDir(); +} + +Generator::~Generator() { + if (m_done) + return; + if (m_enabled) + Keep(); + else + Discard(); +} + +Provider &Generator::Register(std::unique_ptr provider) { + std::lock_guard lock(m_providers_mutex); + + AddProviderToIndex(provider->GetInfo()); + + m_providers.push_back(std::move(provider)); + return *m_providers.back(); +} + +void Generator::Keep() { + assert(!m_done); + m_done = true; + + if (!m_enabled) + return; + + for (auto &provider : m_providers) + provider->Keep(); + + errs() << "Reproducer written to '" << m_directory.GetPath() << "'\n"; +} + +void Generator::Discard() { + assert(!m_done); + m_done = true; + + if (!m_enabled) + return; + + for (auto &provider : m_providers) + provider->Discard(); + + llvm::sys::fs::remove_directories(m_directory.GetPath()); +} + +void Generator::ChangeDirectory(const FileSpec &directory) { + assert(m_providers.empty() && "Changing the directory after providers have " + "been registered would invalidate the index."); + m_directory = directory; +} + +void Generator::AddProviderToIndex(const ProviderInfo &provider_info) { + FileSpec index = m_directory; + index.AppendPathComponent("index.yaml"); + + std::error_code EC; + auto strm = make_unique(index.GetPath(), EC, + sys::fs::OpenFlags::F_None); + yaml::Output yout(*strm); + yout << const_cast(provider_info); +} + +Loader::Loader() : m_loaded(false) {} + +llvm::Error Loader::LoadIndex(const FileSpec &directory) { + if (m_loaded) + return llvm::Error::success(); + + FileSpec index = directory; + index.AppendPathComponent("index.yaml"); + + auto error_or_file = MemoryBuffer::getFile(index.GetPath()); + if (auto err = error_or_file.getError()) + return errorCodeToError(err); + + std::vector provider_info; + yaml::Input yin((*error_or_file)->getBuffer()); + yin >> provider_info; + + if (auto err = yin.error()) + return errorCodeToError(err); + + for (auto &info : provider_info) + m_provider_info[info.name] = info; + + m_directory = directory; + m_loaded = true; + + return llvm::Error::success(); +} + +llvm::Optional Loader::GetProviderInfo(StringRef name) { + assert(m_loaded); + + auto it = m_provider_info.find(name); + if (it == m_provider_info.end()) + return llvm::None; + + return it->second; +} Index: tools/driver/Driver.cpp =================================================================== --- tools/driver/Driver.cpp +++ tools/driver/Driver.cpp @@ -117,19 +117,23 @@ "Tells the debugger to execute this one-line lldb command after any file " "provided on the command line has been loaded."}, {LLDB_3_TO_5, false, "source-before-file", 'S', required_argument, 0, - eArgTypeFilename, "Tells the debugger to read in and execute the lldb " - "commands in the given file, before any file provided " - "on the command line has been loaded."}, + eArgTypeFilename, + "Tells the debugger to read in and execute the lldb " + "commands in the given file, before any file provided " + "on the command line has been loaded."}, {LLDB_3_TO_5, false, "one-line-before-file", 'O', required_argument, 0, - eArgTypeNone, "Tells the debugger to execute this one-line lldb command " - "before any file provided on the command line has been " - "loaded."}, + eArgTypeNone, + "Tells the debugger to execute this one-line lldb command " + "before any file provided on the command line has been " + "loaded."}, {LLDB_3_TO_5, false, "one-line-on-crash", 'k', required_argument, 0, - eArgTypeNone, "When in batch mode, tells the debugger to execute this " - "one-line lldb command if the target crashes."}, + eArgTypeNone, + "When in batch mode, tells the debugger to execute this " + "one-line lldb command if the target crashes."}, {LLDB_3_TO_5, false, "source-on-crash", 'K', required_argument, 0, - eArgTypeFilename, "When in batch mode, tells the debugger to source this " - "file of lldb commands if the target crashes."}, + eArgTypeFilename, + "When in batch mode, tells the debugger to source this " + "file of lldb commands if the target crashes."}, {LLDB_3_TO_5, false, "source-quietly", 'Q', no_argument, 0, eArgTypeNone, "Tells the debugger to execute this one-line lldb command before any file " "provided on the command line has been loaded."}, @@ -156,6 +160,9 @@ "extensions have been implemented."}, {LLDB_3_TO_5, false, "debug", 'd', no_argument, 0, eArgTypeNone, "Tells the debugger to print out extra information for debugging itself."}, + {LLDB_3_TO_5, false, "reproducer", 'z', required_argument, 0, + eArgTypeFilename, + "Tells the debugger to use the fullpath to as a reproducer."}, {LLDB_OPT_SET_7, true, "repl", 'r', optional_argument, 0, eArgTypeNone, "Runs lldb in REPL mode with a stub process."}, {LLDB_OPT_SET_7, true, "repl-language", 'R', required_argument, 0, @@ -751,6 +758,16 @@ m_option_data.m_debug_mode = true; break; + case 'z': { + SBFileSpec file(optarg); + if (file.Exists()) { + m_debugger.SetReproducerPath(optarg); + } else + error.SetErrorStringWithFormat("file specified in --reproducer " + "(-z) option doesn't exist: '%s'", + optarg); + } break; + case 'Q': m_option_data.m_source_quietly = true; break; Index: unittests/Process/gdb-remote/GDBRemoteClientBaseTest.cpp =================================================================== --- unittests/Process/gdb-remote/GDBRemoteClientBaseTest.cpp +++ unittests/Process/gdb-remote/GDBRemoteClientBaseTest.cpp @@ -48,7 +48,8 @@ class GDBRemoteClientBaseTest : public GDBRemoteTest { public: void SetUp() override { - ASSERT_THAT_ERROR(Connect(client, server), llvm::Succeeded()); + ASSERT_THAT_ERROR(GDBRemoteCommunication::ConnectLocally(client, server), + llvm::Succeeded()); ASSERT_EQ(TestClient::eBroadcastBitRunPacketSent, listener_sp->StartListeningForEvents( &client, TestClient::eBroadcastBitRunPacketSent)); Index: unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp =================================================================== --- unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -63,7 +63,8 @@ class GDBRemoteCommunicationClientTest : public GDBRemoteTest { public: void SetUp() override { - ASSERT_THAT_ERROR(Connect(client, server), llvm::Succeeded()); + ASSERT_THAT_ERROR(GDBRemoteCommunication::ConnectLocally(client, server), + llvm::Succeeded()); } protected: Index: unittests/Process/gdb-remote/GDBRemoteCommunicationTest.cpp =================================================================== --- unittests/Process/gdb-remote/GDBRemoteCommunicationTest.cpp +++ unittests/Process/gdb-remote/GDBRemoteCommunicationTest.cpp @@ -30,7 +30,8 @@ class GDBRemoteCommunicationTest : public GDBRemoteTest { public: void SetUp() override { - ASSERT_THAT_ERROR(Connect(client, server), llvm::Succeeded()); + ASSERT_THAT_ERROR(GDBRemoteCommunication::ConnectLocally(client, server), + llvm::Succeeded()); } protected: Index: unittests/Process/gdb-remote/GDBRemoteTestUtils.h =================================================================== --- unittests/Process/gdb-remote/GDBRemoteTestUtils.h +++ unittests/Process/gdb-remote/GDBRemoteTestUtils.h @@ -20,10 +20,6 @@ public: static void SetUpTestCase(); static void TearDownTestCase(); - -protected: - llvm::Error Connect(GDBRemoteCommunication &client, - GDBRemoteCommunication &server); }; struct MockServer : public GDBRemoteCommunicationServer { Index: unittests/Process/gdb-remote/GDBRemoteTestUtils.cpp =================================================================== --- unittests/Process/gdb-remote/GDBRemoteTestUtils.cpp +++ unittests/Process/gdb-remote/GDBRemoteTestUtils.cpp @@ -9,11 +9,6 @@ #include "GDBRemoteTestUtils.h" -#include "lldb/Host/common/TCPSocket.h" -#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h" - -#include - namespace lldb_private { namespace process_gdb_remote { @@ -30,34 +25,5 @@ #endif } -llvm::Error GDBRemoteTest::Connect(GDBRemoteCommunication &client, - GDBRemoteCommunication &server) { - bool child_processes_inherit = false; - TCPSocket listen_socket(true, child_processes_inherit); - if (llvm::Error error = listen_socket.Listen("127.0.0.1:0", 5).ToError()) - return error; - - Socket *accept_socket; - std::future accept_status = std::async( - std::launch::async, [&] { return listen_socket.Accept(accept_socket); }); - - llvm::SmallString<32> remote_addr; - llvm::raw_svector_ostream(remote_addr) - << "connect://localhost:" << listen_socket.GetLocalPortNumber(); - - std::unique_ptr conn_up( - new ConnectionFileDescriptor()); - if (conn_up->Connect(remote_addr, nullptr) != lldb::eConnectionStatusSuccess) - return llvm::make_error("Unable to connect", - llvm::inconvertibleErrorCode()); - - client.SetConnection(conn_up.release()); - if (llvm::Error error = accept_status.get().ToError()) - return error; - - server.SetConnection(new ConnectionFileDescriptor(accept_socket)); - return llvm::Error::success(); -} - } // namespace process_gdb_remote } // namespace lldb_private