| //===-- ReproducerInstrumentation.cpp -------------------------------------===// |
| // |
| // 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" |
| #include <cstdio> |
| #include <cstdlib> |
| #include <limits> |
| #include <thread> |
| |
| using namespace lldb_private; |
| using namespace lldb_private::repro; |
| |
| // Whether we're currently across the API boundary. |
| static thread_local bool g_global_boundary = false; |
| |
| 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; |
| } |
| |
| std::vector<void *> IndexToObject::GetAllObjects() const { |
| std::vector<std::pair<unsigned, void *>> pairs; |
| for (auto &e : m_mapping) { |
| pairs.emplace_back(e.first, e.second); |
| } |
| |
| // Sort based on index. |
| std::sort(pairs.begin(), pairs.end(), |
| [](auto &lhs, auto &rhs) { return lhs.first < rhs.first; }); |
| |
| std::vector<void *> objects; |
| objects.reserve(pairs.size()); |
| for (auto &p : pairs) { |
| objects.push_back(p.second); |
| } |
| |
| return objects; |
| } |
| |
| template <> const uint8_t *Deserializer::Deserialize<const uint8_t *>() { |
| return Deserialize<uint8_t *>(); |
| } |
| |
| template <> void *Deserializer::Deserialize<void *>() { |
| return const_cast<void *>(Deserialize<const void *>()); |
| } |
| |
| template <> const void *Deserializer::Deserialize<const void *>() { |
| return nullptr; |
| } |
| |
| template <> char *Deserializer::Deserialize<char *>() { |
| return const_cast<char *>(Deserialize<const char *>()); |
| } |
| |
| template <> const char *Deserializer::Deserialize<const char *>() { |
| const size_t size = Deserialize<size_t>(); |
| if (size == std::numeric_limits<size_t>::max()) |
| return nullptr; |
| assert(HasData(size + 1)); |
| const char *str = m_buffer.data(); |
| m_buffer = m_buffer.drop_front(size + 1); |
| #ifdef LLDB_REPRO_INSTR_TRACE |
| llvm::errs() << "Deserializing with " << LLVM_PRETTY_FUNCTION << " -> \"" |
| << str << "\"\n"; |
| #endif |
| return str; |
| } |
| |
| template <> const char **Deserializer::Deserialize<const char **>() { |
| const size_t size = Deserialize<size_t>(); |
| if (size == 0) |
| return nullptr; |
| const char **r = |
| reinterpret_cast<const char **>(calloc(size + 1, sizeof(char *))); |
| for (size_t i = 0; i < size; ++i) |
| r[i] = Deserialize<const char *>(); |
| return r; |
| } |
| |
| void Deserializer::CheckSequence(unsigned sequence) { |
| if (m_expected_sequence && *m_expected_sequence != sequence) |
| 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."); |
| m_expected_sequence.reset(); |
| } |
| |
| bool Registry::Replay(const FileSpec &file) { |
| auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath()); |
| if (auto err = error_or_file.getError()) |
| return false; |
| |
| return Replay((*error_or_file)->getBuffer()); |
| } |
| |
| bool Registry::Replay(llvm::StringRef buffer) { |
| Deserializer deserializer(buffer); |
| return Replay(deserializer); |
| } |
| |
| bool Registry::Replay(Deserializer &deserializer) { |
| #ifndef LLDB_REPRO_INSTR_TRACE |
| Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_API); |
| #endif |
| |
| // Disable buffering stdout so that we approximate the way things get flushed |
| // during an interactive session. |
| setvbuf(stdout, nullptr, _IONBF, 0); |
| |
| while (deserializer.HasData(1)) { |
| unsigned sequence = deserializer.Deserialize<unsigned>(); |
| unsigned id = deserializer.Deserialize<unsigned>(); |
| |
| #ifndef LLDB_REPRO_INSTR_TRACE |
| LLDB_LOG(log, "Replaying {0}: {1}", id, GetSignature(id)); |
| #else |
| llvm::errs() << "Replaying " << id << ": " << GetSignature(id) << "\n"; |
| #endif |
| |
| deserializer.SetExpectedSequence(sequence); |
| GetReplayer(id)->operator()(deserializer); |
| } |
| |
| // Add a small artificial delay to ensure that all asynchronous events have |
| // completed before we exit. |
| std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| |
| return true; |
| } |
| |
| void Registry::DoRegister(uintptr_t RunID, std::unique_ptr<Replayer> replayer, |
| SignatureStr signature) { |
| const unsigned id = m_replayers.size() + 1; |
| assert(m_replayers.find(RunID) == m_replayers.end()); |
| m_replayers[RunID] = std::make_pair(std::move(replayer), id); |
| m_ids[id] = |
| std::make_pair(m_replayers[RunID].first.get(), std::move(signature)); |
| } |
| |
| unsigned Registry::GetID(uintptr_t addr) { |
| unsigned id = m_replayers[addr].second; |
| assert(id != 0 && "Forgot to add function to registry?"); |
| return id; |
| } |
| |
| std::string Registry::GetSignature(unsigned id) { |
| assert(m_ids.count(id) != 0 && "ID not in registry"); |
| return m_ids[id].second.ToString(); |
| } |
| |
| void Registry::CheckID(unsigned expected, unsigned actual) { |
| if (expected != actual) { |
| llvm::errs() << "Reproducer expected signature " << expected << ": '" |
| << GetSignature(expected) << "'\n"; |
| llvm::errs() << "Reproducer actual signature " << actual << ": '" |
| << GetSignature(actual) << "'\n"; |
| llvm::report_fatal_error( |
| "Detected reproducer replay divergence. Refusing to continue."); |
| } |
| |
| #ifdef LLDB_REPRO_INSTR_TRACE |
| llvm::errs() << "Replaying " << actual << ": " << GetSignature(actual) |
| << "\n"; |
| #endif |
| } |
| |
| Replayer *Registry::GetReplayer(unsigned id) { |
| assert(m_ids.count(id) != 0 && "ID not in registry"); |
| return m_ids[id].first; |
| } |
| |
| std::string Registry::SignatureStr::ToString() const { |
| return (result + (result.empty() ? "" : " ") + scope + "::" + name + args) |
| .str(); |
| } |
| |
| unsigned ObjectToIndex::GetIndexForObjectImpl(const void *object) { |
| 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() |
| : m_pretty_func(), m_pretty_args(), |
| |
| m_sequence(std::numeric_limits<unsigned>::max()) { |
| if (!g_global_boundary) { |
| g_global_boundary = true; |
| m_local_boundary = true; |
| m_sequence = GetNextSequenceNumber(); |
| } |
| } |
| |
| 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_sequence(std::numeric_limits<unsigned>::max()) { |
| if (!g_global_boundary) { |
| g_global_boundary = true; |
| m_local_boundary = true; |
| m_sequence = GetNextSequenceNumber(); |
| LLDB_LOG(GetLogIfAllCategoriesSet(LIBLLDB_LOG_API), "{0} ({1})", |
| m_pretty_func, m_pretty_args); |
| } |
| } |
| |
| Recorder::~Recorder() { |
| assert(m_result_recorded && "Did you forget LLDB_RECORD_RESULT?"); |
| UpdateBoundary(); |
| } |
| |
| unsigned Recorder::GetSequenceNumber() const { |
| assert(m_sequence != std::numeric_limits<unsigned>::max()); |
| return m_sequence; |
| } |
| |
| void Recorder::PrivateThread() { g_global_boundary = true; } |
| |
| void Recorder::UpdateBoundary() { |
| if (m_local_boundary) |
| g_global_boundary = false; |
| } |
| |
| void InstrumentationData::Initialize(Serializer &serializer, |
| Registry ®istry) { |
| InstanceImpl().emplace(serializer, registry); |
| } |
| |
| void InstrumentationData::Initialize(Deserializer &deserializer, |
| Registry ®istry) { |
| InstanceImpl().emplace(deserializer, registry); |
| } |
| |
| InstrumentationData &InstrumentationData::Instance() { |
| if (!InstanceImpl()) |
| InstanceImpl().emplace(); |
| return *InstanceImpl(); |
| } |
| |
| llvm::Optional<InstrumentationData> &InstrumentationData::InstanceImpl() { |
| static llvm::Optional<InstrumentationData> g_instrumentation_data; |
| return g_instrumentation_data; |
| } |
| |
| std::atomic<unsigned> lldb_private::repro::Recorder::g_sequence; |
| std::mutex lldb_private::repro::Recorder::g_mutex; |