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 @@ -265,6 +265,11 @@ void SetPrompt(llvm::StringRef p); void SetPrompt(const char *) = delete; + llvm::StringRef GetReproducerPath() const; + + void SetReproducerPath(llvm::StringRef p); + void SetReproducerPath(const char *) = delete; + bool GetUseExternalEditor() const; bool SetUseExternalEditor(bool use_external_editor_p); Index: include/lldb/Host/HostInfoBase.h =================================================================== --- include/lldb/Host/HostInfoBase.h +++ include/lldb/Host/HostInfoBase.h @@ -93,6 +93,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 @@ -105,6 +111,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,173 @@ +//===-- 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 "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; } + llvm::StringRef 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(llvm::StringRef directory) : m_directory(directory.str()) {} + + /// Every provider keeps track of its own files. + ProviderInfo m_info; + +private: + /// Every provider knows where to dump its potential files. + std::string 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(llvm::StringRef directory); + llvm::StringRef GetDirectory() const; + +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. + std::string 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(llvm::StringRef directory); + + llvm::StringRef GetDirectory() { return m_directory; } + +private: + llvm::StringMap m_provider_info; + std::string m_directory; + bool m_loaded; +}; + +/// The reproducer enables clients to obtain access to the Generator and +/// Loader. +class Reproducer final { + +public: + static Reproducer &Instance(); + + Generator *GetGenerator(); + Loader *GetLoader(); + + const Generator *GetGenerator() const; + const Loader *GetLoader() const; + + llvm::Error SetGenerateReproducer(bool value); + llvm::Error SetReplayReproducer(bool value); + + llvm::Error SetReproducerPath(llvm::StringRef path); + llvm::StringRef GetReproducerPath() const; + +private: + Generator m_generator; + Loader m_loader; + + bool m_generate_reproducer = false; + bool m_replay_reproducer = false; + + mutable std::mutex m_mutex; +}; + +} // 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,49 @@ +""" +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__) + NO_DEBUG_INFO_TESTCASE = True + + def test(self): + """Test record and replay of gdb-remote packets.""" + self.build() + + # Create temp directory for the reproducer. + exe = self.getBuildArtifact("a.out") + + # First capture a regular debugging session. + self.runCmd("reproducer capture enable") + + reproducer_path = self.dbg.GetReproducerPath() + + self.runCmd("file {}".format(exe)) + self.runCmd("breakpoint set -f main.c -l 13") + self.runCmd("run") + self.runCmd("bt") + self.runCmd("cont") + + # Generate the reproducer and stop capturing. + self.runCmd("reproducer generate") + self.runCmd("reproducer capture disable") + + # Replay the session from the reproducer. + self.runCmd("reproducer replay {}".format(reproducer_path)) + + # We have to issue the same commands. + self.runCmd("file {}".format(exe)) + self.runCmd("breakpoint set -f main.c -l 13") + self.runCmd("run") + self.runCmd("bt") + self.expect("cont") 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: scripts/interface/SBDebugger.i =================================================================== --- scripts/interface/SBDebugger.i +++ scripts/interface/SBDebugger.i @@ -28,7 +28,7 @@ # Create a new debugger instance debugger = lldb.SBDebugger.Create() -# When we step or continue, don't return from the function until the process +# When we step or continue, don't return from the function until the process # stops. We do this by setting the async mode to false. debugger.SetAsync (False) @@ -46,7 +46,7 @@ # Launch the process. Since we specified synchronous mode, we won't return # from this function until we hit the breakpoint at main process = target.LaunchSimple (None, None, os.getcwd()) - + # Make sure the launch went ok if process: # Print some simple process info @@ -122,10 +122,10 @@ static void Initialize(); - + static void Terminate(); - + static lldb::SBDebugger Create(); @@ -155,8 +155,8 @@ void SetAsync (bool b); - - bool + + bool GetAsync (); void @@ -248,7 +248,7 @@ lldb::SBPlatform GetSelectedPlatform(); - + void SetSelectedPlatform(lldb::SBPlatform &platform); @@ -287,7 +287,7 @@ // SBPlatform from this class. lldb::SBError SetCurrentPlatform (const char *platform_name); - + bool SetCurrentPlatformSDKRoot (const char *sysroot); @@ -295,8 +295,8 @@ // an interface to the Set/Get UseExternalEditor. bool SetUseExternalEditor (bool input); - - bool + + bool GetUseExternalEditor (); bool @@ -342,7 +342,7 @@ void DispatchInputEndOfFile (); - + const char * GetInstanceName (); @@ -366,14 +366,20 @@ lldb::user_id_t GetID (); - + const char * GetPrompt() const; void SetPrompt (const char *prompt); - - lldb::ScriptLanguage + + const char * + GetReproducerPath() const; + + void + SetReproducerPath (const char *path); + + lldb::ScriptLanguage GetScriptLanguage() const; void @@ -381,31 +387,31 @@ bool GetCloseInputOnEOF () const; - + void SetCloseInputOnEOF (bool b); - + lldb::SBTypeCategory GetCategory (const char* category_name); - + SBTypeCategory GetCategory (lldb::LanguageType lang_type); - + lldb::SBTypeCategory CreateCategory (const char* category_name); - + bool DeleteCategory (const char* category_name); - + uint32_t GetNumCategories (); - + lldb::SBTypeCategory GetCategoryAtIndex (uint32_t); - + lldb::SBTypeCategory GetDefaultCategory(); - + lldb::SBTypeFormat GetFormatForType (lldb::SBTypeNameSpecifier); @@ -425,7 +431,7 @@ int &num_errors, bool &quit_requested, bool &stopped_for_crash); - + lldb::SBError RunREPL (lldb::LanguageType language, const char *repl_options); }; // class SBDebugger Index: source/API/SBDebugger.cpp =================================================================== --- source/API/SBDebugger.cpp +++ source/API/SBDebugger.cpp @@ -1055,6 +1055,17 @@ m_opaque_sp->SetPrompt(llvm::StringRef::withNullAsEmpty(prompt)); } +const char *SBDebugger::GetReproducerPath() const { + return (m_opaque_sp + ? ConstString(m_opaque_sp->GetReproducerPath()).GetCString() + : nullptr); +} + +void SBDebugger::SetReproducerPath(const char *p) { + if (m_opaque_sp) + m_opaque_sp->SetReproducerPath(llvm::StringRef::withNullAsEmpty(p)); +} + ScriptLanguage SBDebugger::GetScriptLanguage() const { return (m_opaque_sp ? m_opaque_sp->GetScriptLanguage() : eScriptLanguageNone); } Index: source/Commands/CMakeLists.txt =================================================================== --- source/Commands/CMakeLists.txt +++ source/Commands/CMakeLists.txt @@ -18,6 +18,7 @@ CommandObjectProcess.cpp CommandObjectQuit.cpp CommandObjectRegister.cpp + CommandObjectReproducer.cpp CommandObjectSettings.cpp CommandObjectSource.cpp CommandObjectStats.cpp Index: source/Commands/CommandObjectReproducer.h =================================================================== --- /dev/null +++ source/Commands/CommandObjectReproducer.h @@ -0,0 +1,31 @@ +//===-- CommandObjectReproducer.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_CommandObjectReproducer_h_ +#define liblldb_CommandObjectReproducer_h_ + +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/Options.h" + +namespace lldb_private { + +//------------------------------------------------------------------------- +// CommandObjectReproducer +//------------------------------------------------------------------------- + +class CommandObjectReproducer : public CommandObjectMultiword { +public: + CommandObjectReproducer(CommandInterpreter &interpreter); + + ~CommandObjectReproducer() override; +}; + +} // namespace lldb_private + +#endif // liblldb_CommandObjectReproducer_h_ Index: source/Commands/CommandObjectReproducer.cpp =================================================================== --- /dev/null +++ source/Commands/CommandObjectReproducer.cpp @@ -0,0 +1,206 @@ +//===-- CommandObjectReproducer.cpp -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CommandObjectReproducer.h" + +#include "lldb/Utility/Reproducer.h" + +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupBoolean.h" + +using namespace lldb; +using namespace lldb_private; + +static void AppendErrorToResult(llvm::Error e, CommandReturnObject &result) { + std::string error_str = llvm::toString(std::move(e)); + result.AppendErrorWithFormat("%s", error_str.c_str()); +} + +class CommandObjectReproducerCaptureEnable : public CommandObjectParsed { +public: + CommandObjectReproducerCaptureEnable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "reproducer capture enable", + "Enable gathering information for reproducer", + nullptr) {} + + ~CommandObjectReproducerCaptureEnable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (!command.empty()) { + result.AppendErrorWithFormat("'%s' takes no arguments", + m_cmd_name.c_str()); + return false; + } + + auto &r = repro::Reproducer::Instance(); + if (auto e = r.SetGenerateReproducer(true)) { + AppendErrorToResult(std::move(e), result); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } +}; + +class CommandObjectReproducerCaptureDisable : public CommandObjectParsed { +public: + CommandObjectReproducerCaptureDisable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "reproducer capture enable", + "Disable gathering information for reproducer", + nullptr) {} + + ~CommandObjectReproducerCaptureDisable() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (!command.empty()) { + result.AppendErrorWithFormat("'%s' takes no arguments", + m_cmd_name.c_str()); + return false; + } + + auto &r = repro::Reproducer::Instance(); + if (auto e = r.SetGenerateReproducer(false)) { + AppendErrorToResult(std::move(e), result); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } +}; + +class CommandObjectReproducerGenerate : public CommandObjectParsed { +public: + CommandObjectReproducerGenerate(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "reproducer generate", + "Generate reproducer on disk.", nullptr) {} + + ~CommandObjectReproducerGenerate() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (!command.empty()) { + result.AppendErrorWithFormat("'%s' takes no arguments", + m_cmd_name.c_str()); + return false; + } + + auto &r = repro::Reproducer::Instance(); + if (auto generator = r.GetGenerator()) { + generator->Keep(); + } else { + result.AppendErrorWithFormat("Unable to get the reproducer generator"); + return false; + } + + result.GetOutputStream() + << "Reproducer written to '" << r.GetReproducerPath() << "'\n"; + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } +}; + +class CommandObjectReproducerReplay : public CommandObjectParsed { +public: + CommandObjectReproducerReplay(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "reproducer capture", + "Enable or disable gathering of information needed " + "to generate a reproducer.", + nullptr) { + CommandArgumentEntry arg1; + CommandArgumentData path_arg; + + // Define the first (and only) variant of this arg. + path_arg.arg_type = eArgTypePath; + path_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(path_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectReproducerReplay() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (command.empty()) { + result.AppendErrorWithFormat( + "'%s' takes a single argument: the reproducer path", + m_cmd_name.c_str()); + return false; + } + + auto &r = repro::Reproducer::Instance(); + + if (auto e = r.SetReplayReproducer(true)) { + std::string error_str = llvm::toString(std::move(e)); + result.AppendErrorWithFormat("%s", error_str.c_str()); + return false; + } + + if (auto loader = r.GetLoader()) { + const char *repro_path = command.GetArgumentAtIndex(0); + if (auto e = loader->LoadIndex(repro_path)) { + std::string error_str = llvm::toString(std::move(e)); + result.AppendErrorWithFormat("Unable to load reproducer: %s", + error_str.c_str()); + return false; + } + } else { + result.AppendErrorWithFormat("Unable to get the reproducer loader"); + return false; + } + + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); + } +}; + +class CommandObjectReproducerCapture : public CommandObjectMultiword { +private: +public: + CommandObjectReproducerCapture(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "reproducer capture", + "Manage gathering of information needed to generate a reproducer.", + NULL) { + LoadSubCommand( + "enable", + CommandObjectSP(new CommandObjectReproducerCaptureEnable(interpreter))); + LoadSubCommand("disable", + CommandObjectSP( + new CommandObjectReproducerCaptureDisable(interpreter))); + } + + ~CommandObjectReproducerCapture() {} +}; + +CommandObjectReproducer::CommandObjectReproducer( + CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "reproducer", + "Commands controlling LLDB reproducers.", + "log []") { + LoadSubCommand("capture", CommandObjectSP(new CommandObjectReproducerCapture( + interpreter))); + LoadSubCommand( + "generate", + CommandObjectSP(new CommandObjectReproducerGenerate(interpreter))); + LoadSubCommand("replay", CommandObjectSP( + new CommandObjectReproducerReplay(interpreter))); +} + +CommandObjectReproducer::~CommandObjectReproducer() = default; Index: source/Core/Debugger.cpp =================================================================== --- source/Core/Debugger.cpp +++ source/Core/Debugger.cpp @@ -43,6 +43,7 @@ #include "lldb/Target/ThreadList.h" // for ThreadList #include "lldb/Utility/AnsiTerminal.h" #include "lldb/Utility/Log.h" // for LLDB_LOG_OPTION_... +#include "lldb/Utility/Reproducer.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" // for Stream #include "lldb/Utility/StreamCallback.h" @@ -411,6 +412,17 @@ GetCommandInterpreter().UpdatePrompt(new_prompt); } +llvm::StringRef Debugger::GetReproducerPath() const { + auto &r = repro::Reproducer::Instance(); + return r.GetReproducerPath(); +} + +void Debugger::SetReproducerPath(llvm::StringRef p) { + auto &r = repro::Reproducer::Instance(); + if (auto e = r.SetReproducerPath(p)) + llvm::consumeError(std::move(e)); +} + const FormatEntity::Entry *Debugger::GetThreadFormat() const { const uint32_t idx = ePropertyThreadFormat; return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); 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/Interpreter/CommandInterpreter.cpp =================================================================== --- source/Interpreter/CommandInterpreter.cpp +++ source/Interpreter/CommandInterpreter.cpp @@ -31,6 +31,7 @@ #include "Commands/CommandObjectProcess.h" #include "Commands/CommandObjectQuit.h" #include "Commands/CommandObjectRegister.h" +#include "Commands/CommandObjectReproducer.h" #include "Commands/CommandObjectSettings.h" #include "Commands/CommandObjectSource.h" #include "Commands/CommandObjectStats.h" @@ -430,7 +431,6 @@ AddAlias("var", cmd_obj_sp); AddAlias("vo", cmd_obj_sp, "--object-description"); } - } void CommandInterpreter::Clear() { @@ -484,6 +484,8 @@ m_command_dict["quit"] = CommandObjectSP(new CommandObjectQuit(*this)); m_command_dict["register"] = CommandObjectSP(new CommandObjectRegister(*this)); + m_command_dict["reproducer"] = + CommandObjectSP(new CommandObjectReproducer(*this)); m_command_dict["script"] = CommandObjectSP(new CommandObjectScript(*this, script_language)); m_command_dict["settings"] = @@ -2387,7 +2389,7 @@ flags |= eHandleCommandFlagStopOnError; } - // stop-on-crash can only be set, if it is present in all levels of + // stop-on-crash can only be set, if it is present in all levels of // pushed flag sets. if (options.GetStopOnCrash()) { if (m_command_source_flags.empty()) { 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(llvm::raw_ostream *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,42 @@ void GDBRemoteCommunication::DumpHistory(Stream &strm) { m_history.Dump(strm); } +void GDBRemoteCommunication::SetHistoryStream(llvm::raw_ostream *strm) { + m_history.SetStream(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,156 @@ +//===-- 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(llvm::raw_ostream *strm) { m_stream = 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; + llvm::raw_ostream *m_stream = nullptr; +}; + +} // 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,143 @@ +//===-- 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); + strm.flush(); +} + +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 && type == ePacketTypeRecv) + m_packets[idx].Serialize(*m_stream); +} + +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 && type == ePacketTypeRecv) + m_packets[idx].Serialize(*m_stream); +} + +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 { @@ -157,7 +159,37 @@ return g_settings_sp; } -} // anonymous namespace end +class ProcessGDBRemoteProvider : public repro::Provider { +public: + ProcessGDBRemoteProvider(llvm::StringRef directory) : Provider(directory) { + m_info.name = "gdb-remote"; + m_info.files.push_back("gdb-remote.yaml"); + } + + raw_ostream *GetHistoryStream() { + FileSpec history_file(GetDirectory(), false); + history_file.AppendPathComponent("gdb-remote.yaml"); + + std::error_code EC; + m_stream_up = make_unique(history_file.GetPath(), EC, + sys::fs::OpenFlags::F_None); + return m_stream_up.get(); + } + + void SetCallback(std::function callback) { + m_callback = std::move(callback); + } + + void Keep() override { m_callback(); } + + void Discard() override { m_callback(); } + +private: + std::function m_callback; + std::unique_ptr m_stream_up; +}; + +} // 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 @@ -258,8 +290,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, @@ -267,6 +299,16 @@ m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadDidExit, "async thread did exit"); + repro::Generator *generator = repro::Reproducer::Instance().GetGenerator(); + if (generator) { + ProcessGDBRemoteProvider &provider = + generator->CreateProvider(); + // Set the history stream to the stream owned by the provider. + m_gdb_comm.SetHistoryStream(provider.GetHistoryStream()); + // Make sure to clear the stream again when we're finished. + provider.SetCallback([&]() { m_gdb_comm.SetHistoryStream(nullptr); }); + } + Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_ASYNC)); const uint32_t async_event_mask = @@ -1059,9 +1101,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() : ""); @@ -1070,9 +1113,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() : ""); @@ -1084,9 +1128,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() : ""); @@ -1106,9 +1151,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() : ""); @@ -1136,9 +1182,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() : ""); @@ -3379,6 +3426,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(), false); + 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? @@ -3389,6 +3473,9 @@ if (platform_sp && !platform_sp->IsHost()) return Status("Lost debug server connection"); + if (repro::Loader *loader = repro::Reproducer::Instance().GetLoader()) + return ConnectToReplayServer(loader); + auto error = LaunchAndConnectToDebugserver(process_info); if (error.Fail()) { const char *error_string = error.AsCString(); @@ -3498,7 +3585,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)); @@ -4270,8 +4357,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; @@ -4433,7 +4521,7 @@ return true; } -} // namespace {} +} // namespace // query the target of gdb-remote for extended target information return: // 'true' on success Index: source/Utility/CMakeLists.txt =================================================================== --- source/Utility/CMakeLists.txt +++ source/Utility/CMakeLists.txt @@ -63,6 +63,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,203 @@ +//===-- 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/Threading.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lldb_private; +using namespace lldb_private::repro; +using namespace llvm; +using namespace llvm::yaml; + +Reproducer &Reproducer::Instance() { + static Reproducer *g_reproducer; + static llvm::once_flag g_once_flag; + llvm::call_once(g_once_flag, []() { + if (g_reproducer == nullptr) + g_reproducer = new Reproducer(); + }); + return *g_reproducer; +} + +const Generator *Reproducer::GetGenerator() const { + std::lock_guard guard(m_mutex); + if (m_generate_reproducer) + return &m_generator; + return nullptr; +} + +const Loader *Reproducer::GetLoader() const { + std::lock_guard guard(m_mutex); + if (m_replay_reproducer) + return &m_loader; + return nullptr; +} + +Generator *Reproducer::GetGenerator() { + std::lock_guard guard(m_mutex); + if (m_generate_reproducer) + return &m_generator; + return nullptr; +} + +Loader *Reproducer::GetLoader() { + std::lock_guard guard(m_mutex); + if (m_replay_reproducer) + return &m_loader; + return nullptr; +} + +llvm::Error Reproducer::SetGenerateReproducer(bool value) { + std::lock_guard guard(m_mutex); + + if (value && m_replay_reproducer) + return make_error( + "cannot generate a reproducer when replay one", + inconvertibleErrorCode()); + + m_generate_reproducer = value; + m_generator.SetEnabled(value); + + return Error::success(); +} + +llvm::Error Reproducer::SetReplayReproducer(bool value) { + std::lock_guard guard(m_mutex); + + if (value && m_generate_reproducer) + return make_error( + "cannot replay a reproducer when generating one", + inconvertibleErrorCode()); + + m_replay_reproducer = value; + + return Error::success(); +} + +llvm::Error Reproducer::SetReproducerPath(llvm::StringRef path) { + // Setting the path implies using the reproducer. + if (auto e = SetReplayReproducer(true)) + return e; + + // Tell the reproducer to load the index form the given path. + if (auto loader = GetLoader()) { + if (auto e = loader->LoadIndex(path)) + return e; + } + + return make_error("unable to get loader", + inconvertibleErrorCode()); +} + +llvm::StringRef Reproducer::GetReproducerPath() const { + if (auto g = GetGenerator()) + return g->GetDirectory(); + return {}; +} + +Generator::Generator() : m_enabled(false), m_done(false) { + m_directory = HostInfo::GetReproducerTempDir().GetPath(); +} + +Generator::~Generator() {} + +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(); +} + +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); +} + +void Generator::ChangeDirectory(llvm::StringRef directory) { + assert(m_providers.empty() && "Changing the directory after providers have " + "been registered would invalidate the index."); + m_directory = directory.str(); +} + +llvm::StringRef Generator::GetDirectory() const { return m_directory; } + +void Generator::AddProviderToIndex(const ProviderInfo &provider_info) { + SmallString<128> index(m_directory.begin(), m_directory.end()); + sys::path::append(index, "index.yaml"); + + std::error_code EC; + auto strm = + make_unique(index, EC, sys::fs::OpenFlags::F_None); + yaml::Output yout(*strm); + yout << const_cast(provider_info); +} + +Loader::Loader() : m_loaded(false) {} + +llvm::Error Loader::LoadIndex(llvm::StringRef directory) { + if (m_loaded) + return llvm::Error::success(); + + SmallString<128> index(directory.begin(), directory.end()); + sys::path::append(index, "index.yaml"); + + auto error_or_file = MemoryBuffer::getFile(index); + 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 @@ -120,19 +120,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."}, @@ -159,6 +163,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, @@ -724,6 +731,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