Index: include/lldb/API/SBReproducer.h =================================================================== --- /dev/null +++ include/lldb/API/SBReproducer.h @@ -0,0 +1,24 @@ +//===-- SBReproducer.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_H +#define LLDB_API_SBREPRODUCER_H + +#include "lldb/lldb-defines.h" + +namespace lldb { + +class LLDB_API SBReproducer { +public: + bool Replay() const; +}; + +} // namespace lldb + +#endif Index: include/lldb/Utility/ReproducerInstrumentation.h =================================================================== --- /dev/null +++ include/lldb/Utility/ReproducerInstrumentation.h @@ -0,0 +1,504 @@ +//===-- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UTILITY_REPRODUCER_INSTRUMENTATION_H +#define LLDB_UTILITY_REPRODUCER_INSTRUMENTATION_H + +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Logging.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" + +#include +#include + +namespace lldb_private { +namespace repro { + +/// Mapping between serialized indices and their corresponding objects. +/// +/// This class is used during replay to map indices back to in-memory objects. +/// +/// When objects are constructed, they are added to this mapping using +/// AddObjectForIndex. +/// +/// When an object is passed to a function, its index is deserialized and +/// AddObjectForIndex returns the corresponding object. If there is no object +/// for the given index, a nullptr is returend. The latter is valid when custom +/// replay code is in place and the actual object is ignored. +class IndexToObject { +public: + /// Returns an object as a pointer for the given index or nullptr if not + /// present in the map. + template T *GetObjectForIndex(unsigned idx) { + assert(idx != 0 && "Cannot get object for sentinel"); + void *object = GetObjectForIndexImpl(idx); + return static_cast(object); + } + + /// Adds a pointer to an object to the mapping for the given index. + template void AddObjectForIndex(unsigned idx, T *object) { + AddObjectForIndexImpl( + idx, static_cast( + const_cast::type *>(object))); + } + + /// Adds a reference to an object to the mapping for the given index. + template void AddObjectForIndex(unsigned idx, T &object) { + AddObjectForIndexImpl( + idx, static_cast( + const_cast::type *>(&object))); + } + +private: + /// Helper method that does the actual lookup. The void* result is later cast + /// by the caller. + void *GetObjectForIndexImpl(unsigned idx); + + /// Helper method that does the actual insertion. + void AddObjectForIndexImpl(unsigned idx, void *object); + + /// Keeps a mapping between indices and their corresponding object. + llvm::DenseMap m_mapping; +}; + +/// We need to differentiate between pointers to fundamental and +/// non-fundamental types. See the corresponding Deserializer::Read method +/// for the reason why. +struct PointerTag {}; +struct ReferenceTag {}; +struct ValueTag {}; +struct FundamentalPointerTag {}; +struct FundamentalReferenceTag {}; + +/// Return the deserialization tag for the given type T. +template struct serializer_tag { typedef ValueTag type; }; +template struct serializer_tag { + typedef + typename std::conditional::value, + FundamentalPointerTag, PointerTag>::type type; +}; +template struct serializer_tag { + typedef typename std::conditional::value, + FundamentalReferenceTag, ReferenceTag>::type + type; +}; + +/// Deserializes data from a buffer. It is used to deserialize function indices +/// to replay, their arguments and return values. +/// +/// Fundamental types and strings are read by value. Objects are read by their +/// index, which get translated by the IndexToObject mapping maintained in +/// this class. +/// +/// Additional bookkeeping with regards to the IndexToObject is required to +/// deserialize objects. When a constructor is run or an object is returned by +/// value, we need to capture the object and add it to the index together with +/// its index. This is the job of HandleReplayResult(Void). +class Deserializer { +public: + Deserializer(llvm::StringRef buffer) : m_buffer(buffer), m_offset(0) {} + + /// Returns true when the buffer has unread data. + bool HasData(unsigned size) { return m_offset + size <= m_buffer.size(); } + + /// Deserialize and interpret value as T. + template T Deserialize() { + return Read(typename serializer_tag::type()); + } + + /// Store the returned value in the index-to-object mapping. + template void HandleReplayResult(const T &t) { + unsigned result = Deserialize(); + if (std::is_fundamental::value) + return; + // We need to make a copy as the original object might go out of scope. + m_index_to_object.AddObjectForIndex(result, new T(t)); + } + + /// Store the returned value in the index-to-object mapping. + template void HandleReplayResult(T *t) { + unsigned result = Deserialize(); + if (std::is_fundamental::value) + return; + m_index_to_object.AddObjectForIndex(result, t); + } + + /// All returned types are recorded, even when the function returns a void. + /// The latter requires special handling. + void HandleReplayResultVoid() { + unsigned result = Deserialize(); + assert(result == 0); + } + +protected: + IndexToObject &GetIndexToObject() { return m_index_to_object; } + +private: + template T Read(ValueTag) { + assert(HasData(sizeof(T))); + T t; + std::memcpy(reinterpret_cast(&t), &m_buffer.data()[m_offset], + sizeof(T)); + m_offset += sizeof(T); + return t; + } + + template T Read(PointerTag) { + typedef typename std::remove_pointer::type UnderlyingT; + return m_index_to_object.template GetObjectForIndex( + Deserialize()); + } + + template T Read(ReferenceTag) { + typedef typename std::remove_reference::type UnderlyingT; + // If this is a reference to a fundamental type we just read its value. + return *m_index_to_object.template GetObjectForIndex( + Deserialize()); + } + + /// This method is used to parse references to fundamental types. Because + /// they're not recorded in the object table we have serialized their value. + /// We read its value, allocate a copy on the heap, and return a pointer to + /// the copy. + template T Read(FundamentalPointerTag) { + typedef typename std::remove_pointer::type UnderlyingT; + return new UnderlyingT(Deserialize()); + } + + /// This method is used to parse references to fundamental types. Because + /// they're not recorded in the object table we have serialized their value. + /// We read its value, allocate a copy on the heap, and return a reference to + /// the copy. + template T Read(FundamentalReferenceTag) { + // If this is a reference to a fundamental type we just read its value. + typedef typename std::remove_reference::type UnderlyingT; + return *(new UnderlyingT(Deserialize())); + } + + /// Mapping of indices to objects. + IndexToObject m_index_to_object; + + /// Buffer containing the serialized data. + llvm::StringRef m_buffer; + + /// Current offset in the buffer. + uint32_t m_offset; +}; + +/// Partial specialization for C-style strings. We read the string value +/// instead of treating it as pointer. +template <> const char *Deserializer::Deserialize(); +template <> char *Deserializer::Deserialize(); + +/// Helpers to auto-synthesize function replay code. It deserializes the replay +/// function's arguments one by one and finally calls the corresponding +/// function. +template struct DeserializationHelper; + +template +struct DeserializationHelper { + template struct deserialized { + static Result doit(Deserializer &deserializer, + Result (*f)(Deserialized..., Head, Tail...), + Deserialized... d) { + return DeserializationHelper:: + template deserialized::doit( + deserializer, f, d..., deserializer.Deserialize()); + } + }; +}; + +template <> struct DeserializationHelper<> { + template struct deserialized { + static Result doit(Deserializer &deserializer, Result (*f)(Deserialized...), + Deserialized... d) { + return f(d...); + } + }; +}; + +/// 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 struct DefaultReplayer; +template +struct DefaultReplayer : public Replayer { + DefaultReplayer(Result (*f)(Args...)) : Replayer(), f(f) {} + + void operator()(Deserializer &deserializer) const override { + deserializer.HandleReplayResult( + DeserializationHelper::template deserialized::doit( + deserializer, f)); + } + + Result (*f)(Args...); +}; + +/// Partial specialization for function returning a void type. It ignores the +/// (absent) return value. +template +struct DefaultReplayer : public Replayer { + DefaultReplayer(void (*f)(Args...)) : Replayer(), f(f) {} + + void operator()(Deserializer &deserializer) const override { + DeserializationHelper::template deserialized::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 void Register(Signature *f) { + DoRegister(uintptr_t(f), new DefaultReplayer(f)); + } + + /// Register a replayer that invokes a custom function with the same + /// signature as the replayed function. + template void Register(Signature *f, Signature *g) { + DoRegister(uintptr_t(f), new DefaultReplayer(g)); + } + + /// Replay functions from a file. + bool Replay(const FileSpec &file); + + /// Returns the ID for a given function address. + unsigned GetID(uintptr_t addr); + +protected: + /// Initialize the registry by registering function. + virtual void Init() = 0; + + /// Register the given replayer for a function (and the ID mapping). + void DoRegister(uintptr_t RunID, Replayer *replayer); + +private: + /// Mapping of function addresses to replayers and their ID. + std::map> m_sbreplayers; + + /// Mapping of IDs to replayer instances. + std::map m_ids; +}; + +/// To be used as the "Runtime ID" of a constructor. It also invokes the +/// constructor when called. +template struct construct; +template struct construct { + 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 struct invoke; +template +struct invoke { + template struct method { + static Result doit(Class *c, Args... args) { return (c->*m)(args...); } + }; +}; + +template +struct invoke { + template struct method_const { + static Result doit(Class *c, Args... args) { return (c->*m)(args...); } + }; +}; + +template +struct invoke { + template 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. +/// +/// Indices start at 1 in order to differentiate with an invalid index (0) in +/// the serialized buffer. +class ObjectToIndex { +public: + template unsigned GetIndexForObject(T *t) { + return GetIndexForObjectImpl((void *)t); + } + +private: + unsigned GetIndexForObjectImpl(void *object); + + std::mutex m_mutex; + llvm::DenseMap m_mapping; +}; + +/// Serializes functions, their arguments and their return type to a stream. +class Serializer { +public: + Serializer(llvm::raw_ostream &stream = llvm::outs()) : m_stream(stream) {} + + /// Recursively serialize all the given arguments. + template + void SerializeAll(const Head &head, const Tail &... tail) { + Serialize(head); + SerializeAll(tail...); + } + + void SerializeAll() {} + +private: + /// Serialize pointers. We need to differentiate between pointers to + /// fundamental types (in which case we serialize its value) and pointer to + /// objects (in which case we serialize their index). + template void Serialize(T *t) { + if (std::is_fundamental::value) { + Serialize(*t); + } else { + unsigned idx = m_tracker.GetIndexForObject(t); + Serialize(idx); + } + } + + /// Serialize references. We need to differentiate between references to + /// fundamental types (in which case we serialize its value) and references + /// to objects (in which case we serialize their index). + template void Serialize(T &t) { + if (std::is_fundamental::value) { + m_stream.write(reinterpret_cast(&t), sizeof(T)); + } else { + unsigned idx = m_tracker.GetIndexForObject(&t); + Serialize(idx); + } + } + + void Serialize(void *v) { + // Do nothing. + } + + void Serialize(const char *t) { + m_stream << t; + m_stream.write(0x0); + } + + /// Serialization stream. + llvm::raw_ostream &m_stream; + + /// Mapping of objects to indices. + ObjectToIndex m_tracker; +}; + +/// 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 + void Record(Result (*f)(FArgs...), const RArgs &... args) { + if (!ShouldCapture()) + return; + + unsigned id = m_registry.GetID(uintptr_t(f)); + + if (Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_API)) + LLDB_LOG(log, "#%u '%s'", id, m_pretty_func); + + m_serializer.SerializeAll(id); + m_serializer.SerializeAll(args...); + + m_expect_result_recored = true; + } + + /// Records a single function call. + template + void Record(void (*f)(Args...), const Args &... args) { + if (!ShouldCapture()) + return; + + unsigned id = m_registry.GetID(uintptr_t(f)); + + if (Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_API)) + LLDB_LOG(log, "#%u '%s'", id, m_pretty_func); + + m_serializer.SerializeAll(id); + m_serializer.SerializeAll(args...); + + m_expect_result_recored = false; + } + + /// Record the result of a function call. + template Result RecordResult(const Result &r) { + UpdateBoundary(); + if (ShouldCapture()) { + m_serializer.SerializeAll(r); + m_result_recorded = true; + } + return r; + } + + /// Serialize an omitted return value. + void RecordOmittedResult(); + +private: + void UpdateBoundary() { + if (m_local_boundary) + g_global_boundary = false; + } + + bool ShouldCapture() { return m_local_boundary; } + + /// Pretty function for logging. + llvm::StringRef m_pretty_func; + + /// The serializer is set from the reproducer framework. If the serializer is + /// not set, we're not in recording mode. + Serializer &m_serializer; + + // The registry with a mapping between function addresses and indices. + Registry &m_registry; + + /// 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 expect the result to be recorded. + bool m_expect_result_recored; + + /// Whether we're currently across the API boundary. + static bool g_global_boundary; +}; + +} // namespace repro +} // namespace lldb_private + +#endif // LLDB_UTILITY_REPRODUCER_INSTRUMENTATION_H Index: source/API/CMakeLists.txt =================================================================== --- source/API/CMakeLists.txt +++ source/API/CMakeLists.txt @@ -50,6 +50,7 @@ SBProcessInfo.cpp SBQueue.cpp SBQueueItem.cpp + SBReproducer.cpp SBSection.cpp SBSourceManager.cpp SBStream.cpp Index: source/API/SBReproducer.cpp =================================================================== --- /dev/null +++ source/API/SBReproducer.cpp @@ -0,0 +1,80 @@ +//===-- 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; + +#define SB_REGISTER_CONSTRUCTOR(Class, Signature) \ + Register(&construct::doit) +#define SB_REGISTER_METHOD(Result, Class, Method, Signature) \ + Register(&invoke::method<&Class::Method>::doit) +#define SB_REGISTER_METHOD_CONST(Result, Class, Method, Signature) \ + Register(&invoke::method_const<&Class::Method>::doit) +#define SB_REGISTER_STATIC_METHOD(Result, Class, Method, Signature) \ + Register(static_cast(&Class::Method)) + +static void SetInputFileHandleRedirect(SBDebugger *t, FILE *, bool) { + repro::Loader *loader = repro::Reproducer::Instance().GetLoader(); + if (!loader) + return; + + FileSpec fs = loader->GetFile(); + if (!fs) + return; + + FILE *f = FileSystem::Instance().Fopen(fs.GetPath().c_str(), "r"); + if (f == nullptr) + return; + t->SetInputFileHandle(f, true); +} + +static void SetFileHandleRedirect(SBDebugger *, FILE *, bool) { + // Do nothing. +} + +void SBRegistry::Init() {} + +bool SBReproducer::Replay() const { + repro::Loader *loader = repro::Reproducer::Instance().GetLoader(); + if (!loader) + return false; + + FileSpec file = loader->GetFile(); + 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"; Index: source/API/SBReproducerPrivate.h =================================================================== --- /dev/null +++ source/API/SBReproducerPrivate.h @@ -0,0 +1,151 @@ +//===-- 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" + +#include + +namespace lldb_private { +namespace repro { + +class SBRegistry : public Registry { + virtual void Init() override; +}; + +struct SBInfo { + static const char *name; + static const char *file; +}; + +class SBProvider : public Provider { +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; +}; + +} // namespace repro +} // namespace lldb_private + +#define SB_RECORD_CONSTRUCTOR(Class, Signature, ...) \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + lldb_private::repro::Recorder sb_recorder( \ + g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder.Record(&lldb_private::repro::construct::doit, \ + __VA_ARGS__); \ + sb_recorder.RecordResult(this); \ + } + +#define SB_RECORD_CONSTRUCTOR_NO_ARGS(Class) \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + lldb_private::repro::Recorder sb_recorder( \ + g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder.Record(&lldb_private::repro::construct::doit); \ + sb_recorder.RecordResult(this); \ + } + +#define SB_RECORD_METHOD(Result, Class, Method, Signature, ...) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record( \ + &lldb_private::repro::invoke::method<&Class::Method>::doit, \ + this, __VA_ARGS__); \ + } + +#define SB_RECORD_METHOD_CONST(Result, Class, Method, Signature, ...) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record( \ + &lldb_private::repro::invoke::method_const<&Class::Method>::doit, \ + this, __VA_ARGS__); \ + } + +#define SB_RECORD_METHOD_NO_ARGS(Result, Class, Method) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(&lldb_private::repro::invoke::method<&Class::Method>::doit, \ + this); \ + } + +#define SB_RECORD_METHOD_CONST_NO_ARGS(Result, Class, Method) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record( \ + &lldb_private::repro::invoke::method_const<&Class::Method>::doit, \ + this); \ + } + +#define SB_RECORD_STATIC_METHOD(Result, Class, Method, Signature, ...) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(static_cast(&Class::Method), \ + __VA_ARGS__); \ + } + +#define SB_RECORD_STATIC_METHOD_NO_ARGS(Result, Class, Method) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(static_cast(&Class::Method)); \ + } + +#define SB_RECORD_RESULT(Result) \ + sb_recorder ? sb_recorder->RecordResult(Result) : Result; + +#endif Index: source/Utility/CMakeLists.txt =================================================================== --- source/Utility/CMakeLists.txt +++ source/Utility/CMakeLists.txt @@ -67,6 +67,7 @@ RegisterValue.cpp RegularExpression.cpp Reproducer.cpp + ReproducerInstrumentation.cpp Scalar.cpp SelectHelper.cpp SharingPtr.cpp Index: source/Utility/ReproducerInstrumentation.cpp =================================================================== --- /dev/null +++ source/Utility/ReproducerInstrumentation.cpp @@ -0,0 +1,104 @@ +//===-- ReproducerInstrumentation.cpp ---------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "lldb/Utility/ReproducerInstrumentation.h" +#include "lldb/Utility/Reproducer.h" + +using namespace lldb_private; +using namespace lldb_private::repro; + +void *IndexToObject::GetObjectForIndexImpl(unsigned idx) { + return m_mapping.lookup(idx); +} + +void IndexToObject::AddObjectForIndexImpl(unsigned idx, void *object) { + assert(idx != 0 && "Cannot add object for sentinel"); + m_mapping[idx] = object; +} + +template <> char *Deserializer::Deserialize() { + return const_cast(Deserialize()); +} + +template <> const char *Deserializer::Deserialize() { + auto pos = m_buffer.find('\0', m_offset); + if (pos == llvm::StringRef::npos) + return nullptr; + size_t begin = m_offset; + m_offset = pos + 1; + return m_buffer.data() + begin; +} + +bool Registry::Replay(const FileSpec &file) { + auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath()); + if (auto err = error_or_file.getError()) + return false; + + Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_API); + + Deserializer deserializer((*error_or_file)->getBuffer()); + while (deserializer.HasData(1)) { + unsigned id = deserializer.Deserialize(); + if (log) + LLDB_LOG(log, "Replaying function #%u", id); + m_ids[id]->operator()(deserializer); + } + + return true; +} + +void Registry::DoRegister(uintptr_t RunID, Replayer *replayer) { + unsigned id = m_sbreplayers.size() + 1; + assert(m_sbreplayers.find(RunID) == m_sbreplayers.end()); + m_sbreplayers[RunID] = std::make_pair(replayer, id); + m_ids[id] = replayer; +} + +unsigned Registry::GetID(uintptr_t addr) { + unsigned id = m_sbreplayers[addr].second; + assert(id != 0); + return id; +} + +unsigned ObjectToIndex::GetIndexForObjectImpl(void *object) { + std::lock_guard 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_pretty_func(pretty_func), m_serializer(serializer), + m_registry(registry), m_local_boundary(false), m_result_recorded(false), + m_expect_result_recored(false) { + if (!g_global_boundary) { + g_global_boundary = true; + m_local_boundary = true; + } +} + +Recorder::~Recorder() { + UpdateBoundary(); + RecordOmittedResult(); +} + +void Recorder::RecordOmittedResult() { + assert(!m_expect_result_recored && "Did you forget SB_RECORD_RESULT?"); + if (m_result_recorded) + return; + if (!ShouldCapture()) + return; + + m_serializer.SerializeAll(0); + m_result_recorded = true; +} + +bool lldb_private::repro::Recorder::g_global_boundary; Index: tools/driver/Driver.cpp =================================================================== --- tools/driver/Driver.cpp +++ tools/driver/Driver.cpp @@ -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" @@ -909,6 +910,11 @@ return 1; } + SBReproducer reproducer; + if (reproducer.Replay()) { + return 0; + } + SBHostOS::ThreadCreated(""); signal(SIGINT, sigint_handler); Index: unittests/Utility/CMakeLists.txt =================================================================== --- unittests/Utility/CMakeLists.txt +++ unittests/Utility/CMakeLists.txt @@ -20,6 +20,7 @@ PredicateTest.cpp RegisterValueTest.cpp ReproducerTest.cpp + ReproducerInstrumentationTest.cpp ScalarTest.cpp StateTest.cpp StatusTest.cpp Index: unittests/Utility/ReproducerInstrumentationTest.cpp =================================================================== --- /dev/null +++ unittests/Utility/ReproducerInstrumentationTest.cpp @@ -0,0 +1,209 @@ +//===-- ReproducerTest.cpp ------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "lldb/Utility/ReproducerInstrumentation.h" + +using namespace lldb_private; +using namespace lldb_private::repro; + +namespace { +struct Foo {}; +struct Bar {}; + +struct Pod { + bool a = true; + bool b = false; + char c = 'a'; + float d = 1.1; + int e = 2; + long long f = 3; + long g = 4; + short h = 5; + unsigned char i = 'b'; + unsigned int j = 6; + unsigned long long k = 7; + unsigned long l = 8; + unsigned short m = 9; +}; + +class TestingDeserializer : public Deserializer { +public: + using Deserializer::Deserializer; + using Deserializer::GetIndexToObject; +}; +} // namespace + +static const Pod p; + +TEST(IndexToObjectTest, ObjectForIndex) { + IndexToObject index_to_object; + Foo foo; + Bar bar; + + EXPECT_EQ(nullptr, index_to_object.GetObjectForIndex(1)); + EXPECT_EQ(nullptr, index_to_object.GetObjectForIndex(2)); + + index_to_object.AddObjectForIndex(1, foo); + index_to_object.AddObjectForIndex(2, &bar); + + EXPECT_EQ(&foo, index_to_object.GetObjectForIndex(1)); + EXPECT_EQ(&bar, index_to_object.GetObjectForIndex(2)); +} + +TEST(DeserializerTest, HasData) { + { + Deserializer deserializer(""); + EXPECT_FALSE(deserializer.HasData(1)); + } + + { + Deserializer deserializer("a"); + EXPECT_TRUE(deserializer.HasData(1)); + EXPECT_FALSE(deserializer.HasData(2)); + } +} + +TEST(SerializationRountripTest, SerializeDeserializePod) { + std::string str; + llvm::raw_string_ostream os(str); + + Serializer serializer(os); + serializer.SerializeAll(p.a, p.b, p.c, p.d, p.e, p.f, p.g, p.h, p.i, p.j, p.k, + p.l, p.m); + + llvm::StringRef buffer(os.str()); + Deserializer deserializer(buffer); + + EXPECT_EQ(p.a, deserializer.Deserialize()); + EXPECT_EQ(p.b, deserializer.Deserialize()); + EXPECT_EQ(p.c, deserializer.Deserialize()); + EXPECT_EQ(p.d, deserializer.Deserialize()); + EXPECT_EQ(p.e, deserializer.Deserialize()); + EXPECT_EQ(p.f, deserializer.Deserialize()); + EXPECT_EQ(p.g, deserializer.Deserialize()); + EXPECT_EQ(p.h, deserializer.Deserialize()); + EXPECT_EQ(p.i, deserializer.Deserialize()); + EXPECT_EQ(p.j, deserializer.Deserialize()); + EXPECT_EQ(p.k, deserializer.Deserialize()); + EXPECT_EQ(p.l, deserializer.Deserialize()); + EXPECT_EQ(p.m, deserializer.Deserialize()); +} + +TEST(SerializationRountripTest, SerializeDeserializePodPointers) { + std::string str; + llvm::raw_string_ostream os(str); + + Serializer serializer(os); + serializer.SerializeAll(&p.a, &p.b, &p.c, &p.d, &p.e, &p.f, &p.g, &p.h, &p.i, + &p.j, &p.k, &p.l, &p.m); + + llvm::StringRef buffer(os.str()); + Deserializer deserializer(buffer); + + EXPECT_EQ(p.a, *deserializer.Deserialize()); + EXPECT_EQ(p.b, *deserializer.Deserialize()); + EXPECT_EQ(p.c, *deserializer.Deserialize()); + EXPECT_EQ(p.d, *deserializer.Deserialize()); + EXPECT_EQ(p.e, *deserializer.Deserialize()); + EXPECT_EQ(p.f, *deserializer.Deserialize()); + EXPECT_EQ(p.g, *deserializer.Deserialize()); + EXPECT_EQ(p.h, *deserializer.Deserialize()); + EXPECT_EQ(p.i, *deserializer.Deserialize()); + EXPECT_EQ(p.j, *deserializer.Deserialize()); + EXPECT_EQ(p.k, *deserializer.Deserialize()); + EXPECT_EQ(p.l, *deserializer.Deserialize()); + EXPECT_EQ(p.m, *deserializer.Deserialize()); +} + +TEST(SerializationRountripTest, SerializeDeserializePodReferences) { + std::string str; + llvm::raw_string_ostream os(str); + + Serializer serializer(os); + serializer.SerializeAll(p.a, p.b, p.c, p.d, p.e, p.f, p.g, p.h, p.i, p.j, p.k, + p.l, p.m); + + llvm::StringRef buffer(os.str()); + Deserializer deserializer(buffer); + + EXPECT_EQ(p.a, deserializer.Deserialize()); + EXPECT_EQ(p.b, deserializer.Deserialize()); + EXPECT_EQ(p.c, deserializer.Deserialize()); + EXPECT_EQ(p.d, deserializer.Deserialize()); + EXPECT_EQ(p.e, deserializer.Deserialize()); + EXPECT_EQ(p.f, deserializer.Deserialize()); + EXPECT_EQ(p.g, deserializer.Deserialize()); + EXPECT_EQ(p.h, deserializer.Deserialize()); + EXPECT_EQ(p.i, deserializer.Deserialize()); + EXPECT_EQ(p.j, deserializer.Deserialize()); + EXPECT_EQ(p.k, deserializer.Deserialize()); + EXPECT_EQ(p.l, deserializer.Deserialize()); + EXPECT_EQ(p.m, deserializer.Deserialize()); +} + +TEST(SerializationRountripTest, SerializeDeserializeCString) { + const char *cstr = "string"; + + std::string str; + llvm::raw_string_ostream os(str); + + Serializer serializer(os); + serializer.SerializeAll(cstr); + + llvm::StringRef buffer(os.str()); + Deserializer deserializer(buffer); + + EXPECT_STREQ(cstr, deserializer.Deserialize()); +} + +TEST(SerializationRountripTest, SerializeDeserializeObjectPointer) { + Foo foo; + Bar bar; + + std::string str; + llvm::raw_string_ostream os(str); + + Serializer serializer(os); + serializer.SerializeAll(&foo, &bar); + + llvm::StringRef buffer(os.str()); + TestingDeserializer deserializer(buffer); + + // Add objects to index. + IndexToObject &index_to_object = deserializer.GetIndexToObject(); + index_to_object.AddObjectForIndex(1, foo); + index_to_object.AddObjectForIndex(2, bar); + + EXPECT_EQ(&foo, deserializer.Deserialize()); + EXPECT_EQ(&bar, deserializer.Deserialize()); +} + +TEST(SerializationRountripTest, SerializeDeserializeObjectReference) { + Foo foo; + Bar bar; + + std::string str; + llvm::raw_string_ostream os(str); + + Serializer serializer(os); + serializer.SerializeAll(foo, bar); + + llvm::StringRef buffer(os.str()); + TestingDeserializer deserializer(buffer); + + // Add objects to index. + IndexToObject &index_to_object = deserializer.GetIndexToObject(); + index_to_object.AddObjectForIndex(1, foo); + index_to_object.AddObjectForIndex(2, bar); + + EXPECT_EQ(&foo, &deserializer.Deserialize()); + EXPECT_EQ(&bar, &deserializer.Deserialize()); +}