Skip to content

Commit

Permalink
[Reproducers] SBReproducer framework: Capture & Replay
Browse files Browse the repository at this point in the history
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
  • Loading branch information
JDevlieghere committed Feb 6, 2019
1 parent 7c77044 commit 58947cf
Show file tree
Hide file tree
Showing 8 changed files with 830 additions and 10 deletions.
23 changes: 23 additions & 0 deletions lldb/include/lldb/API/SBReproducer.h
@@ -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
320 changes: 314 additions & 6 deletions lldb/include/lldb/Utility/ReproducerInstrumentation.h
@@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -139,9 +235,6 @@ class Deserializer {
assert(result == 0);
}

protected:
IndexToObject &GetIndexToObject() { return m_index_to_object; }

private:
template <typename T> T Read(ValueTag) {
assert(HasData(sizeof(T)));
Expand Down Expand Up @@ -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.
///
Expand All @@ -236,7 +437,6 @@ class ObjectToIndex {
private:
unsigned GetIndexForObjectImpl(void *object);

std::mutex m_mutex;
llvm::DenseMap<void *, unsigned> m_mapping;
};

Expand Down Expand Up @@ -296,6 +496,114 @@ class Serializer {
ObjectToIndex m_tracker;
};

class InstrumentationData {
public:
InstrumentationData() : m_serializer(nullptr), m_registry(nullptr){};
InstrumentationData(Serializer &serializer, Registry &registry)
: m_serializer(&serializer), m_registry(&registry){};

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 &registry,
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

Expand Down
1 change: 1 addition & 0 deletions lldb/source/API/CMakeLists.txt
Expand Up @@ -50,6 +50,7 @@ add_lldb_library(liblldb SHARED
SBProcessInfo.cpp
SBQueue.cpp
SBQueueItem.cpp
SBReproducer.cpp
SBSection.cpp
SBSourceManager.cpp
SBStream.cpp
Expand Down

0 comments on commit 58947cf

Please sign in to comment.