| //===-- Reproducer.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_PROVIDER_H |
| #define LLDB_UTILITY_REPRODUCER_PROVIDER_H |
| |
| #include "lldb/Utility/FileSpec.h" |
| #include "lldb/Utility/ProcessInfo.h" |
| #include "lldb/Utility/Reproducer.h" |
| #include "lldb/Utility/UUID.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/FileCollector.h" |
| #include "llvm/Support/YAMLTraits.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| namespace lldb_private { |
| namespace repro { |
| |
| /// The recorder is a small object handed out by a provider to record data. It |
| /// is commonly used in combination with a MultiProvider which is meant to |
| /// record information for multiple instances of the same source of data. |
| class AbstractRecorder { |
| protected: |
| AbstractRecorder(const FileSpec &filename, std::error_code &ec) |
| : m_filename(filename.GetFilename().GetStringRef()), |
| m_os(filename.GetPath(), ec, llvm::sys::fs::OF_Text), m_record(true) {} |
| |
| public: |
| const FileSpec &GetFilename() { return m_filename; } |
| |
| void Stop() { |
| assert(m_record); |
| m_record = false; |
| } |
| |
| private: |
| FileSpec m_filename; |
| |
| protected: |
| llvm::raw_fd_ostream m_os; |
| bool m_record; |
| }; |
| |
| /// Recorder that records its data as text to a file. |
| class DataRecorder : public AbstractRecorder { |
| public: |
| DataRecorder(const FileSpec &filename, std::error_code &ec) |
| : AbstractRecorder(filename, ec) {} |
| |
| static llvm::Expected<std::unique_ptr<DataRecorder>> |
| Create(const FileSpec &filename); |
| |
| template <typename T> void Record(const T &t, bool newline = false) { |
| if (!m_record) |
| return; |
| m_os << t; |
| if (newline) |
| m_os << '\n'; |
| m_os.flush(); |
| } |
| }; |
| |
| /// Recorder that records its data as YAML to a file. |
| class YamlRecorder : public AbstractRecorder { |
| public: |
| YamlRecorder(const FileSpec &filename, std::error_code &ec) |
| : AbstractRecorder(filename, ec) {} |
| |
| static llvm::Expected<std::unique_ptr<YamlRecorder>> |
| Create(const FileSpec &filename); |
| |
| template <typename T> void Record(const T &t) { |
| if (!m_record) |
| return; |
| llvm::yaml::Output yout(m_os); |
| // The YAML traits are defined as non-const because they are used for |
| // serialization and deserialization. The cast is safe because |
| // serialization doesn't modify the object. |
| yout << const_cast<T &>(t); |
| m_os.flush(); |
| } |
| }; |
| |
| class FlushingFileCollector : public llvm::FileCollectorBase { |
| public: |
| FlushingFileCollector(llvm::StringRef files_path, llvm::StringRef dirs_path, |
| std::error_code &ec); |
| |
| protected: |
| void addFileImpl(llvm::StringRef file) override; |
| |
| llvm::vfs::directory_iterator |
| addDirectoryImpl(const llvm::Twine &dir, |
| llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs, |
| std::error_code &dir_ec) override; |
| |
| llvm::Optional<llvm::raw_fd_ostream> m_files_os; |
| llvm::Optional<llvm::raw_fd_ostream> m_dirs_os; |
| }; |
| |
| class FileProvider : public Provider<FileProvider> { |
| public: |
| struct Info { |
| static const char *name; |
| static const char *file; |
| }; |
| |
| FileProvider(const FileSpec &directory) : Provider(directory) { |
| std::error_code ec; |
| m_collector = std::make_shared<FlushingFileCollector>( |
| directory.CopyByAppendingPathComponent("files.txt").GetPath(), |
| directory.CopyByAppendingPathComponent("dirs.txt").GetPath(), ec); |
| if (ec) |
| m_collector.reset(); |
| } |
| |
| std::shared_ptr<llvm::FileCollectorBase> GetFileCollector() { |
| return m_collector; |
| } |
| |
| void RecordInterestingDirectory(const llvm::Twine &dir); |
| void RecordInterestingDirectoryRecursive(const llvm::Twine &dir); |
| |
| static char ID; |
| |
| private: |
| std::shared_ptr<FlushingFileCollector> m_collector; |
| }; |
| |
| /// Provider for the LLDB version number. |
| /// |
| /// When the reproducer is kept, it writes the lldb version to a file named |
| /// version.txt in the reproducer root. |
| class VersionProvider : public Provider<VersionProvider> { |
| public: |
| VersionProvider(const FileSpec &directory) : Provider(directory) {} |
| struct Info { |
| static const char *name; |
| static const char *file; |
| }; |
| void SetVersion(std::string version) { |
| assert(m_version.empty()); |
| m_version = std::move(version); |
| } |
| void Keep() override; |
| std::string m_version; |
| static char ID; |
| }; |
| |
| /// Abstract provider to storing directory paths. |
| template <typename T> class DirectoryProvider : public repro::Provider<T> { |
| public: |
| DirectoryProvider(const FileSpec &root) : Provider<T>(root) {} |
| void SetDirectory(std::string directory) { |
| m_directory = std::move(directory); |
| } |
| llvm::StringRef GetDirectory() { return m_directory; } |
| |
| void Keep() override { |
| FileSpec file = this->GetRoot().CopyByAppendingPathComponent(T::Info::file); |
| std::error_code ec; |
| llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text); |
| if (ec) |
| return; |
| os << m_directory << "\n"; |
| } |
| |
| protected: |
| std::string m_directory; |
| }; |
| |
| /// Provider for the current working directory. |
| /// |
| /// When the reproducer is kept, it writes lldb's current working directory to |
| /// a file named cwd.txt in the reproducer root. |
| class WorkingDirectoryProvider |
| : public DirectoryProvider<WorkingDirectoryProvider> { |
| public: |
| WorkingDirectoryProvider(const FileSpec &directory) |
| : DirectoryProvider(directory) { |
| llvm::SmallString<128> cwd; |
| if (std::error_code EC = llvm::sys::fs::current_path(cwd)) |
| return; |
| SetDirectory(std::string(cwd)); |
| } |
| struct Info { |
| static const char *name; |
| static const char *file; |
| }; |
| static char ID; |
| }; |
| |
| /// Provider for the home directory. |
| /// |
| /// When the reproducer is kept, it writes the user's home directory to a file |
| /// a file named home.txt in the reproducer root. |
| class HomeDirectoryProvider : public DirectoryProvider<HomeDirectoryProvider> { |
| public: |
| HomeDirectoryProvider(const FileSpec &directory) |
| : DirectoryProvider(directory) { |
| llvm::SmallString<128> home_dir; |
| llvm::sys::path::home_directory(home_dir); |
| SetDirectory(std::string(home_dir)); |
| } |
| struct Info { |
| static const char *name; |
| static const char *file; |
| }; |
| static char ID; |
| }; |
| |
| /// Provider for mapping UUIDs to symbol and executable files. |
| class SymbolFileProvider : public Provider<SymbolFileProvider> { |
| public: |
| SymbolFileProvider(const FileSpec &directory) |
| : Provider(directory), m_symbol_files() {} |
| |
| void AddSymbolFile(const UUID *uuid, const FileSpec &module_path, |
| const FileSpec &symbol_path); |
| void Keep() override; |
| |
| struct Entry { |
| Entry() = default; |
| Entry(std::string uuid) : uuid(std::move(uuid)) {} |
| Entry(std::string uuid, std::string module_path, std::string symbol_path) |
| : uuid(std::move(uuid)), module_path(std::move(module_path)), |
| symbol_path(std::move(symbol_path)) {} |
| |
| bool operator==(const Entry &rhs) const { return uuid == rhs.uuid; } |
| bool operator<(const Entry &rhs) const { return uuid < rhs.uuid; } |
| |
| std::string uuid; |
| std::string module_path; |
| std::string symbol_path; |
| }; |
| |
| struct Info { |
| static const char *name; |
| static const char *file; |
| }; |
| static char ID; |
| |
| private: |
| std::vector<Entry> m_symbol_files; |
| }; |
| |
| /// The MultiProvider is a provider that hands out recorder which can be used |
| /// to capture data for different instances of the same object. The recorders |
| /// can be passed around or stored as an instance member. |
| /// |
| /// The Info::file for the MultiProvider contains an index of files for every |
| /// recorder. Use the MultiLoader to read the index and get the individual |
| /// files. |
| template <typename T, typename V> |
| class MultiProvider : public repro::Provider<V> { |
| public: |
| MultiProvider(const FileSpec &directory) : Provider<V>(directory) {} |
| |
| T *GetNewRecorder() { |
| std::size_t i = m_recorders.size() + 1; |
| std::string filename = (llvm::Twine(V::Info::name) + llvm::Twine("-") + |
| llvm::Twine(i) + llvm::Twine(".yaml")) |
| .str(); |
| auto recorder_or_error = |
| T::Create(this->GetRoot().CopyByAppendingPathComponent(filename)); |
| if (!recorder_or_error) { |
| llvm::consumeError(recorder_or_error.takeError()); |
| return nullptr; |
| } |
| |
| m_recorders.push_back(std::move(*recorder_or_error)); |
| return m_recorders.back().get(); |
| } |
| |
| void Keep() override { |
| std::vector<std::string> files; |
| for (auto &recorder : m_recorders) { |
| recorder->Stop(); |
| files.push_back(recorder->GetFilename().GetPath()); |
| } |
| |
| FileSpec file = this->GetRoot().CopyByAppendingPathComponent(V::Info::file); |
| std::error_code ec; |
| llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text); |
| if (ec) |
| return; |
| llvm::yaml::Output yout(os); |
| yout << files; |
| } |
| |
| void Discard() override { m_recorders.clear(); } |
| |
| private: |
| std::vector<std::unique_ptr<T>> m_recorders; |
| }; |
| |
| class CommandProvider : public MultiProvider<DataRecorder, CommandProvider> { |
| public: |
| struct Info { |
| static const char *name; |
| static const char *file; |
| }; |
| |
| CommandProvider(const FileSpec &directory) |
| : MultiProvider<DataRecorder, CommandProvider>(directory) {} |
| |
| static char ID; |
| }; |
| |
| class ProcessInfoRecorder : public AbstractRecorder { |
| public: |
| ProcessInfoRecorder(const FileSpec &filename, std::error_code &ec) |
| : AbstractRecorder(filename, ec) {} |
| |
| static llvm::Expected<std::unique_ptr<ProcessInfoRecorder>> |
| Create(const FileSpec &filename); |
| |
| void Record(const ProcessInstanceInfoList &process_infos); |
| }; |
| |
| class ProcessInfoProvider : public repro::Provider<ProcessInfoProvider> { |
| public: |
| struct Info { |
| static const char *name; |
| static const char *file; |
| }; |
| |
| ProcessInfoProvider(const FileSpec &directory) : Provider(directory) {} |
| |
| ProcessInfoRecorder *GetNewProcessInfoRecorder(); |
| |
| void Keep() override; |
| void Discard() override; |
| |
| static char ID; |
| |
| private: |
| std::unique_ptr<llvm::raw_fd_ostream> m_stream_up; |
| std::vector<std::unique_ptr<ProcessInfoRecorder>> m_process_info_recorders; |
| }; |
| |
| /// Loader for data captured with the MultiProvider. It will read the index and |
| /// return the path to the files in the index. |
| template <typename T> class MultiLoader { |
| public: |
| MultiLoader(std::vector<std::string> files) : m_files(std::move(files)) {} |
| |
| static std::unique_ptr<MultiLoader> Create(Loader *loader) { |
| if (!loader) |
| return {}; |
| |
| FileSpec file = loader->GetFile<typename T::Info>(); |
| if (!file) |
| return {}; |
| |
| auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath()); |
| if (auto err = error_or_file.getError()) |
| return {}; |
| |
| std::vector<std::string> files; |
| llvm::yaml::Input yin((*error_or_file)->getBuffer()); |
| yin >> files; |
| |
| if (auto err = yin.error()) |
| return {}; |
| |
| for (auto &file : files) { |
| FileSpec absolute_path = |
| loader->GetRoot().CopyByAppendingPathComponent(file); |
| file = absolute_path.GetPath(); |
| } |
| |
| return std::make_unique<MultiLoader<T>>(std::move(files)); |
| } |
| |
| llvm::Optional<std::string> GetNextFile() { |
| if (m_index >= m_files.size()) |
| return {}; |
| return m_files[m_index++]; |
| } |
| |
| private: |
| std::vector<std::string> m_files; |
| unsigned m_index = 0; |
| }; |
| |
| class SymbolFileLoader { |
| public: |
| SymbolFileLoader(Loader *loader); |
| std::pair<FileSpec, FileSpec> GetPaths(const UUID *uuid) const; |
| |
| private: |
| // Sorted list of UUID to path mappings. |
| std::vector<SymbolFileProvider::Entry> m_symbol_files; |
| }; |
| |
| /// Helper to read directories written by the DirectoryProvider. |
| template <typename T> |
| llvm::Expected<std::string> GetDirectoryFrom(repro::Loader *loader) { |
| llvm::Expected<std::string> dir = loader->LoadBuffer<T>(); |
| if (!dir) |
| return dir.takeError(); |
| return std::string(llvm::StringRef(*dir).rtrim()); |
| } |
| |
| } // namespace repro |
| } // namespace lldb_private |
| |
| LLVM_YAML_IS_SEQUENCE_VECTOR(lldb_private::repro::SymbolFileProvider::Entry) |
| |
| namespace llvm { |
| namespace yaml { |
| template <> |
| struct MappingTraits<lldb_private::repro::SymbolFileProvider::Entry> { |
| static void mapping(IO &io, |
| lldb_private::repro::SymbolFileProvider::Entry &entry) { |
| io.mapRequired("uuid", entry.uuid); |
| io.mapRequired("module-path", entry.module_path); |
| io.mapRequired("symbol-path", entry.symbol_path); |
| } |
| }; |
| } // namespace yaml |
| } // namespace llvm |
| |
| #endif // LLDB_UTILITY_REPRODUCER_PROVIDER_H |