forked from OSchip/llvm-project
[Reproducers] SBReproducer framework: Capture & Replay
This is part two of the reproducer instrumentation framework. It contains the code to capture and replay function calls. The main user of this framework will be the SB API layer. For all the details refer to the RFC on the mailing list: http://lists.llvm.org/pipermail/lldb-dev/2019-January/014530.html Differential revision: https://reviews.llvm.org/D56322 llvm-svn: 353324
This commit is contained in:
parent
7c77044a38
commit
58947cf854
|
@ -0,0 +1,23 @@
|
|||
//===-- SBReproducer.h ------------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLDB_API_SBREPRODUCER_H
|
||||
#define LLDB_API_SBREPRODUCER_H
|
||||
|
||||
#include "lldb/lldb-defines.h"
|
||||
|
||||
namespace lldb {
|
||||
|
||||
class LLDB_API SBReproducer {
|
||||
public:
|
||||
static bool Replay();
|
||||
};
|
||||
|
||||
} // namespace lldb
|
||||
|
||||
#endif
|
|
@ -1,5 +1,4 @@
|
|||
//===-- ReproducerInstrumentation.h -----------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
@ -18,7 +17,104 @@
|
|||
#include "llvm/Support/ErrorHandling.h"
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#define LLDB_REGISTER_CONSTRUCTOR(Class, Signature) \
|
||||
Register<Class * Signature>(&construct<Class Signature>::doit)
|
||||
#define LLDB_REGISTER_METHOD(Result, Class, Method, Signature) \
|
||||
Register(&invoke<Result(Class::*) Signature>::method<&Class::Method>::doit)
|
||||
#define LLDB_REGISTER_METHOD_CONST(Result, Class, Method, Signature) \
|
||||
Register(&invoke<Result(Class::*) \
|
||||
Signature const>::method_const<&Class::Method>::doit)
|
||||
#define LLDB_REGISTER_STATIC_METHOD(Result, Class, Method, Signature) \
|
||||
Register<Result Signature>(static_cast<Result(*) Signature>(&Class::Method))
|
||||
|
||||
#define LLDB_RECORD_CONSTRUCTOR(Class, Signature, ...) \
|
||||
if (lldb_private::repro::InstrumentationData data = \
|
||||
LLDB_GET_INSTRUMENTATION_DATA()) { \
|
||||
lldb_private::repro::Recorder sb_recorder( \
|
||||
data.GetSerializer(), data.GetRegistry(), LLVM_PRETTY_FUNCTION); \
|
||||
sb_recorder.Record(&lldb_private::repro::construct<Class Signature>::doit, \
|
||||
__VA_ARGS__); \
|
||||
sb_recorder.RecordResult(this); \
|
||||
}
|
||||
|
||||
#define LLDB_RECORD_CONSTRUCTOR_NO_ARGS(Class) \
|
||||
if (lldb_private::repro::InstrumentationData data = \
|
||||
LLDB_GET_INSTRUMENTATION_DATA()) { \
|
||||
lldb_private::repro::Recorder sb_recorder( \
|
||||
data.GetSerializer(), data.GetRegistry(), LLVM_PRETTY_FUNCTION); \
|
||||
sb_recorder.Record(&lldb_private::repro::construct<Class()>::doit); \
|
||||
sb_recorder.RecordResult(this); \
|
||||
}
|
||||
|
||||
#define LLDB_RECORD_METHOD(Result, Class, Method, Signature, ...) \
|
||||
llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
|
||||
if (lldb_private::repro::InstrumentationData data = \
|
||||
LLDB_GET_INSTRUMENTATION_DATA()) { \
|
||||
sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
|
||||
LLVM_PRETTY_FUNCTION); \
|
||||
sb_recorder->Record( \
|
||||
&lldb_private::repro::invoke<Result( \
|
||||
Class::*) Signature>::method<&Class::Method>::doit, \
|
||||
this, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LLDB_RECORD_METHOD_CONST(Result, Class, Method, Signature, ...) \
|
||||
llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
|
||||
if (lldb_private::repro::InstrumentationData data = \
|
||||
LLDB_GET_INSTRUMENTATION_DATA()) { \
|
||||
sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
|
||||
LLVM_PRETTY_FUNCTION); \
|
||||
sb_recorder->Record( \
|
||||
&lldb_private::repro::invoke<Result( \
|
||||
Class::*) Signature const>::method_const<&Class::Method>::doit, \
|
||||
this, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LLDB_RECORD_METHOD_NO_ARGS(Result, Class, Method) \
|
||||
llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
|
||||
if (lldb_private::repro::InstrumentationData data = \
|
||||
LLDB_GET_INSTRUMENTATION_DATA()) { \
|
||||
sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
|
||||
LLVM_PRETTY_FUNCTION); \
|
||||
sb_recorder->Record(&lldb_private::repro::invoke<Result ( \
|
||||
Class::*)()>::method<&Class::Method>::doit, \
|
||||
this); \
|
||||
}
|
||||
|
||||
#define LLDB_RECORD_METHOD_CONST_NO_ARGS(Result, Class, Method) \
|
||||
llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
|
||||
if (lldb_private::repro::InstrumentationData data = \
|
||||
LLDB_GET_INSTRUMENTATION_DATA()) { \
|
||||
sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
|
||||
LLVM_PRETTY_FUNCTION); \
|
||||
sb_recorder->Record( \
|
||||
&lldb_private::repro::invoke<Result ( \
|
||||
Class::*)() const>::method_const<&Class::Method>::doit, \
|
||||
this); \
|
||||
}
|
||||
|
||||
#define LLDB_RECORD_STATIC_METHOD(Result, Class, Method, Signature, ...) \
|
||||
llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
|
||||
if (lldb_private::repro::InstrumentationData data = \
|
||||
LLDB_GET_INSTRUMENTATION_DATA()) { \
|
||||
sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
|
||||
LLVM_PRETTY_FUNCTION); \
|
||||
sb_recorder->Record(static_cast<Result(*) Signature>(&Class::Method), \
|
||||
__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LLDB_RECORD_STATIC_METHOD_NO_ARGS(Result, Class, Method) \
|
||||
llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
|
||||
if (lldb_private::repro::InstrumentationData data = \
|
||||
LLDB_GET_INSTRUMENTATION_DATA()) { \
|
||||
sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
|
||||
LLVM_PRETTY_FUNCTION); \
|
||||
sb_recorder->Record(static_cast<Result (*)()>(&Class::Method)); \
|
||||
}
|
||||
|
||||
#define LLDB_RECORD_RESULT(Result) \
|
||||
sb_recorder ? sb_recorder->RecordResult(Result) : Result;
|
||||
|
||||
namespace lldb_private {
|
||||
namespace repro {
|
||||
|
@ -139,9 +235,6 @@ public:
|
|||
assert(result == 0);
|
||||
}
|
||||
|
||||
protected:
|
||||
IndexToObject &GetIndexToObject() { return m_index_to_object; }
|
||||
|
||||
private:
|
||||
template <typename T> T Read(ValueTag) {
|
||||
assert(HasData(sizeof(T)));
|
||||
|
@ -222,6 +315,114 @@ template <> struct DeserializationHelper<> {
|
|||
};
|
||||
};
|
||||
|
||||
/// The replayer interface.
|
||||
struct Replayer {
|
||||
virtual ~Replayer() {}
|
||||
virtual void operator()(Deserializer &deserializer) const = 0;
|
||||
};
|
||||
|
||||
/// The default replayer deserializes the arguments and calls the function.
|
||||
template <typename Signature> struct DefaultReplayer;
|
||||
template <typename Result, typename... Args>
|
||||
struct DefaultReplayer<Result(Args...)> : public Replayer {
|
||||
DefaultReplayer(Result (*f)(Args...)) : Replayer(), f(f) {}
|
||||
|
||||
void operator()(Deserializer &deserializer) const override {
|
||||
deserializer.HandleReplayResult(
|
||||
DeserializationHelper<Args...>::template deserialized<Result>::doit(
|
||||
deserializer, f));
|
||||
}
|
||||
|
||||
Result (*f)(Args...);
|
||||
};
|
||||
|
||||
/// Partial specialization for function returning a void type. It ignores the
|
||||
/// (absent) return value.
|
||||
template <typename... Args>
|
||||
struct DefaultReplayer<void(Args...)> : public Replayer {
|
||||
DefaultReplayer(void (*f)(Args...)) : Replayer(), f(f) {}
|
||||
|
||||
void operator()(Deserializer &deserializer) const override {
|
||||
DeserializationHelper<Args...>::template deserialized<void>::doit(
|
||||
deserializer, f);
|
||||
deserializer.HandleReplayResultVoid();
|
||||
}
|
||||
|
||||
void (*f)(Args...);
|
||||
};
|
||||
|
||||
/// The registry contains a unique mapping between functions and their ID. The
|
||||
/// IDs can be serialized and deserialized to replay a function. Functions need
|
||||
/// to be registered with the registry for this to work.
|
||||
class Registry {
|
||||
public:
|
||||
Registry() = default;
|
||||
virtual ~Registry() = default;
|
||||
|
||||
/// Register a default replayer for a function.
|
||||
template <typename Signature> void Register(Signature *f) {
|
||||
DoRegister(uintptr_t(f), llvm::make_unique<DefaultReplayer<Signature>>(f));
|
||||
}
|
||||
|
||||
/// Register a replayer that invokes a custom function with the same
|
||||
/// signature as the replayed function.
|
||||
template <typename Signature> void Register(Signature *f, Signature *g) {
|
||||
DoRegister(uintptr_t(f), llvm::make_unique<DefaultReplayer<Signature>>(g));
|
||||
}
|
||||
|
||||
/// Replay functions from a file.
|
||||
bool Replay(const FileSpec &file);
|
||||
|
||||
/// Replay functions from a buffer.
|
||||
bool Replay(llvm::StringRef buffer);
|
||||
|
||||
/// Returns the ID for a given function address.
|
||||
unsigned GetID(uintptr_t addr);
|
||||
|
||||
protected:
|
||||
/// Register the given replayer for a function (and the ID mapping).
|
||||
void DoRegister(uintptr_t RunID, std::unique_ptr<Replayer> replayer);
|
||||
|
||||
private:
|
||||
/// Mapping of function addresses to replayers and their ID.
|
||||
std::map<uintptr_t, std::pair<std::unique_ptr<Replayer>, unsigned>>
|
||||
m_replayers;
|
||||
|
||||
/// Mapping of IDs to replayer instances.
|
||||
std::map<unsigned, Replayer *> m_ids;
|
||||
};
|
||||
|
||||
/// To be used as the "Runtime ID" of a constructor. It also invokes the
|
||||
/// constructor when called.
|
||||
template <typename Signature> struct construct;
|
||||
template <typename Class, typename... Args> struct construct<Class(Args...)> {
|
||||
static Class *doit(Args... args) { return new Class(args...); }
|
||||
};
|
||||
|
||||
/// To be used as the "Runtime ID" of a member function. It also invokes the
|
||||
/// member function when called.
|
||||
template <typename Signature> struct invoke;
|
||||
template <typename Result, typename Class, typename... Args>
|
||||
struct invoke<Result (Class::*)(Args...)> {
|
||||
template <Result (Class::*m)(Args...)> struct method {
|
||||
static Result doit(Class *c, Args... args) { return (c->*m)(args...); }
|
||||
};
|
||||
};
|
||||
|
||||
template <typename Result, typename Class, typename... Args>
|
||||
struct invoke<Result (Class::*)(Args...) const> {
|
||||
template <Result (Class::*m)(Args...) const> struct method_const {
|
||||
static Result doit(Class *c, Args... args) { return (c->*m)(args...); }
|
||||
};
|
||||
};
|
||||
|
||||
template <typename Class, typename... Args>
|
||||
struct invoke<void (Class::*)(Args...)> {
|
||||
template <void (Class::*m)(Args...)> struct method {
|
||||
static void doit(Class *c, Args... args) { (c->*m)(args...); }
|
||||
};
|
||||
};
|
||||
|
||||
/// Maps an object to an index for serialization. Indices are unique and
|
||||
/// incremented for every new object.
|
||||
///
|
||||
|
@ -236,7 +437,6 @@ public:
|
|||
private:
|
||||
unsigned GetIndexForObjectImpl(void *object);
|
||||
|
||||
std::mutex m_mutex;
|
||||
llvm::DenseMap<void *, unsigned> m_mapping;
|
||||
};
|
||||
|
||||
|
@ -296,6 +496,114 @@ private:
|
|||
ObjectToIndex m_tracker;
|
||||
};
|
||||
|
||||
class InstrumentationData {
|
||||
public:
|
||||
InstrumentationData() : m_serializer(nullptr), m_registry(nullptr){};
|
||||
InstrumentationData(Serializer &serializer, Registry ®istry)
|
||||
: m_serializer(&serializer), m_registry(®istry){};
|
||||
|
||||
Serializer &GetSerializer() { return *m_serializer; }
|
||||
Registry &GetRegistry() { return *m_registry; }
|
||||
|
||||
operator bool() { return m_serializer != nullptr && m_registry != nullptr; }
|
||||
|
||||
private:
|
||||
Serializer *m_serializer;
|
||||
Registry *m_registry;
|
||||
};
|
||||
|
||||
/// RAII object that tracks the function invocations and their return value.
|
||||
///
|
||||
/// API calls are only captured when the API boundary is crossed. Once we're in
|
||||
/// the API layer, and another API function is called, it doesn't need to be
|
||||
/// recorded.
|
||||
///
|
||||
/// When a call is recored, its result is always recorded as well, even if the
|
||||
/// function returns a void. For functions that return by value, RecordResult
|
||||
/// should be used. Otherwise a sentinel value (0) will be serialized.
|
||||
class Recorder {
|
||||
public:
|
||||
Recorder(Serializer &serializer, Registry ®istry,
|
||||
llvm::StringRef pretty_func = {});
|
||||
~Recorder();
|
||||
|
||||
/// Records a single function call.
|
||||
template <typename Result, typename... FArgs, typename... RArgs>
|
||||
void Record(Result (*f)(FArgs...), const RArgs &... args) {
|
||||
if (!ShouldCapture())
|
||||
return;
|
||||
|
||||
unsigned id = m_registry.GetID(uintptr_t(f));
|
||||
|
||||
LLDB_LOG(GetLogIfAllCategoriesSet(LIBLLDB_LOG_API), "#{0} '{1}'", id,
|
||||
m_pretty_func);
|
||||
|
||||
m_serializer.SerializeAll(id);
|
||||
m_serializer.SerializeAll(args...);
|
||||
|
||||
if (std::is_class<typename std::remove_pointer<
|
||||
typename std::remove_reference<Result>::type>::type>::value) {
|
||||
m_result_recorded = false;
|
||||
} else {
|
||||
m_serializer.SerializeAll(0);
|
||||
m_result_recorded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Records a single function call.
|
||||
template <typename... Args>
|
||||
void Record(void (*f)(Args...), const Args &... args) {
|
||||
if (!ShouldCapture())
|
||||
return;
|
||||
|
||||
unsigned id = m_registry.GetID(uintptr_t(f));
|
||||
|
||||
LLDB_LOG(GetLogIfAllCategoriesSet(LIBLLDB_LOG_API), "#{0} '{1}'", id,
|
||||
m_pretty_func);
|
||||
|
||||
m_serializer.SerializeAll(id);
|
||||
m_serializer.SerializeAll(args...);
|
||||
|
||||
// Record result.
|
||||
m_serializer.SerializeAll(0);
|
||||
m_result_recorded = true;
|
||||
}
|
||||
|
||||
/// Record the result of a function call.
|
||||
template <typename Result> Result RecordResult(const Result &r) {
|
||||
UpdateBoundary();
|
||||
if (ShouldCapture()) {
|
||||
assert(!m_result_recorded);
|
||||
m_serializer.SerializeAll(r);
|
||||
m_result_recorded = true;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateBoundary() {
|
||||
if (m_local_boundary)
|
||||
g_global_boundary = false;
|
||||
}
|
||||
|
||||
bool ShouldCapture() { return m_local_boundary; }
|
||||
|
||||
Serializer &m_serializer;
|
||||
Registry &m_registry;
|
||||
|
||||
/// Pretty function for logging.
|
||||
llvm::StringRef m_pretty_func;
|
||||
|
||||
/// Whether this function call was the one crossing the API boundary.
|
||||
bool m_local_boundary;
|
||||
|
||||
/// Whether the return value was recorded explicitly.
|
||||
bool m_result_recorded;
|
||||
|
||||
/// Whether we're currently across the API boundary.
|
||||
static bool g_global_boundary;
|
||||
};
|
||||
|
||||
} // namespace repro
|
||||
} // namespace lldb_private
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ add_lldb_library(liblldb SHARED
|
|||
SBProcessInfo.cpp
|
||||
SBQueue.cpp
|
||||
SBQueueItem.cpp
|
||||
SBReproducer.cpp
|
||||
SBSection.cpp
|
||||
SBSourceManager.cpp
|
||||
SBStream.cpp
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
//===-- SBReproducer.cpp ----------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "SBReproducerPrivate.h"
|
||||
|
||||
#include "lldb/API/LLDB.h"
|
||||
#include "lldb/API/SBAddress.h"
|
||||
#include "lldb/API/SBAttachInfo.h"
|
||||
#include "lldb/API/SBBlock.h"
|
||||
#include "lldb/API/SBBreakpoint.h"
|
||||
#include "lldb/API/SBCommandInterpreter.h"
|
||||
#include "lldb/API/SBData.h"
|
||||
#include "lldb/API/SBDebugger.h"
|
||||
#include "lldb/API/SBDeclaration.h"
|
||||
#include "lldb/API/SBError.h"
|
||||
#include "lldb/API/SBFileSpec.h"
|
||||
#include "lldb/API/SBHostOS.h"
|
||||
#include "lldb/API/SBReproducer.h"
|
||||
|
||||
#include "lldb/Host/FileSystem.h"
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
using namespace lldb_private::repro;
|
||||
|
||||
SBRegistry::SBRegistry() {}
|
||||
|
||||
bool SBReproducer::Replay() {
|
||||
repro::Loader *loader = repro::Reproducer::Instance().GetLoader();
|
||||
if (!loader)
|
||||
return false;
|
||||
|
||||
FileSpec file = loader->GetFile<SBInfo>();
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
SBRegistry registry;
|
||||
registry.Replay(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char lldb_private::repro::SBProvider::ID = 0;
|
||||
const char *SBInfo::name = "sbapi";
|
||||
const char *SBInfo::file = "sbapi.bin";
|
|
@ -0,0 +1,72 @@
|
|||
//===-- SBReproducerPrivate.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_API_SBREPRODUCER_PRIVATE_H
|
||||
#define LLDB_API_SBREPRODUCER_PRIVATE_H
|
||||
|
||||
#include "lldb/API/SBReproducer.h"
|
||||
|
||||
#include "lldb/Utility/FileSpec.h"
|
||||
#include "lldb/Utility/Log.h"
|
||||
#include "lldb/Utility/Reproducer.h"
|
||||
#include "lldb/Utility/ReproducerInstrumentation.h"
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
|
||||
#define LLDB_GET_INSTRUMENTATION_DATA() \
|
||||
lldb_private::repro::GetInstrumentationData()
|
||||
|
||||
namespace lldb_private {
|
||||
namespace repro {
|
||||
|
||||
class SBRegistry : public Registry {
|
||||
public:
|
||||
SBRegistry();
|
||||
};
|
||||
|
||||
struct SBInfo {
|
||||
static const char *name;
|
||||
static const char *file;
|
||||
};
|
||||
|
||||
class SBProvider : public Provider<SBProvider> {
|
||||
public:
|
||||
typedef SBInfo info;
|
||||
|
||||
SBProvider(const FileSpec &directory)
|
||||
: Provider(directory),
|
||||
m_stream(directory.CopyByAppendingPathComponent("sbapi.bin").GetPath(),
|
||||
m_ec, llvm::sys::fs::OpenFlags::F_None),
|
||||
m_serializer(m_stream) {}
|
||||
|
||||
Serializer &GetSerializer() { return m_serializer; }
|
||||
Registry &GetRegistry() { return m_registry; }
|
||||
|
||||
static char ID;
|
||||
|
||||
private:
|
||||
std::error_code m_ec;
|
||||
llvm::raw_fd_ostream m_stream;
|
||||
Serializer m_serializer;
|
||||
SBRegistry m_registry;
|
||||
};
|
||||
|
||||
inline InstrumentationData GetInstrumentationData() {
|
||||
if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) {
|
||||
auto &p = g->GetOrCreate<SBProvider>();
|
||||
return {p.GetSerializer(), p.GetRegistry()};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace repro
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif
|
|
@ -34,11 +34,62 @@ template <> const char *Deserializer::Deserialize<const char *>() {
|
|||
return str;
|
||||
}
|
||||
|
||||
bool Registry::Replay(const FileSpec &file) {
|
||||
auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath());
|
||||
if (auto err = error_or_file.getError())
|
||||
return false;
|
||||
|
||||
return Replay((*error_or_file)->getBuffer());
|
||||
}
|
||||
|
||||
bool Registry::Replay(llvm::StringRef buffer) {
|
||||
Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_API);
|
||||
|
||||
Deserializer deserializer(buffer);
|
||||
while (deserializer.HasData(1)) {
|
||||
unsigned id = deserializer.Deserialize<unsigned>();
|
||||
LLDB_LOG(log, "Replaying function #{0}", id);
|
||||
m_ids[id]->operator()(deserializer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Registry::DoRegister(uintptr_t RunID, std::unique_ptr<Replayer> replayer) {
|
||||
const unsigned id = m_replayers.size() + 1;
|
||||
assert(m_replayers.find(RunID) == m_replayers.end());
|
||||
m_replayers[RunID] = std::make_pair(std::move(replayer), id);
|
||||
m_ids[id] = m_replayers[RunID].first.get();
|
||||
}
|
||||
|
||||
unsigned Registry::GetID(uintptr_t addr) {
|
||||
unsigned id = m_replayers[addr].second;
|
||||
assert(id != 0 && "Forgot to add function to registry?");
|
||||
return id;
|
||||
}
|
||||
|
||||
unsigned ObjectToIndex::GetIndexForObjectImpl(void *object) {
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
unsigned index = m_mapping.size() + 1;
|
||||
auto it = m_mapping.find(object);
|
||||
if (it == m_mapping.end())
|
||||
m_mapping[object] = index;
|
||||
return m_mapping[object];
|
||||
}
|
||||
|
||||
Recorder::Recorder(Serializer &serializer, Registry ®istry,
|
||||
llvm::StringRef pretty_func)
|
||||
: m_serializer(serializer), m_registry(registry),
|
||||
m_pretty_func(pretty_func), m_local_boundary(false),
|
||||
m_result_recorded(true) {
|
||||
if (!g_global_boundary) {
|
||||
g_global_boundary = true;
|
||||
m_local_boundary = true;
|
||||
}
|
||||
}
|
||||
|
||||
Recorder::~Recorder() {
|
||||
assert(m_result_recorded && "Did you forget LLDB_RECORD_RESULT?");
|
||||
UpdateBoundary();
|
||||
}
|
||||
|
||||
bool lldb_private::repro::Recorder::g_global_boundary;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "lldb/API/SBDebugger.h"
|
||||
#include "lldb/API/SBHostOS.h"
|
||||
#include "lldb/API/SBLanguageRuntime.h"
|
||||
#include "lldb/API/SBReproducer.h"
|
||||
#include "lldb/API/SBStream.h"
|
||||
#include "lldb/API/SBStringList.h"
|
||||
|
||||
|
@ -888,8 +889,10 @@ main(int argc, char const *argv[])
|
|||
<< '\n';
|
||||
}
|
||||
|
||||
SBInitializerOptions options;
|
||||
// Remember if we're in replay mode for later.
|
||||
bool replay = false;
|
||||
|
||||
SBInitializerOptions options;
|
||||
if (auto *arg = input_args.getLastArg(OPT_capture)) {
|
||||
auto arg_value = arg->getValue();
|
||||
options.SetReproducerPath(arg_value);
|
||||
|
@ -900,6 +903,7 @@ main(int argc, char const *argv[])
|
|||
auto arg_value = arg->getValue();
|
||||
options.SetReplayReproducer(true);
|
||||
options.SetReproducerPath(arg_value);
|
||||
replay = true;
|
||||
}
|
||||
|
||||
SBError error = SBDebugger::Initialize(options);
|
||||
|
@ -909,6 +913,14 @@ main(int argc, char const *argv[])
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (replay) {
|
||||
SBReproducer reproducer;
|
||||
if (!reproducer.Replay()) {
|
||||
WithColor::error() << "something went wrong running the reporducer.\n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SBHostOS::ThreadCreated("<lldb.driver.main-thread>");
|
||||
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "lldb/Utility/ReproducerInstrumentation.h"
|
||||
|
||||
using namespace lldb_private;
|
||||
using namespace lldb_private::repro;
|
||||
|
||||
namespace {
|
||||
struct Foo {
|
||||
int m = 1;
|
||||
};
|
||||
|
@ -40,7 +42,205 @@ struct Pod {
|
|||
unsigned long l = 8;
|
||||
unsigned short m = 9;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class TestingRegistry : public Registry {
|
||||
public:
|
||||
TestingRegistry();
|
||||
};
|
||||
|
||||
static llvm::Optional<Serializer> g_serializer;
|
||||
static llvm::Optional<TestingRegistry> g_registry;
|
||||
|
||||
#define LLDB_GET_INSTRUMENTATION_DATA() \
|
||||
InstrumentationData(*g_serializer, *g_registry)
|
||||
|
||||
class InstrumentedFoo {
|
||||
public:
|
||||
InstrumentedFoo() = default;
|
||||
/// Instrumented methods.
|
||||
/// {
|
||||
InstrumentedFoo(int i);
|
||||
InstrumentedFoo(const InstrumentedFoo &foo);
|
||||
InstrumentedFoo &operator=(const InstrumentedFoo &foo);
|
||||
void A(int a);
|
||||
void B(int &b) const;
|
||||
int C(float *c);
|
||||
int D(const char *d) const;
|
||||
static void E(double e);
|
||||
static int F();
|
||||
void Validate();
|
||||
//// }
|
||||
|
||||
private:
|
||||
int m_a = 0;
|
||||
mutable int m_b = 0;
|
||||
float m_c = 0;
|
||||
mutable std::string m_d = {};
|
||||
static double g_e;
|
||||
static bool g_f;
|
||||
mutable int m_called = 0;
|
||||
};
|
||||
|
||||
class InstrumentedBar {
|
||||
public:
|
||||
/// Instrumented methods.
|
||||
/// {
|
||||
InstrumentedBar();
|
||||
InstrumentedFoo GetInstrumentedFoo();
|
||||
void SetInstrumentedFoo(InstrumentedFoo *foo);
|
||||
void SetInstrumentedFoo(InstrumentedFoo &foo);
|
||||
void Validate();
|
||||
/// }
|
||||
|
||||
private:
|
||||
bool m_get_instrumend_foo_called = false;
|
||||
InstrumentedFoo *m_foo_set_by_ptr = nullptr;
|
||||
InstrumentedFoo *m_foo_set_by_ref = nullptr;
|
||||
};
|
||||
|
||||
double InstrumentedFoo::g_e = 0;
|
||||
bool InstrumentedFoo::g_f = false;
|
||||
|
||||
static std::vector<InstrumentedFoo *> g_foos;
|
||||
static std::vector<InstrumentedBar *> g_bars;
|
||||
|
||||
void ClearObjects() {
|
||||
g_foos.clear();
|
||||
g_bars.clear();
|
||||
}
|
||||
|
||||
void ValidateObjects(size_t expected_foos, size_t expected_bars) {
|
||||
EXPECT_EQ(expected_foos, g_foos.size());
|
||||
EXPECT_EQ(expected_bars, g_bars.size());
|
||||
|
||||
for (auto *foo : g_foos) {
|
||||
foo->Validate();
|
||||
}
|
||||
|
||||
for (auto *bar : g_bars) {
|
||||
bar->Validate();
|
||||
}
|
||||
}
|
||||
|
||||
InstrumentedFoo::InstrumentedFoo(int i) {
|
||||
LLDB_RECORD_CONSTRUCTOR(InstrumentedFoo, (int), i);
|
||||
g_foos.push_back(this);
|
||||
}
|
||||
|
||||
InstrumentedFoo::InstrumentedFoo(const InstrumentedFoo &foo) {
|
||||
LLDB_RECORD_CONSTRUCTOR(InstrumentedFoo, (const InstrumentedFoo &), foo);
|
||||
g_foos.erase(std::remove(g_foos.begin(), g_foos.end(), &foo));
|
||||
g_foos.push_back(this);
|
||||
}
|
||||
|
||||
InstrumentedFoo &InstrumentedFoo::operator=(const InstrumentedFoo &foo) {
|
||||
LLDB_RECORD_METHOD(InstrumentedFoo &,
|
||||
InstrumentedFoo, operator=,(const InstrumentedFoo &), foo);
|
||||
g_foos.erase(std::remove(g_foos.begin(), g_foos.end(), &foo));
|
||||
g_foos.push_back(this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void InstrumentedFoo::A(int a) {
|
||||
LLDB_RECORD_METHOD(void, InstrumentedFoo, A, (int), a);
|
||||
B(a);
|
||||
m_a = a;
|
||||
}
|
||||
|
||||
void InstrumentedFoo::B(int &b) const {
|
||||
LLDB_RECORD_METHOD_CONST(void, InstrumentedFoo, B, (int &), b);
|
||||
m_called++;
|
||||
m_b = b;
|
||||
}
|
||||
|
||||
int InstrumentedFoo::C(float *c) {
|
||||
LLDB_RECORD_METHOD(int, InstrumentedFoo, C, (float *), c);
|
||||
m_c = *c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int InstrumentedFoo::D(const char *d) const {
|
||||
LLDB_RECORD_METHOD_CONST(int, InstrumentedFoo, D, (const char *), d);
|
||||
m_d = std::string(d);
|
||||
return 2;
|
||||
}
|
||||
|
||||
void InstrumentedFoo::E(double e) {
|
||||
LLDB_RECORD_STATIC_METHOD(void, InstrumentedFoo, E, (double), e);
|
||||
g_e = e;
|
||||
}
|
||||
|
||||
int InstrumentedFoo::F() {
|
||||
LLDB_RECORD_STATIC_METHOD_NO_ARGS(int, InstrumentedFoo, F);
|
||||
g_f = true;
|
||||
return 3;
|
||||
}
|
||||
|
||||
void InstrumentedFoo::Validate() {
|
||||
LLDB_RECORD_METHOD_NO_ARGS(void, InstrumentedFoo, Validate);
|
||||
EXPECT_EQ(m_a, 100);
|
||||
EXPECT_EQ(m_b, 200);
|
||||
EXPECT_NEAR(m_c, 300.3, 0.01);
|
||||
EXPECT_EQ(m_d, "bar");
|
||||
EXPECT_NEAR(g_e, 400.4, 0.01);
|
||||
EXPECT_EQ(g_f, true);
|
||||
EXPECT_EQ(2, m_called);
|
||||
}
|
||||
|
||||
InstrumentedBar::InstrumentedBar() {
|
||||
LLDB_RECORD_CONSTRUCTOR_NO_ARGS(InstrumentedBar);
|
||||
g_bars.push_back(this);
|
||||
}
|
||||
|
||||
InstrumentedFoo InstrumentedBar::GetInstrumentedFoo() {
|
||||
LLDB_RECORD_METHOD_NO_ARGS(InstrumentedFoo, InstrumentedBar,
|
||||
GetInstrumentedFoo);
|
||||
m_get_instrumend_foo_called = true;
|
||||
return LLDB_RECORD_RESULT(InstrumentedFoo(0));
|
||||
}
|
||||
|
||||
void InstrumentedBar::SetInstrumentedFoo(InstrumentedFoo *foo) {
|
||||
LLDB_RECORD_METHOD(void, InstrumentedBar, SetInstrumentedFoo,
|
||||
(InstrumentedFoo *), foo);
|
||||
m_foo_set_by_ptr = foo;
|
||||
}
|
||||
|
||||
void InstrumentedBar::SetInstrumentedFoo(InstrumentedFoo &foo) {
|
||||
LLDB_RECORD_METHOD(void, InstrumentedBar, SetInstrumentedFoo,
|
||||
(InstrumentedFoo &), foo);
|
||||
m_foo_set_by_ref = &foo;
|
||||
}
|
||||
|
||||
void InstrumentedBar::Validate() {
|
||||
LLDB_RECORD_METHOD_NO_ARGS(void, InstrumentedBar, Validate);
|
||||
|
||||
EXPECT_TRUE(m_get_instrumend_foo_called);
|
||||
EXPECT_NE(m_foo_set_by_ptr, nullptr);
|
||||
EXPECT_EQ(m_foo_set_by_ptr, m_foo_set_by_ref);
|
||||
}
|
||||
|
||||
TestingRegistry::TestingRegistry() {
|
||||
LLDB_REGISTER_CONSTRUCTOR(InstrumentedFoo, (int i));
|
||||
LLDB_REGISTER_CONSTRUCTOR(InstrumentedFoo, (const InstrumentedFoo &));
|
||||
LLDB_REGISTER_METHOD(InstrumentedFoo &,
|
||||
InstrumentedFoo, operator=,(const InstrumentedFoo &));
|
||||
LLDB_REGISTER_METHOD(void, InstrumentedFoo, A, (int));
|
||||
LLDB_REGISTER_METHOD_CONST(void, InstrumentedFoo, B, (int &));
|
||||
LLDB_REGISTER_METHOD(int, InstrumentedFoo, C, (float *));
|
||||
LLDB_REGISTER_METHOD_CONST(int, InstrumentedFoo, D, (const char *));
|
||||
LLDB_REGISTER_STATIC_METHOD(void, InstrumentedFoo, E, (double));
|
||||
LLDB_REGISTER_STATIC_METHOD(int, InstrumentedFoo, F, ());
|
||||
LLDB_REGISTER_METHOD(void, InstrumentedFoo, Validate, ());
|
||||
|
||||
LLDB_REGISTER_CONSTRUCTOR(InstrumentedBar, ());
|
||||
LLDB_REGISTER_METHOD(InstrumentedFoo, InstrumentedBar, GetInstrumentedFoo,
|
||||
());
|
||||
LLDB_REGISTER_METHOD(void, InstrumentedBar, SetInstrumentedFoo,
|
||||
(InstrumentedFoo *));
|
||||
LLDB_REGISTER_METHOD(void, InstrumentedBar, SetInstrumentedFoo,
|
||||
(InstrumentedFoo &));
|
||||
LLDB_REGISTER_METHOD(void, InstrumentedBar, Validate, ());
|
||||
}
|
||||
|
||||
static const Pod p;
|
||||
|
||||
|
@ -206,3 +406,105 @@ TEST(SerializationRountripTest, SerializeDeserializeObjectReference) {
|
|||
EXPECT_EQ(foo, deserializer.Deserialize<Foo &>());
|
||||
EXPECT_EQ(bar, deserializer.Deserialize<Bar &>());
|
||||
}
|
||||
|
||||
TEST(RecordReplayTest, InstrumentedFoo) {
|
||||
std::string str;
|
||||
llvm::raw_string_ostream os(str);
|
||||
g_registry.emplace();
|
||||
g_serializer.emplace(os);
|
||||
|
||||
{
|
||||
int b = 200;
|
||||
float c = 300.3;
|
||||
double e = 400.4;
|
||||
|
||||
InstrumentedFoo foo(0);
|
||||
foo.A(100);
|
||||
foo.B(b);
|
||||
foo.C(&c);
|
||||
foo.D("bar");
|
||||
InstrumentedFoo::E(e);
|
||||
InstrumentedFoo::F();
|
||||
foo.Validate();
|
||||
}
|
||||
|
||||
ClearObjects();
|
||||
|
||||
TestingRegistry registry;
|
||||
registry.Replay(os.str());
|
||||
|
||||
ValidateObjects(1, 0);
|
||||
}
|
||||
|
||||
TEST(RecordReplayTest, InstrumentedFooSameThis) {
|
||||
std::string str;
|
||||
llvm::raw_string_ostream os(str);
|
||||
g_registry.emplace();
|
||||
g_serializer.emplace(os);
|
||||
|
||||
int b = 200;
|
||||
float c = 300.3;
|
||||
double e = 400.4;
|
||||
|
||||
InstrumentedFoo *foo = new InstrumentedFoo(0);
|
||||
foo->A(100);
|
||||
foo->B(b);
|
||||
foo->C(&c);
|
||||
foo->D("bar");
|
||||
InstrumentedFoo::E(e);
|
||||
InstrumentedFoo::F();
|
||||
foo->Validate();
|
||||
foo->~InstrumentedFoo();
|
||||
|
||||
InstrumentedFoo *foo2 = new (foo) InstrumentedFoo(0);
|
||||
foo2->A(100);
|
||||
foo2->B(b);
|
||||
foo2->C(&c);
|
||||
foo2->D("bar");
|
||||
InstrumentedFoo::E(e);
|
||||
InstrumentedFoo::F();
|
||||
foo2->Validate();
|
||||
delete foo2;
|
||||
|
||||
ClearObjects();
|
||||
|
||||
TestingRegistry registry;
|
||||
registry.Replay(os.str());
|
||||
|
||||
ValidateObjects(2, 0);
|
||||
}
|
||||
|
||||
TEST(RecordReplayTest, InstrumentedBar) {
|
||||
std::string str;
|
||||
llvm::raw_string_ostream os(str);
|
||||
g_registry.emplace();
|
||||
g_serializer.emplace(os);
|
||||
|
||||
{
|
||||
InstrumentedBar bar;
|
||||
InstrumentedFoo foo = bar.GetInstrumentedFoo();
|
||||
|
||||
int b = 200;
|
||||
float c = 300.3;
|
||||
double e = 400.4;
|
||||
|
||||
foo.A(100);
|
||||
foo.B(b);
|
||||
foo.C(&c);
|
||||
foo.D("bar");
|
||||
InstrumentedFoo::E(e);
|
||||
InstrumentedFoo::F();
|
||||
foo.Validate();
|
||||
|
||||
bar.SetInstrumentedFoo(foo);
|
||||
bar.SetInstrumentedFoo(&foo);
|
||||
bar.Validate();
|
||||
}
|
||||
|
||||
ClearObjects();
|
||||
|
||||
TestingRegistry registry;
|
||||
registry.Replay(os.str());
|
||||
|
||||
ValidateObjects(1, 1);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue