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