//===-- 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 #include #include 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_TextWithCRLF), 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> Create(const FileSpec &filename); template 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> Create(const FileSpec &filename); template 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); 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 vfs, std::error_code &dir_ec) override; llvm::Optional m_files_os; llvm::Optional m_dirs_os; }; class FileProvider : public Provider { 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( directory.CopyByAppendingPathComponent("files.txt").GetPath(), directory.CopyByAppendingPathComponent("dirs.txt").GetPath(), ec); if (ec) m_collector.reset(); } std::shared_ptr GetFileCollector() { return m_collector; } void RecordInterestingDirectory(const llvm::Twine &dir); void RecordInterestingDirectoryRecursive(const llvm::Twine &dir); static char ID; private: std::shared_ptr 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 { 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 class DirectoryProvider : public repro::Provider { public: DirectoryProvider(const FileSpec &root) : Provider(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_TextWithCRLF); 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 { 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 { 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 { public: SymbolFileProvider(const FileSpec &directory) : Provider(directory) {} 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 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 class MultiProvider : public repro::Provider { public: MultiProvider(const FileSpec &directory) : Provider(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 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_TextWithCRLF); if (ec) return; llvm::yaml::Output yout(os); yout << files; } void Discard() override { m_recorders.clear(); } private: std::vector> m_recorders; }; class CommandProvider : public MultiProvider { public: struct Info { static const char *name; static const char *file; }; CommandProvider(const FileSpec &directory) : MultiProvider(directory) {} static char ID; }; class ProcessInfoRecorder : public AbstractRecorder { public: ProcessInfoRecorder(const FileSpec &filename, std::error_code &ec) : AbstractRecorder(filename, ec) {} static llvm::Expected> Create(const FileSpec &filename); void Record(const ProcessInstanceInfoList &process_infos); }; class ProcessInfoProvider : public repro::Provider { 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 m_stream_up; std::vector> 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 class MultiLoader { public: MultiLoader(std::vector files) : m_files(std::move(files)) {} static std::unique_ptr Create(Loader *loader) { if (!loader) return {}; FileSpec file = loader->GetFile(); if (!file) return {}; auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath()); if (auto err = error_or_file.getError()) return {}; std::vector 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>(std::move(files)); } llvm::Optional GetNextFile() { if (m_index >= m_files.size()) return {}; return m_files[m_index++]; } private: std::vector m_files; unsigned m_index = 0; }; class SymbolFileLoader { public: SymbolFileLoader(Loader *loader); std::pair GetPaths(const UUID *uuid) const; private: // Sorted list of UUID to path mappings. std::vector m_symbol_files; }; /// Helper to read directories written by the DirectoryProvider. template llvm::Expected GetDirectoryFrom(repro::Loader *loader) { llvm::Expected dir = loader->LoadBuffer(); 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 { 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