1 //===-- Reproducer.h --------------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #ifndef LLDB_UTILITY_REPRODUCER_PROVIDER_H
10 #define LLDB_UTILITY_REPRODUCER_PROVIDER_H
11
12 #include "lldb/Utility/FileSpec.h"
13 #include "lldb/Utility/ProcessInfo.h"
14 #include "lldb/Utility/Reproducer.h"
15 #include "lldb/Utility/UUID.h"
16 #include "llvm/ADT/StringRef.h"
17 #include "llvm/Support/Error.h"
18 #include "llvm/Support/FileCollector.h"
19 #include "llvm/Support/YAMLTraits.h"
20
21 #include <string>
22 #include <utility>
23 #include <vector>
24
25 namespace lldb_private {
26 namespace repro {
27
28 /// The recorder is a small object handed out by a provider to record data. It
29 /// is commonly used in combination with a MultiProvider which is meant to
30 /// record information for multiple instances of the same source of data.
31 class AbstractRecorder {
32 protected:
AbstractRecorder(const FileSpec & filename,std::error_code & ec)33 AbstractRecorder(const FileSpec &filename, std::error_code &ec)
34 : m_filename(filename.GetFilename().GetStringRef()),
35 m_os(filename.GetPath(), ec, llvm::sys::fs::OF_Text), m_record(true) {}
36
37 public:
GetFilename()38 const FileSpec &GetFilename() { return m_filename; }
39
Stop()40 void Stop() {
41 assert(m_record);
42 m_record = false;
43 }
44
45 private:
46 FileSpec m_filename;
47
48 protected:
49 llvm::raw_fd_ostream m_os;
50 bool m_record;
51 };
52
53 /// Recorder that records its data as text to a file.
54 class DataRecorder : public AbstractRecorder {
55 public:
DataRecorder(const FileSpec & filename,std::error_code & ec)56 DataRecorder(const FileSpec &filename, std::error_code &ec)
57 : AbstractRecorder(filename, ec) {}
58
59 static llvm::Expected<std::unique_ptr<DataRecorder>>
60 Create(const FileSpec &filename);
61
62 template <typename T> void Record(const T &t, bool newline = false) {
63 if (!m_record)
64 return;
65 m_os << t;
66 if (newline)
67 m_os << '\n';
68 m_os.flush();
69 }
70 };
71
72 /// Recorder that records its data as YAML to a file.
73 class YamlRecorder : public AbstractRecorder {
74 public:
YamlRecorder(const FileSpec & filename,std::error_code & ec)75 YamlRecorder(const FileSpec &filename, std::error_code &ec)
76 : AbstractRecorder(filename, ec) {}
77
78 static llvm::Expected<std::unique_ptr<YamlRecorder>>
79 Create(const FileSpec &filename);
80
Record(const T & t)81 template <typename T> void Record(const T &t) {
82 if (!m_record)
83 return;
84 llvm::yaml::Output yout(m_os);
85 // The YAML traits are defined as non-const because they are used for
86 // serialization and deserialization. The cast is safe because
87 // serialization doesn't modify the object.
88 yout << const_cast<T &>(t);
89 m_os.flush();
90 }
91 };
92
93 class FlushingFileCollector : public llvm::FileCollectorBase {
94 public:
95 FlushingFileCollector(llvm::StringRef files_path, llvm::StringRef dirs_path,
96 std::error_code &ec);
97
98 protected:
99 void addFileImpl(llvm::StringRef file) override;
100
101 llvm::vfs::directory_iterator
102 addDirectoryImpl(const llvm::Twine &dir,
103 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs,
104 std::error_code &dir_ec) override;
105
106 llvm::Optional<llvm::raw_fd_ostream> m_files_os;
107 llvm::Optional<llvm::raw_fd_ostream> m_dirs_os;
108 };
109
110 class FileProvider : public Provider<FileProvider> {
111 public:
112 struct Info {
113 static const char *name;
114 static const char *file;
115 };
116
FileProvider(const FileSpec & directory)117 FileProvider(const FileSpec &directory) : Provider(directory) {
118 std::error_code ec;
119 m_collector = std::make_shared<FlushingFileCollector>(
120 directory.CopyByAppendingPathComponent("files.txt").GetPath(),
121 directory.CopyByAppendingPathComponent("dirs.txt").GetPath(), ec);
122 if (ec)
123 m_collector.reset();
124 }
125
GetFileCollector()126 std::shared_ptr<llvm::FileCollectorBase> GetFileCollector() {
127 return m_collector;
128 }
129
130 void RecordInterestingDirectory(const llvm::Twine &dir);
131 void RecordInterestingDirectoryRecursive(const llvm::Twine &dir);
132
133 static char ID;
134
135 private:
136 std::shared_ptr<FlushingFileCollector> m_collector;
137 };
138
139 /// Provider for the LLDB version number.
140 ///
141 /// When the reproducer is kept, it writes the lldb version to a file named
142 /// version.txt in the reproducer root.
143 class VersionProvider : public Provider<VersionProvider> {
144 public:
VersionProvider(const FileSpec & directory)145 VersionProvider(const FileSpec &directory) : Provider(directory) {}
146 struct Info {
147 static const char *name;
148 static const char *file;
149 };
SetVersion(std::string version)150 void SetVersion(std::string version) {
151 assert(m_version.empty());
152 m_version = std::move(version);
153 }
154 void Keep() override;
155 std::string m_version;
156 static char ID;
157 };
158
159 /// Abstract provider to storing directory paths.
160 template <typename T> class DirectoryProvider : public repro::Provider<T> {
161 public:
DirectoryProvider(const FileSpec & root)162 DirectoryProvider(const FileSpec &root) : Provider<T>(root) {}
SetDirectory(std::string directory)163 void SetDirectory(std::string directory) {
164 m_directory = std::move(directory);
165 }
GetDirectory()166 llvm::StringRef GetDirectory() { return m_directory; }
167
Keep()168 void Keep() override {
169 FileSpec file = this->GetRoot().CopyByAppendingPathComponent(T::Info::file);
170 std::error_code ec;
171 llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text);
172 if (ec)
173 return;
174 os << m_directory << "\n";
175 }
176
177 protected:
178 std::string m_directory;
179 };
180
181 /// Provider for the current working directory.
182 ///
183 /// When the reproducer is kept, it writes lldb's current working directory to
184 /// a file named cwd.txt in the reproducer root.
185 class WorkingDirectoryProvider
186 : public DirectoryProvider<WorkingDirectoryProvider> {
187 public:
WorkingDirectoryProvider(const FileSpec & directory)188 WorkingDirectoryProvider(const FileSpec &directory)
189 : DirectoryProvider(directory) {
190 llvm::SmallString<128> cwd;
191 if (std::error_code EC = llvm::sys::fs::current_path(cwd))
192 return;
193 SetDirectory(std::string(cwd));
194 }
195 struct Info {
196 static const char *name;
197 static const char *file;
198 };
199 static char ID;
200 };
201
202 /// Provider for the home directory.
203 ///
204 /// When the reproducer is kept, it writes the user's home directory to a file
205 /// a file named home.txt in the reproducer root.
206 class HomeDirectoryProvider : public DirectoryProvider<HomeDirectoryProvider> {
207 public:
HomeDirectoryProvider(const FileSpec & directory)208 HomeDirectoryProvider(const FileSpec &directory)
209 : DirectoryProvider(directory) {
210 llvm::SmallString<128> home_dir;
211 llvm::sys::path::home_directory(home_dir);
212 SetDirectory(std::string(home_dir));
213 }
214 struct Info {
215 static const char *name;
216 static const char *file;
217 };
218 static char ID;
219 };
220
221 /// Provider for mapping UUIDs to symbol and executable files.
222 class SymbolFileProvider : public Provider<SymbolFileProvider> {
223 public:
SymbolFileProvider(const FileSpec & directory)224 SymbolFileProvider(const FileSpec &directory)
225 : Provider(directory), m_symbol_files() {}
226
227 void AddSymbolFile(const UUID *uuid, const FileSpec &module_path,
228 const FileSpec &symbol_path);
229 void Keep() override;
230
231 struct Entry {
232 Entry() = default;
EntryEntry233 Entry(std::string uuid) : uuid(std::move(uuid)) {}
EntryEntry234 Entry(std::string uuid, std::string module_path, std::string symbol_path)
235 : uuid(std::move(uuid)), module_path(std::move(module_path)),
236 symbol_path(std::move(symbol_path)) {}
237
238 bool operator==(const Entry &rhs) const { return uuid == rhs.uuid; }
239 bool operator<(const Entry &rhs) const { return uuid < rhs.uuid; }
240
241 std::string uuid;
242 std::string module_path;
243 std::string symbol_path;
244 };
245
246 struct Info {
247 static const char *name;
248 static const char *file;
249 };
250 static char ID;
251
252 private:
253 std::vector<Entry> m_symbol_files;
254 };
255
256 /// The MultiProvider is a provider that hands out recorder which can be used
257 /// to capture data for different instances of the same object. The recorders
258 /// can be passed around or stored as an instance member.
259 ///
260 /// The Info::file for the MultiProvider contains an index of files for every
261 /// recorder. Use the MultiLoader to read the index and get the individual
262 /// files.
263 template <typename T, typename V>
264 class MultiProvider : public repro::Provider<V> {
265 public:
MultiProvider(const FileSpec & directory)266 MultiProvider(const FileSpec &directory) : Provider<V>(directory) {}
267
GetNewRecorder()268 T *GetNewRecorder() {
269 std::size_t i = m_recorders.size() + 1;
270 std::string filename = (llvm::Twine(V::Info::name) + llvm::Twine("-") +
271 llvm::Twine(i) + llvm::Twine(".yaml"))
272 .str();
273 auto recorder_or_error =
274 T::Create(this->GetRoot().CopyByAppendingPathComponent(filename));
275 if (!recorder_or_error) {
276 llvm::consumeError(recorder_or_error.takeError());
277 return nullptr;
278 }
279
280 m_recorders.push_back(std::move(*recorder_or_error));
281 return m_recorders.back().get();
282 }
283
Keep()284 void Keep() override {
285 std::vector<std::string> files;
286 for (auto &recorder : m_recorders) {
287 recorder->Stop();
288 files.push_back(recorder->GetFilename().GetPath());
289 }
290
291 FileSpec file = this->GetRoot().CopyByAppendingPathComponent(V::Info::file);
292 std::error_code ec;
293 llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text);
294 if (ec)
295 return;
296 llvm::yaml::Output yout(os);
297 yout << files;
298 }
299
Discard()300 void Discard() override { m_recorders.clear(); }
301
302 private:
303 std::vector<std::unique_ptr<T>> m_recorders;
304 };
305
306 class CommandProvider : public MultiProvider<DataRecorder, CommandProvider> {
307 public:
308 struct Info {
309 static const char *name;
310 static const char *file;
311 };
312
CommandProvider(const FileSpec & directory)313 CommandProvider(const FileSpec &directory)
314 : MultiProvider<DataRecorder, CommandProvider>(directory) {}
315
316 static char ID;
317 };
318
319 class ProcessInfoRecorder : public AbstractRecorder {
320 public:
ProcessInfoRecorder(const FileSpec & filename,std::error_code & ec)321 ProcessInfoRecorder(const FileSpec &filename, std::error_code &ec)
322 : AbstractRecorder(filename, ec) {}
323
324 static llvm::Expected<std::unique_ptr<ProcessInfoRecorder>>
325 Create(const FileSpec &filename);
326
327 void Record(const ProcessInstanceInfoList &process_infos);
328 };
329
330 class ProcessInfoProvider : public repro::Provider<ProcessInfoProvider> {
331 public:
332 struct Info {
333 static const char *name;
334 static const char *file;
335 };
336
ProcessInfoProvider(const FileSpec & directory)337 ProcessInfoProvider(const FileSpec &directory) : Provider(directory) {}
338
339 ProcessInfoRecorder *GetNewProcessInfoRecorder();
340
341 void Keep() override;
342 void Discard() override;
343
344 static char ID;
345
346 private:
347 std::unique_ptr<llvm::raw_fd_ostream> m_stream_up;
348 std::vector<std::unique_ptr<ProcessInfoRecorder>> m_process_info_recorders;
349 };
350
351 /// Loader for data captured with the MultiProvider. It will read the index and
352 /// return the path to the files in the index.
353 template <typename T> class MultiLoader {
354 public:
MultiLoader(std::vector<std::string> files)355 MultiLoader(std::vector<std::string> files) : m_files(std::move(files)) {}
356
Create(Loader * loader)357 static std::unique_ptr<MultiLoader> Create(Loader *loader) {
358 if (!loader)
359 return {};
360
361 FileSpec file = loader->GetFile<typename T::Info>();
362 if (!file)
363 return {};
364
365 auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath());
366 if (auto err = error_or_file.getError())
367 return {};
368
369 std::vector<std::string> files;
370 llvm::yaml::Input yin((*error_or_file)->getBuffer());
371 yin >> files;
372
373 if (auto err = yin.error())
374 return {};
375
376 for (auto &file : files) {
377 FileSpec absolute_path =
378 loader->GetRoot().CopyByAppendingPathComponent(file);
379 file = absolute_path.GetPath();
380 }
381
382 return std::make_unique<MultiLoader<T>>(std::move(files));
383 }
384
GetNextFile()385 llvm::Optional<std::string> GetNextFile() {
386 if (m_index >= m_files.size())
387 return {};
388 return m_files[m_index++];
389 }
390
391 private:
392 std::vector<std::string> m_files;
393 unsigned m_index = 0;
394 };
395
396 class SymbolFileLoader {
397 public:
398 SymbolFileLoader(Loader *loader);
399 std::pair<FileSpec, FileSpec> GetPaths(const UUID *uuid) const;
400
401 private:
402 // Sorted list of UUID to path mappings.
403 std::vector<SymbolFileProvider::Entry> m_symbol_files;
404 };
405
406 /// Helper to read directories written by the DirectoryProvider.
407 template <typename T>
GetDirectoryFrom(repro::Loader * loader)408 llvm::Expected<std::string> GetDirectoryFrom(repro::Loader *loader) {
409 llvm::Expected<std::string> dir = loader->LoadBuffer<T>();
410 if (!dir)
411 return dir.takeError();
412 return std::string(llvm::StringRef(*dir).rtrim());
413 }
414
415 } // namespace repro
416 } // namespace lldb_private
417
LLVM_YAML_IS_SEQUENCE_VECTOR(lldb_private::repro::SymbolFileProvider::Entry)418 LLVM_YAML_IS_SEQUENCE_VECTOR(lldb_private::repro::SymbolFileProvider::Entry)
419
420 namespace llvm {
421 namespace yaml {
422 template <>
423 struct MappingTraits<lldb_private::repro::SymbolFileProvider::Entry> {
424 static void mapping(IO &io,
425 lldb_private::repro::SymbolFileProvider::Entry &entry) {
426 io.mapRequired("uuid", entry.uuid);
427 io.mapRequired("module-path", entry.module_path);
428 io.mapRequired("symbol-path", entry.symbol_path);
429 }
430 };
431 } // namespace yaml
432 } // namespace llvm
433
434 #endif // LLDB_UTILITY_REPRODUCER_PROVIDER_H
435