Index: lldb/include/lldb/Utility/ReproducerInstrumentation.h =================================================================== --- lldb/include/lldb/Utility/ReproducerInstrumentation.h +++ lldb/include/lldb/Utility/ReproducerInstrumentation.h @@ -333,6 +333,8 @@ } template const T &HandleReplayResult(const T &t) { + unsigned idx = Deserialize(); + CheckIndex(idx); unsigned result = Deserialize(); if (is_trivially_serializable::value) return t; @@ -342,6 +344,8 @@ /// Store the returned value in the index-to-object mapping. template T &HandleReplayResult(T &t) { + unsigned idx = Deserialize(); + CheckIndex(idx); unsigned result = Deserialize(); if (is_trivially_serializable::value) return t; @@ -351,6 +355,8 @@ /// Store the returned value in the index-to-object mapping. template T *HandleReplayResult(T *t) { + unsigned idx = Deserialize(); + CheckIndex(idx); unsigned result = Deserialize(); if (is_trivially_serializable::value) return t; @@ -360,6 +366,8 @@ /// All returned types are recorded, even when the function returns a void. /// The latter requires special handling. void HandleReplayResultVoid() { + unsigned idx = Deserialize(); + CheckIndex(idx); unsigned result = Deserialize(); assert(result == 0); (void)result; @@ -369,6 +377,8 @@ return m_index_to_object.GetAllObjects(); } + void SetExpectedIndex(unsigned idx) { m_expected_idx = idx; } + private: template T Read(ValueTag) { assert(HasData(sizeof(T))); @@ -410,11 +420,17 @@ return *(new UnderlyingT(Deserialize())); } + /// Verify that the given index matches the expected index if set. + void CheckIndex(unsigned idx) const; + /// Mapping of indices to objects. IndexToObject m_index_to_object; /// Buffer containing the serialized data. llvm::StringRef m_buffer; + + /// Expected index of the result. + llvm::Optional m_expected_idx; }; /// Partial specialization for C-style strings. We read the string value @@ -741,6 +757,7 @@ template void Record(Serializer &serializer, Registry ®istry, Result (*f)(FArgs...), const RArgs &... args) { + std::lock_guard lock(g_mutex); m_serializer = &serializer; if (!ShouldCapture()) return; @@ -751,6 +768,7 @@ Log(id); #endif + serializer.SerializeAll(m_index); serializer.SerializeAll(id); serializer.SerializeAll(args...); @@ -758,6 +776,7 @@ typename std::remove_reference::type>::type>::value) { m_result_recorded = false; } else { + serializer.SerializeAll(m_index); serializer.SerializeAll(0); m_result_recorded = true; } @@ -771,16 +790,19 @@ if (!ShouldCapture()) return; + std::lock_guard lock(g_mutex); unsigned id = registry.GetID(uintptr_t(f)); #ifdef LLDB_REPRO_INSTR_TRACE Log(id); #endif + serializer.SerializeAll(m_index); serializer.SerializeAll(id); serializer.SerializeAll(args...); // Record result. + serializer.SerializeAll(m_index); serializer.SerializeAll(0); m_result_recorded = true; } @@ -806,7 +828,9 @@ if (update_boundary) UpdateBoundary(); if (m_serializer && ShouldCapture()) { + std::lock_guard lock(g_mutex); assert(!m_result_recorded); + m_serializer->SerializeAll(m_index); m_serializer->SerializeAll(r); m_result_recorded = true; } @@ -846,6 +870,8 @@ static void PrivateThread() { g_global_boundary = true; } private: + static uint64_t GetNextIndex() { return g_index++; } + template friend struct replay; void UpdateBoundary() { if (m_local_boundary) @@ -871,8 +897,17 @@ /// Whether the return value was recorded explicitly. bool m_result_recorded; + /// The current index. + unsigned m_index; + /// Whether we're currently across the API boundary. static thread_local bool g_global_boundary; + + /// Global mutex to protect concurrent access. + static std::mutex g_mutex; + + /// Global index. + static std::atomic g_index; }; /// To be used as the "Runtime ID" of a constructor. It also invokes the Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -33,6 +33,7 @@ #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Target/Thread.h" #include "lldb/Target/ThreadPlan.h" +#include "lldb/Utility/ReproducerInstrumentation.h" #include "lldb/Utility/Timer.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" Index: lldb/source/Utility/ReproducerInstrumentation.cpp =================================================================== --- lldb/source/Utility/ReproducerInstrumentation.cpp +++ lldb/source/Utility/ReproducerInstrumentation.cpp @@ -8,6 +8,7 @@ #include "lldb/Utility/ReproducerInstrumentation.h" #include "lldb/Utility/Reproducer.h" +#include #include #include #include @@ -84,6 +85,15 @@ return r; } +void Deserializer::CheckIndex(unsigned idx) const { + if (m_expected_idx && *m_expected_idx != idx) + llvm::report_fatal_error( + "The result does not match the preceding " + "function. This is probably the result of concurrent " + "use of the SB API during capture, which is currently not " + "supported."); +} + bool Registry::Replay(const FileSpec &file) { auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath()); if (auto err = error_or_file.getError()) @@ -107,6 +117,7 @@ setvbuf(stdout, nullptr, _IONBF, 0); while (deserializer.HasData(1)) { + unsigned idx = deserializer.Deserialize(); unsigned id = deserializer.Deserialize(); #ifndef LLDB_REPRO_INSTR_TRACE @@ -115,6 +126,7 @@ llvm::errs() << "Replaying " << id << ": " << GetSignature(id) << "\n"; #endif + deserializer.SetExpectedIndex(idx); GetReplayer(id)->operator()(deserializer); } @@ -181,21 +193,23 @@ Recorder::Recorder() : m_serializer(nullptr), m_pretty_func(), m_pretty_args(), - m_local_boundary(false), m_result_recorded(true) { + m_local_boundary(false), m_result_recorded(true), + m_index(std::numeric_limits::max()) { if (!g_global_boundary) { g_global_boundary = true; m_local_boundary = true; + m_index = GetNextIndex(); } } Recorder::Recorder(llvm::StringRef pretty_func, std::string &&pretty_args) : m_serializer(nullptr), m_pretty_func(pretty_func), m_pretty_args(pretty_args), m_local_boundary(false), - m_result_recorded(true) { + m_result_recorded(true), m_index(std::numeric_limits::max()) { if (!g_global_boundary) { g_global_boundary = true; m_local_boundary = true; - + m_index = GetNextIndex(); LLDB_LOG(GetLogIfAllCategoriesSet(LIBLLDB_LOG_API), "{0} ({1})", m_pretty_func, m_pretty_args); } @@ -228,3 +242,5 @@ } thread_local bool lldb_private::repro::Recorder::g_global_boundary = false; +std::atomic lldb_private::repro::Recorder::g_index; +std::mutex lldb_private::repro::Recorder::g_mutex; Index: lldb/test/API/functionalities/reproducers/concurrency/Makefile =================================================================== --- /dev/null +++ lldb/test/API/functionalities/reproducers/concurrency/Makefile @@ -0,0 +1,5 @@ +MAKE_DSYM := NO +ENABLE_THREADS := YES +CXX_SOURCES := driver.cpp + +include Makefile.rules Index: lldb/test/API/functionalities/reproducers/concurrency/TestReproducerConcurrency.py =================================================================== --- /dev/null +++ lldb/test/API/functionalities/reproducers/concurrency/TestReproducerConcurrency.py @@ -0,0 +1,53 @@ +from __future__ import print_function + +import os + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestReproducerConcurrency(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + @skipIfNoSBHeaders + @skipIfWindows + def test_multiple_debuggers(self): + env = {self.dylibPath: self.getLLDBLibraryEnvVal()} + + driver = self.getBuildArtifact("driver") + reproducer = self.getBuildArtifact("reproducer") + + if os.path.exists(reproducer): + try: + shutil.rmtree(reproducer) + except OSError: + pass + + self.buildDriver('driver.cpp', driver) + self.signBinary(driver) + + if self.TraceOn(): + check_call([driver, reproducer], env=env) + else: + with open(os.devnull, 'w') as fnull: + check_call([driver, reproducer], + env=env, + stdout=fnull, + stderr=fnull) + + # Check that replay fails with the expected error. + replay = subprocess.Popen( + [lldbtest_config.lldbExec, '-replay', reproducer], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + _, errs = replay.communicate() + errs = errs.decode('utf-8') + self.assertNotEqual(replay.returncode, 0) + self.assertIn('The result does not match the preceding function.', + errs) Index: lldb/test/API/functionalities/reproducers/concurrency/driver.cpp =================================================================== --- /dev/null +++ lldb/test/API/functionalities/reproducers/concurrency/driver.cpp @@ -0,0 +1,55 @@ +#include "lldb/API/LLDB.h" +#include "lldb/API/SBCommandInterpreter.h" +#include "lldb/API/SBCommandReturnObject.h" +#include "lldb/API/SBDebugger.h" + +#include +#include +#include + +#define THREADS 10 + +using namespace lldb; + +void f(uint64_t idx) { + SBDebugger debugger = lldb::SBDebugger::Create(false); + debugger.GetNumPlatforms(); + debugger.GetNumAvailablePlatforms(); + SBTarget target = debugger.GetDummyTarget(); + SBCommandInterpreter interpreter = debugger.GetCommandInterpreter(); + SBDebugger::Destroy(debugger); +} + +int main(int argc, char **argv) { + if (argc < 2) { + std::cout << "missing argument: reproducer directory"; + return 1; + } + + if (const char *error = SBReproducer::Capture(argv[1])) { + std::cout << "reproducer capture failed: " << error << '\n'; + return 1; + } + + SBReproducer::SetAutoGenerate(true); + + SBError error = SBDebugger::InitializeWithErrorHandling(); + if (error.Fail()) { + std::cout << "initialization failed: " << error.GetCString() << '\n'; + return 1; + } + +#if !defined(_MSC_VER) + signal(SIGPIPE, SIG_IGN); +#endif + + std::vector threads; + for (uint64_t i = 0; i < THREADS; i++) + threads.emplace_back(f, i); + + for (auto &t : threads) + t.join(); + + SBDebugger::Terminate(); + return 0; +}