forked from OSchip/llvm-project
parent
3bea50aeb3
commit
9e046f02e3
|
@ -109,7 +109,7 @@ public:
|
|||
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 @@ public:
|
|||
|
||||
void SetPrompt(const char *prompt);
|
||||
|
||||
const char *GetReproducerPath() const;
|
||||
|
||||
void SetReproducerPath(const char *reproducer);
|
||||
|
||||
lldb::ScriptLanguage GetScriptLanguage() const;
|
||||
|
||||
void SetScriptLanguage(lldb::ScriptLanguage script_lang);
|
||||
|
|
|
@ -261,6 +261,11 @@ public:
|
|||
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);
|
||||
|
|
|
@ -93,6 +93,12 @@ public:
|
|||
/// 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 @@ protected:
|
|||
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);
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
//===-- 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 <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<std::string> 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(const 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 <typename T> T &CreateProvider() {
|
||||
std::unique_ptr<T> provider = llvm::make_unique<T>(m_directory);
|
||||
return static_cast<T &>(Register(std::move(provider)));
|
||||
}
|
||||
|
||||
void ChangeDirectory(const FileSpec &directory);
|
||||
const FileSpec &GetDirectory() const;
|
||||
|
||||
private:
|
||||
friend Reproducer;
|
||||
|
||||
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
||||
Provider &Register(std::unique_ptr<Provider> provider);
|
||||
void AddProviderToIndex(const ProviderInfo &provider_info);
|
||||
|
||||
std::vector<std::unique_ptr<Provider>> 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<ProviderInfo> GetProviderInfo(llvm::StringRef name);
|
||||
llvm::Error LoadIndex(const FileSpec &directory);
|
||||
|
||||
const FileSpec &GetDirectory() { return m_directory; }
|
||||
|
||||
private:
|
||||
llvm::StringMap<ProviderInfo> 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:
|
||||
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(const FileSpec &path);
|
||||
FileSpec 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<lldb_private::repro::ProviderInfo> {
|
||||
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
|
|
@ -0,0 +1,5 @@
|
|||
LEVEL = ../../../make
|
||||
|
||||
C_SOURCES := main.c
|
||||
|
||||
include $(LEVEL)/Makefile.rules
|
|
@ -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")
|
|
@ -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 <stdio.h>
|
||||
|
||||
void foo() {
|
||||
printf("testing\n");
|
||||
}
|
||||
|
||||
int main (int argc, char const *argv[]) {
|
||||
foo();
|
||||
return 0;
|
||||
}
|
|
@ -28,7 +28,7 @@ def disassemble_instructions (insts):
|
|||
# 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 @@ if target:
|
|||
# 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 @@ public:
|
|||
|
||||
static void
|
||||
Initialize();
|
||||
|
||||
|
||||
static void
|
||||
Terminate();
|
||||
|
||||
|
||||
static lldb::SBDebugger
|
||||
Create();
|
||||
|
||||
|
@ -155,8 +155,8 @@ public:
|
|||
|
||||
void
|
||||
SetAsync (bool b);
|
||||
|
||||
bool
|
||||
|
||||
bool
|
||||
GetAsync ();
|
||||
|
||||
void
|
||||
|
@ -248,7 +248,7 @@ public:
|
|||
|
||||
lldb::SBPlatform
|
||||
GetSelectedPlatform();
|
||||
|
||||
|
||||
void
|
||||
SetSelectedPlatform(lldb::SBPlatform &platform);
|
||||
|
||||
|
@ -287,7 +287,7 @@ public:
|
|||
// SBPlatform from this class.
|
||||
lldb::SBError
|
||||
SetCurrentPlatform (const char *platform_name);
|
||||
|
||||
|
||||
bool
|
||||
SetCurrentPlatformSDKRoot (const char *sysroot);
|
||||
|
||||
|
@ -295,8 +295,8 @@ public:
|
|||
// an interface to the Set/Get UseExternalEditor.
|
||||
bool
|
||||
SetUseExternalEditor (bool input);
|
||||
|
||||
bool
|
||||
|
||||
bool
|
||||
GetUseExternalEditor ();
|
||||
|
||||
bool
|
||||
|
@ -342,7 +342,7 @@ public:
|
|||
|
||||
void
|
||||
DispatchInputEndOfFile ();
|
||||
|
||||
|
||||
const char *
|
||||
GetInstanceName ();
|
||||
|
||||
|
@ -366,14 +366,20 @@ public:
|
|||
|
||||
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 @@ public:
|
|||
|
||||
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 @@ public:
|
|||
int &num_errors,
|
||||
bool &quit_requested,
|
||||
bool &stopped_for_crash);
|
||||
|
||||
|
||||
lldb::SBError
|
||||
RunREPL (lldb::LanguageType language, const char *repl_options);
|
||||
}; // class SBDebugger
|
||||
|
|
|
@ -1051,6 +1051,17 @@ void SBDebugger::SetPrompt(const char *prompt) {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ add_lldb_library(lldbCommands
|
|||
CommandObjectProcess.cpp
|
||||
CommandObjectQuit.cpp
|
||||
CommandObjectRegister.cpp
|
||||
CommandObjectReproducer.cpp
|
||||
CommandObjectSettings.cpp
|
||||
CommandObjectSource.cpp
|
||||
CommandObjectStats.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(FileSpec(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 <subcommand> [<command-options>]") {
|
||||
LoadSubCommand("capture", CommandObjectSP(new CommandObjectReproducerCapture(
|
||||
interpreter)));
|
||||
LoadSubCommand(
|
||||
"generate",
|
||||
CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
|
||||
LoadSubCommand("replay", CommandObjectSP(
|
||||
new CommandObjectReproducerReplay(interpreter)));
|
||||
}
|
||||
|
||||
CommandObjectReproducer::~CommandObjectReproducer() = default;
|
|
@ -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_
|
|
@ -44,6 +44,7 @@
|
|||
#include "lldb/Target/ThreadList.h"
|
||||
#include "lldb/Utility/AnsiTerminal.h"
|
||||
#include "lldb/Utility/Log.h"
|
||||
#include "lldb/Utility/Reproducer.h"
|
||||
#include "lldb/Utility/State.h"
|
||||
#include "lldb/Utility/Stream.h"
|
||||
#include "lldb/Utility/StreamCallback.h"
|
||||
|
@ -412,6 +413,17 @@ void Debugger::SetPrompt(llvm::StringRef p) {
|
|||
GetCommandInterpreter().UpdatePrompt(new_prompt);
|
||||
}
|
||||
|
||||
llvm::StringRef Debugger::GetReproducerPath() const {
|
||||
auto &r = repro::Reproducer::Instance();
|
||||
return r.GetReproducerPath().GetCString();
|
||||
}
|
||||
|
||||
void Debugger::SetReproducerPath(llvm::StringRef p) {
|
||||
auto &r = repro::Reproducer::Instance();
|
||||
if (auto e = r.SetReproducerPath(FileSpec(p)))
|
||||
llvm::consumeError(std::move(e));
|
||||
}
|
||||
|
||||
const FormatEntity::Entry *Debugger::GetThreadFormat() const {
|
||||
const uint32_t idx = ePropertyThreadFormat;
|
||||
return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx);
|
||||
|
|
|
@ -194,6 +194,19 @@ FileSpec HostInfoBase::GetGlobalTempDir() {
|
|||
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();
|
||||
|
@ -276,6 +289,26 @@ bool HostInfoBase::ComputeGlobalTempFileDirectory(FileSpec &file_spec) {
|
|||
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.
|
||||
|
|
|
@ -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"
|
||||
|
@ -483,6 +484,8 @@ void CommandInterpreter::LoadCommandDictionary() {
|
|||
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 +2390,7 @@ void CommandInterpreter::HandleCommandsFromFile(
|
|||
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()) {
|
||||
|
|
|
@ -15,6 +15,8 @@ add_lldb_library(lldbPluginProcessGDBRemote PLUGIN
|
|||
GDBRemoteClientBase.cpp
|
||||
GDBRemoteCommunication.cpp
|
||||
GDBRemoteCommunicationClient.cpp
|
||||
GDBRemoteCommunicationHistory.cpp
|
||||
GDBRemoteCommunicationReplayServer.cpp
|
||||
GDBRemoteCommunicationServer.cpp
|
||||
GDBRemoteCommunicationServerCommon.cpp
|
||||
GDBRemoteCommunicationServerLLGS.cpp
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "GDBRemoteCommunication.h"
|
||||
|
||||
#include <future>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
@ -22,6 +23,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"
|
||||
|
@ -51,78 +54,6 @@ using namespace lldb;
|
|||
using namespace lldb_private;
|
||||
using namespace lldb_private::process_gdb_remote;
|
||||
|
||||
GDBRemoteCommunication::History::History(uint32_t size)
|
||||
: m_packets(), m_curr_idx(0), m_total_packet_count(0),
|
||||
m_dumped_to_log(false) {
|
||||
m_packets.resize(size);
|
||||
}
|
||||
|
||||
GDBRemoteCommunication::History::~History() {}
|
||||
|
||||
void GDBRemoteCommunication::History::AddPacket(char packet_char,
|
||||
PacketType type,
|
||||
uint32_t bytes_transmitted) {
|
||||
const size_t size = m_packets.size();
|
||||
if (size > 0) {
|
||||
const uint32_t idx = GetNextIndex();
|
||||
m_packets[idx].packet.assign(1, packet_char);
|
||||
m_packets[idx].type = type;
|
||||
m_packets[idx].bytes_transmitted = bytes_transmitted;
|
||||
m_packets[idx].packet_idx = m_total_packet_count;
|
||||
m_packets[idx].tid = 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
|
||||
//----------------------------------------------------------------------
|
||||
|
@ -169,7 +100,8 @@ size_t GDBRemoteCommunication::SendAck() {
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -180,26 +112,31 @@ size_t GDBRemoteCommunication::SendNack() {
|
|||
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;
|
||||
|
@ -238,11 +175,12 @@ GDBRemoteCommunication::SendPacketNoLock(llvm::StringRef payload) {
|
|||
(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;
|
||||
|
@ -857,7 +795,8 @@ GDBRemoteCommunication::CheckForPacket(const uint8_t *src, size_t src_len,
|
|||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -1302,6 +1241,42 @@ Status GDBRemoteCommunication::StartDebugserverProcess(
|
|||
|
||||
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<Status> 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<ConnectionFileDescriptor> conn_up(
|
||||
new ConnectionFileDescriptor());
|
||||
if (conn_up->Connect(remote_addr, nullptr) != lldb::eConnectionStatusSuccess)
|
||||
return llvm::make_error<llvm::StringError>("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) {
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#ifndef liblldb_GDBRemoteCommunication_h_
|
||||
#define liblldb_GDBRemoteCommunication_h_
|
||||
|
||||
#include "GDBRemoteCommunicationHistory.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
@ -22,7 +24,6 @@
|
|||
#include "lldb/Utility/Args.h"
|
||||
#include "lldb/Utility/Predicate.h"
|
||||
#include "lldb/lldb-public.h"
|
||||
|
||||
#include "lldb/Utility/StringExtractorGDBRemote.h"
|
||||
|
||||
namespace lldb_private {
|
||||
|
@ -136,86 +137,16 @@ public:
|
|||
// fork/exec to avoid having to connect/accept
|
||||
|
||||
void DumpHistory(Stream &strm);
|
||||
void SetHistoryStream(llvm::raw_ostream *strm);
|
||||
|
||||
static llvm::Error ConnectLocally(GDBRemoteCommunication &client,
|
||||
GDBRemoteCommunication &server);
|
||||
|
||||
protected:
|
||||
class History {
|
||||
public:
|
||||
enum PacketType {
|
||||
ePacketTypeInvalid = 0,
|
||||
ePacketTypeSend,
|
||||
ePacketTypeRecv
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
Entry()
|
||||
: packet(), type(ePacketTypeInvalid), bytes_transmitted(0),
|
||||
packet_idx(0), tid(LLDB_INVALID_THREAD_ID) {}
|
||||
|
||||
void Clear() {
|
||||
packet.clear();
|
||||
type = ePacketTypeInvalid;
|
||||
bytes_transmitted = 0;
|
||||
packet_idx = 0;
|
||||
tid = LLDB_INVALID_THREAD_ID;
|
||||
}
|
||||
std::string packet;
|
||||
PacketType type;
|
||||
uint32_t bytes_transmitted;
|
||||
uint32_t packet_idx;
|
||||
lldb::tid_t tid;
|
||||
};
|
||||
|
||||
History(uint32_t size);
|
||||
|
||||
~History();
|
||||
|
||||
// For single char packets for ack, nack and /x03
|
||||
void AddPacket(char packet_char, PacketType type,
|
||||
uint32_t bytes_transmitted);
|
||||
|
||||
void AddPacket(const std::string &src, uint32_t src_len, PacketType type,
|
||||
uint32_t bytes_transmitted);
|
||||
|
||||
void Dump(Stream &strm) const;
|
||||
|
||||
void Dump(Log *log) const;
|
||||
|
||||
bool DidDumpToLog() const { return m_dumped_to_log; }
|
||||
|
||||
protected:
|
||||
uint32_t GetFirstSavedPacketIndex() const {
|
||||
if (m_total_packet_count < m_packets.size())
|
||||
return 0;
|
||||
else
|
||||
return m_curr_idx + 1;
|
||||
}
|
||||
|
||||
uint32_t GetNumPacketsInHistory() const {
|
||||
if (m_total_packet_count < m_packets.size())
|
||||
return m_total_packet_count;
|
||||
else
|
||||
return (uint32_t)m_packets.size();
|
||||
}
|
||||
|
||||
uint32_t GetNextIndex() {
|
||||
++m_total_packet_count;
|
||||
const uint32_t idx = m_curr_idx;
|
||||
m_curr_idx = NormalizeIndex(idx + 1);
|
||||
return idx;
|
||||
}
|
||||
|
||||
uint32_t NormalizeIndex(uint32_t i) const { return i % m_packets.size(); }
|
||||
|
||||
std::vector<Entry> m_packets;
|
||||
uint32_t m_curr_idx;
|
||||
uint32_t m_total_packet_count;
|
||||
mutable bool m_dumped_to_log;
|
||||
};
|
||||
|
||||
std::chrono::seconds m_packet_timeout;
|
||||
uint32_t m_echo_number;
|
||||
LazyBool m_supports_qEcho;
|
||||
History m_history;
|
||||
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
|
||||
|
@ -224,6 +155,8 @@ protected:
|
|||
CompressionType m_compression_type;
|
||||
|
||||
PacketResult SendPacketNoLock(llvm::StringRef payload);
|
||||
PacketResult SendRawPacketNoLock(llvm::StringRef payload,
|
||||
bool skip_ack = false);
|
||||
|
||||
PacketResult ReadPacket(StringExtractorGDBRemote &response,
|
||||
Timeout<std::micro> timeout, bool sync_on_timeout);
|
||||
|
|
|
@ -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<GDBRemoteCommunicationHistory::Entry &>(*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<GDBRemoteCommunicationHistory::PacketType>::
|
||||
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<GDBRemoteCommunicationHistory::Entry::BinaryData>::
|
||||
output(const GDBRemoteCommunicationHistory::Entry::BinaryData &Val, void *,
|
||||
raw_ostream &Out) {
|
||||
Out << toHex(Val.data);
|
||||
}
|
||||
|
||||
StringRef
|
||||
yaml::ScalarTraits<GDBRemoteCommunicationHistory::Entry::BinaryData>::input(
|
||||
StringRef Scalar, void *,
|
||||
GDBRemoteCommunicationHistory::Entry::BinaryData &Val) {
|
||||
Val.data = fromHex(Scalar);
|
||||
return {};
|
||||
}
|
||||
|
||||
void yaml::MappingTraits<GDBRemoteCommunicationHistory::Entry>::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<GDBRemoteCommunicationHistory::Entry>::validate(
|
||||
IO &io, GDBRemoteCommunicationHistory::Entry &Entry) {
|
||||
if (Entry.bytes_transmitted != Entry.packet.data.size())
|
||||
return "BinaryData size doesn't match bytes transmitted";
|
||||
|
||||
return {};
|
||||
}
|
|
@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
#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<GDBRemoteCommunicationHistory>;
|
||||
|
||||
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<Entry> 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<lldb_private::process_gdb_remote::
|
||||
GDBRemoteCommunicationHistory::PacketType> {
|
||||
static void enumeration(IO &io,
|
||||
lldb_private::process_gdb_remote::
|
||||
GDBRemoteCommunicationHistory::PacketType &value);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ScalarTraits<lldb_private::process_gdb_remote::
|
||||
GDBRemoteCommunicationHistory::Entry::BinaryData> {
|
||||
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_
|
|
@ -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 <errno.h>
|
||||
|
||||
#include "lldb/Host/Config.h"
|
||||
|
||||
#include "GDBRemoteCommunicationReplayServer.h"
|
||||
#include "ProcessGDBRemoteLog.h"
|
||||
|
||||
// C Includes
|
||||
// C++ Includes
|
||||
#include <cstring>
|
||||
|
||||
// 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<std::micro> 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<std::recursive_mutex> 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(
|
||||
"<lldb.gdb-remote.server.async>",
|
||||
GDBRemoteCommunicationReplayServer::AsyncThread, this, nullptr);
|
||||
}
|
||||
|
||||
// Wait for handshake.
|
||||
m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue);
|
||||
|
||||
return m_async_thread.IsJoinable();
|
||||
}
|
||||
|
||||
void GDBRemoteCommunicationReplayServer::StopAsyncThread() {
|
||||
std::lock_guard<std::recursive_mutex> 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;
|
||||
}
|
|
@ -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 <functional>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
|
||||
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<std::micro> 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<GDBRemoteCommunicationHistory::Entry> 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_
|
|
@ -65,6 +65,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"
|
||||
|
@ -85,6 +86,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;
|
||||
|
@ -104,7 +106,7 @@ void DumpProcessGDBRemotePacketHistory(void *p, const char *path) {
|
|||
if (error.Success())
|
||||
((ProcessGDBRemote *)p)->GetGDBRemote().DumpHistory(strm);
|
||||
}
|
||||
}
|
||||
} // namespace lldb
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -155,7 +157,37 @@ static const ProcessKDPPropertiesSP &GetGlobalPluginProperties() {
|
|||
return g_settings_sp;
|
||||
}
|
||||
|
||||
} // anonymous namespace end
|
||||
class ProcessGDBRemoteProvider : public repro::Provider {
|
||||
public:
|
||||
ProcessGDBRemoteProvider(const FileSpec &directory) : Provider(directory) {
|
||||
m_info.name = "gdb-remote";
|
||||
m_info.files.push_back("gdb-remote.yaml");
|
||||
}
|
||||
|
||||
raw_ostream *GetHistoryStream() {
|
||||
FileSpec history_file =
|
||||
GetDirectory().CopyByAppendingPathComponent("gdb-remote.yaml");
|
||||
|
||||
std::error_code EC;
|
||||
m_stream_up = make_unique<raw_fd_ostream>(history_file.GetPath(), EC,
|
||||
sys::fs::OpenFlags::F_None);
|
||||
return m_stream_up.get();
|
||||
}
|
||||
|
||||
void SetCallback(std::function<void()> callback) {
|
||||
m_callback = std::move(callback);
|
||||
}
|
||||
|
||||
void Keep() override { m_callback(); }
|
||||
|
||||
void Discard() override { m_callback(); }
|
||||
|
||||
private:
|
||||
std::function<void()> m_callback;
|
||||
std::unique_ptr<raw_fd_ostream> 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
|
||||
|
@ -256,8 +288,8 @@ ProcessGDBRemote::ProcessGDBRemote(lldb::TargetSP target_sp,
|
|||
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,
|
||||
|
@ -265,6 +297,16 @@ ProcessGDBRemote::ProcessGDBRemote(lldb::TargetSP target_sp,
|
|||
m_async_broadcaster.SetEventName(eBroadcastBitAsyncThreadDidExit,
|
||||
"async thread did exit");
|
||||
|
||||
repro::Generator *generator = repro::Reproducer::Instance().GetGenerator();
|
||||
if (generator) {
|
||||
ProcessGDBRemoteProvider &provider =
|
||||
generator->CreateProvider<ProcessGDBRemoteProvider>();
|
||||
// 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 =
|
||||
|
@ -1057,9 +1099,10 @@ void ProcessGDBRemote::DidLaunchOrAttach(ArchSpec &process_arch) {
|
|||
if (log)
|
||||
log->Printf("ProcessGDBRemote::%s gdb-remote had process architecture, "
|
||||
"using %s %s",
|
||||
__FUNCTION__, process_arch.GetArchitectureName()
|
||||
? process_arch.GetArchitectureName()
|
||||
: "<null>",
|
||||
__FUNCTION__,
|
||||
process_arch.GetArchitectureName()
|
||||
? process_arch.GetArchitectureName()
|
||||
: "<null>",
|
||||
process_arch.GetTriple().getTriple().c_str()
|
||||
? process_arch.GetTriple().getTriple().c_str()
|
||||
: "<null>");
|
||||
|
@ -1068,9 +1111,10 @@ void ProcessGDBRemote::DidLaunchOrAttach(ArchSpec &process_arch) {
|
|||
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()
|
||||
: "<null>",
|
||||
__FUNCTION__,
|
||||
process_arch.GetArchitectureName()
|
||||
? process_arch.GetArchitectureName()
|
||||
: "<null>",
|
||||
process_arch.GetTriple().getTriple().c_str()
|
||||
? process_arch.GetTriple().getTriple().c_str()
|
||||
: "<null>");
|
||||
|
@ -1082,9 +1126,10 @@ void ProcessGDBRemote::DidLaunchOrAttach(ArchSpec &process_arch) {
|
|||
if (log)
|
||||
log->Printf(
|
||||
"ProcessGDBRemote::%s analyzing target arch, currently %s %s",
|
||||
__FUNCTION__, target_arch.GetArchitectureName()
|
||||
? target_arch.GetArchitectureName()
|
||||
: "<null>",
|
||||
__FUNCTION__,
|
||||
target_arch.GetArchitectureName()
|
||||
? target_arch.GetArchitectureName()
|
||||
: "<null>",
|
||||
target_arch.GetTriple().getTriple().c_str()
|
||||
? target_arch.GetTriple().getTriple().c_str()
|
||||
: "<null>");
|
||||
|
@ -1104,9 +1149,10 @@ void ProcessGDBRemote::DidLaunchOrAttach(ArchSpec &process_arch) {
|
|||
if (log)
|
||||
log->Printf("ProcessGDBRemote::%s remote process is ARM/Apple, "
|
||||
"setting target arch to %s %s",
|
||||
__FUNCTION__, process_arch.GetArchitectureName()
|
||||
? process_arch.GetArchitectureName()
|
||||
: "<null>",
|
||||
__FUNCTION__,
|
||||
process_arch.GetArchitectureName()
|
||||
? process_arch.GetArchitectureName()
|
||||
: "<null>",
|
||||
process_arch.GetTriple().getTriple().c_str()
|
||||
? process_arch.GetTriple().getTriple().c_str()
|
||||
: "<null>");
|
||||
|
@ -1134,9 +1180,10 @@ void ProcessGDBRemote::DidLaunchOrAttach(ArchSpec &process_arch) {
|
|||
if (log)
|
||||
log->Printf("ProcessGDBRemote::%s final target arch after "
|
||||
"adjustments for remote architecture: %s %s",
|
||||
__FUNCTION__, target_arch.GetArchitectureName()
|
||||
? target_arch.GetArchitectureName()
|
||||
: "<null>",
|
||||
__FUNCTION__,
|
||||
target_arch.GetArchitectureName()
|
||||
? target_arch.GetArchitectureName()
|
||||
: "<null>",
|
||||
target_arch.GetTriple().getTriple().c_str()
|
||||
? target_arch.GetTriple().getTriple().c_str()
|
||||
: "<null>");
|
||||
|
@ -3377,6 +3424,43 @@ Status ProcessGDBRemote::DoSignal(int signo) {
|
|||
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?
|
||||
|
@ -3387,6 +3471,9 @@ ProcessGDBRemote::EstablishConnectionIfNeeded(const ProcessInfo &process_info) {
|
|||
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();
|
||||
|
@ -3496,7 +3583,7 @@ bool ProcessGDBRemote::MonitorDebugserverProcess(
|
|||
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));
|
||||
|
@ -4268,8 +4355,9 @@ bool ParseRegisters(XMLNode feature_node, GdbServerTargetInfo &target_info,
|
|||
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;
|
||||
|
@ -4431,7 +4519,7 @@ bool ParseRegisters(XMLNode feature_node, GdbServerTargetInfo &target_info,
|
|||
return true;
|
||||
}
|
||||
|
||||
} // namespace {}
|
||||
} // namespace
|
||||
|
||||
// query the target of gdb-remote for extended target information return:
|
||||
// 'true' on success
|
||||
|
|
|
@ -34,11 +34,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;
|
||||
|
@ -262,6 +266,7 @@ protected:
|
|||
};
|
||||
|
||||
GDBRemoteCommunicationClient m_gdb_comm;
|
||||
GDBRemoteCommunicationReplayServer m_gdb_replay_server;
|
||||
std::atomic<lldb::pid_t> m_debugserver_pid;
|
||||
std::vector<StringExtractorGDBRemote> m_stop_packet_stack; // The stop packet
|
||||
// stack replaces
|
||||
|
@ -302,6 +307,7 @@ protected:
|
|||
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<lldb::addr_t, size_t>;
|
||||
using FlashRange = FlashRangeVector::Entry;
|
||||
|
@ -329,6 +335,8 @@ protected:
|
|||
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);
|
||||
|
|
|
@ -63,6 +63,7 @@ add_lldb_library(lldbUtility
|
|||
Range.cpp
|
||||
RegisterValue.cpp
|
||||
RegularExpression.cpp
|
||||
Reproducer.cpp
|
||||
Scalar.cpp
|
||||
SelectHelper.cpp
|
||||
SharingPtr.cpp
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
//===-- 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;
|
||||
return g_reproducer;
|
||||
}
|
||||
|
||||
const Generator *Reproducer::GetGenerator() const {
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
if (m_generate_reproducer)
|
||||
return &m_generator;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Loader *Reproducer::GetLoader() const {
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
if (m_replay_reproducer)
|
||||
return &m_loader;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Generator *Reproducer::GetGenerator() {
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
if (m_generate_reproducer)
|
||||
return &m_generator;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Loader *Reproducer::GetLoader() {
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
if (m_replay_reproducer)
|
||||
return &m_loader;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
llvm::Error Reproducer::SetGenerateReproducer(bool value) {
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
|
||||
if (value && m_replay_reproducer)
|
||||
return make_error<StringError>(
|
||||
"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<std::mutex> guard(m_mutex);
|
||||
|
||||
if (value && m_generate_reproducer)
|
||||
return make_error<StringError>(
|
||||
"cannot replay a reproducer when generating one",
|
||||
inconvertibleErrorCode());
|
||||
|
||||
m_replay_reproducer = value;
|
||||
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
llvm::Error Reproducer::SetReproducerPath(const FileSpec &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<StringError>("unable to get loader",
|
||||
inconvertibleErrorCode());
|
||||
}
|
||||
|
||||
FileSpec Reproducer::GetReproducerPath() const {
|
||||
if (auto g = GetGenerator())
|
||||
return g->GetDirectory();
|
||||
return {};
|
||||
}
|
||||
|
||||
Generator::Generator() : m_enabled(false), m_done(false) {
|
||||
m_directory = HostInfo::GetReproducerTempDir();
|
||||
}
|
||||
|
||||
Generator::~Generator() {}
|
||||
|
||||
Provider &Generator::Register(std::unique_ptr<Provider> provider) {
|
||||
std::lock_guard<std::mutex> 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.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;
|
||||
}
|
||||
|
||||
const FileSpec &Generator::GetDirectory() const { return m_directory; }
|
||||
|
||||
void Generator::AddProviderToIndex(const ProviderInfo &provider_info) {
|
||||
FileSpec index = m_directory;
|
||||
index.AppendPathComponent("index.yaml");
|
||||
|
||||
std::error_code EC;
|
||||
auto strm = make_unique<raw_fd_ostream>(index.GetPath(), EC,
|
||||
sys::fs::OpenFlags::F_None);
|
||||
yaml::Output yout(*strm);
|
||||
yout << const_cast<ProviderInfo &>(provider_info);
|
||||
}
|
||||
|
||||
Loader::Loader() : m_loaded(false) {}
|
||||
|
||||
llvm::Error Loader::LoadIndex(const FileSpec &directory) {
|
||||
if (m_loaded)
|
||||
return llvm::Error::success();
|
||||
|
||||
FileSpec index = directory.CopyByAppendingPathComponent("index.yaml");
|
||||
|
||||
auto error_or_file = MemoryBuffer::getFile(index.GetPath());
|
||||
if (auto err = error_or_file.getError())
|
||||
return errorCodeToError(err);
|
||||
|
||||
std::vector<ProviderInfo> 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<ProviderInfo> 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;
|
||||
}
|
|
@ -120,19 +120,23 @@ static constexpr OptionDefinition g_options[] = {
|
|||
"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 @@ static constexpr OptionDefinition g_options[] = {
|
|||
"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 <path> 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 @@ SBError Driver::ParseArgs(int argc, const char *argv[], FILE *out_fh,
|
|||
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;
|
||||
|
|
|
@ -48,7 +48,8 @@ struct TestClient : public GDBRemoteClientBase {
|
|||
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));
|
||||
|
|
|
@ -63,7 +63,8 @@ std::string one_register_hex = "41424344";
|
|||
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:
|
||||
|
|
|
@ -30,7 +30,8 @@ public:
|
|||
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:
|
||||
|
|
|
@ -9,11 +9,6 @@
|
|||
|
||||
#include "GDBRemoteTestUtils.h"
|
||||
|
||||
#include "lldb/Host/common/TCPSocket.h"
|
||||
#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h"
|
||||
|
||||
#include <future>
|
||||
|
||||
namespace lldb_private {
|
||||
namespace process_gdb_remote {
|
||||
|
||||
|
@ -30,34 +25,5 @@ void GDBRemoteTest::TearDownTestCase() {
|
|||
#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<Status> 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<ConnectionFileDescriptor> conn_up(
|
||||
new ConnectionFileDescriptor());
|
||||
if (conn_up->Connect(remote_addr, nullptr) != lldb::eConnectionStatusSuccess)
|
||||
return llvm::make_error<llvm::StringError>("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
|
||||
|
|
|
@ -20,10 +20,6 @@ class GDBRemoteTest : public testing::Test {
|
|||
public:
|
||||
static void SetUpTestCase();
|
||||
static void TearDownTestCase();
|
||||
|
||||
protected:
|
||||
llvm::Error Connect(GDBRemoteCommunication &client,
|
||||
GDBRemoteCommunication &server);
|
||||
};
|
||||
|
||||
struct MockServer : public GDBRemoteCommunicationServer {
|
||||
|
|
Loading…
Reference in New Issue