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