1 //===-- CommandObjectReproducer.cpp ---------------------------------------===//
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 #include "CommandObjectReproducer.h"
10
11 #include "lldb/Host/HostInfo.h"
12 #include "lldb/Host/OptionParser.h"
13 #include "lldb/Interpreter/CommandInterpreter.h"
14 #include "lldb/Interpreter/CommandOptionArgumentTable.h"
15 #include "lldb/Interpreter/CommandReturnObject.h"
16 #include "lldb/Interpreter/OptionArgParser.h"
17 #include "lldb/Utility/GDBRemote.h"
18 #include "lldb/Utility/ProcessInfo.h"
19 #include "lldb/Utility/Reproducer.h"
20
21 #include <csignal>
22
23 using namespace lldb;
24 using namespace llvm;
25 using namespace lldb_private;
26 using namespace lldb_private::repro;
27
28 #define LLDB_OPTIONS_reproducer_dump
29 #include "CommandOptions.inc"
30
31 #define LLDB_OPTIONS_reproducer_xcrash
32 #include "CommandOptions.inc"
33
34 #define LLDB_OPTIONS_reproducer_verify
35 #include "CommandOptions.inc"
36
37 template <typename T>
ReadFromYAML(StringRef filename)38 llvm::Expected<T> static ReadFromYAML(StringRef filename) {
39 auto error_or_file = MemoryBuffer::getFile(filename);
40 if (auto err = error_or_file.getError()) {
41 return errorCodeToError(err);
42 }
43
44 T t;
45 yaml::Input yin((*error_or_file)->getBuffer());
46 yin >> t;
47
48 if (auto err = yin.error()) {
49 return errorCodeToError(err);
50 }
51
52 return t;
53 }
54
SetError(CommandReturnObject & result,Error err)55 static void SetError(CommandReturnObject &result, Error err) {
56 result.AppendError(toString(std::move(err)));
57 }
58
59 /// Create a loader from the given path if specified. Otherwise use the current
60 /// loader used for replay.
61 static Loader *
GetLoaderFromPathOrCurrent(llvm::Optional<Loader> & loader_storage,CommandReturnObject & result,FileSpec reproducer_path)62 GetLoaderFromPathOrCurrent(llvm::Optional<Loader> &loader_storage,
63 CommandReturnObject &result,
64 FileSpec reproducer_path) {
65 if (reproducer_path) {
66 loader_storage.emplace(reproducer_path);
67 Loader *loader = &(*loader_storage);
68 if (Error err = loader->LoadIndex()) {
69 // This is a hard error and will set the result to eReturnStatusFailed.
70 SetError(result, std::move(err));
71 return nullptr;
72 }
73 return loader;
74 }
75
76 if (Loader *loader = Reproducer::Instance().GetLoader())
77 return loader;
78
79 // This is a soft error because this is expected to fail during capture.
80 result.AppendError(
81 "Not specifying a reproducer is only support during replay.");
82 result.SetStatus(eReturnStatusSuccessFinishNoResult);
83 return nullptr;
84 }
85
86 class CommandObjectReproducerGenerate : public CommandObjectParsed {
87 public:
CommandObjectReproducerGenerate(CommandInterpreter & interpreter)88 CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
89 : CommandObjectParsed(
90 interpreter, "reproducer generate",
91 "Generate reproducer on disk. When the debugger is in capture "
92 "mode, this command will output the reproducer to a directory on "
93 "disk and quit. In replay mode this command in a no-op.",
94 nullptr) {}
95
96 ~CommandObjectReproducerGenerate() override = default;
97
98 protected:
DoExecute(Args & command,CommandReturnObject & result)99 bool DoExecute(Args &command, CommandReturnObject &result) override {
100 auto &r = Reproducer::Instance();
101 if (auto generator = r.GetGenerator()) {
102 generator->Keep();
103 } else {
104 result.AppendErrorWithFormat("Unable to get the reproducer generator");
105 return false;
106 }
107
108 result.GetOutputStream()
109 << "Reproducer written to '" << r.GetReproducerPath() << "'\n";
110 result.GetOutputStream()
111 << "Please have a look at the directory to assess if you're willing to "
112 "share the contained information.\n";
113
114 m_interpreter.BroadcastEvent(
115 CommandInterpreter::eBroadcastBitQuitCommandReceived);
116 result.SetStatus(eReturnStatusQuit);
117 return result.Succeeded();
118 }
119 };
120
121 class CommandObjectReproducerXCrash : public CommandObjectParsed {
122 public:
CommandObjectReproducerXCrash(CommandInterpreter & interpreter)123 CommandObjectReproducerXCrash(CommandInterpreter &interpreter)
124 : CommandObjectParsed(interpreter, "reproducer xcrash",
125 "Intentionally force the debugger to crash in "
126 "order to trigger and test reproducer generation.",
127 nullptr) {}
128
129 ~CommandObjectReproducerXCrash() override = default;
130
GetOptions()131 Options *GetOptions() override { return &m_options; }
132
133 class CommandOptions : public Options {
134 public:
135 CommandOptions() = default;
136
137 ~CommandOptions() override = default;
138
SetOptionValue(uint32_t option_idx,StringRef option_arg,ExecutionContext * execution_context)139 Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
140 ExecutionContext *execution_context) override {
141 Status error;
142 const int short_option = m_getopt_table[option_idx].val;
143
144 switch (short_option) {
145 case 's':
146 signal = (ReproducerCrashSignal)OptionArgParser::ToOptionEnum(
147 option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
148 if (!error.Success())
149 error.SetErrorStringWithFormat("unrecognized value for signal '%s'",
150 option_arg.str().c_str());
151 break;
152 default:
153 llvm_unreachable("Unimplemented option");
154 }
155
156 return error;
157 }
158
OptionParsingStarting(ExecutionContext * execution_context)159 void OptionParsingStarting(ExecutionContext *execution_context) override {
160 signal = eReproducerCrashSigsegv;
161 }
162
GetDefinitions()163 ArrayRef<OptionDefinition> GetDefinitions() override {
164 return makeArrayRef(g_reproducer_xcrash_options);
165 }
166
167 ReproducerCrashSignal signal = eReproducerCrashSigsegv;
168 };
169
170 protected:
DoExecute(Args & command,CommandReturnObject & result)171 bool DoExecute(Args &command, CommandReturnObject &result) override {
172 auto &r = Reproducer::Instance();
173
174 if (!r.IsCapturing()) {
175 result.AppendError(
176 "forcing a crash is only supported when capturing a reproducer.");
177 result.SetStatus(eReturnStatusSuccessFinishNoResult);
178 return false;
179 }
180
181 switch (m_options.signal) {
182 case eReproducerCrashSigill:
183 std::raise(SIGILL);
184 break;
185 case eReproducerCrashSigsegv:
186 std::raise(SIGSEGV);
187 break;
188 }
189
190 result.SetStatus(eReturnStatusQuit);
191 return result.Succeeded();
192 }
193
194 private:
195 CommandOptions m_options;
196 };
197
198 class CommandObjectReproducerStatus : public CommandObjectParsed {
199 public:
CommandObjectReproducerStatus(CommandInterpreter & interpreter)200 CommandObjectReproducerStatus(CommandInterpreter &interpreter)
201 : CommandObjectParsed(
202 interpreter, "reproducer status",
203 "Show the current reproducer status. In capture mode the "
204 "debugger "
205 "is collecting all the information it needs to create a "
206 "reproducer. In replay mode the reproducer is replaying a "
207 "reproducer. When the reproducers are off, no data is collected "
208 "and no reproducer can be generated.",
209 nullptr) {}
210
211 ~CommandObjectReproducerStatus() override = default;
212
213 protected:
DoExecute(Args & command,CommandReturnObject & result)214 bool DoExecute(Args &command, CommandReturnObject &result) override {
215 auto &r = Reproducer::Instance();
216 if (r.IsCapturing()) {
217 result.GetOutputStream() << "Reproducer is in capture mode.\n";
218 result.GetOutputStream()
219 << "Path: " << r.GetReproducerPath().GetPath() << '\n';
220 } else {
221 result.GetOutputStream() << "Reproducer is off.\n";
222 }
223
224 // Auto generate is hidden unless enabled because this is mostly for
225 // development and testing.
226 if (Generator *g = r.GetGenerator()) {
227 if (g->IsAutoGenerate())
228 result.GetOutputStream() << "Auto generate: on\n";
229 }
230
231 result.SetStatus(eReturnStatusSuccessFinishResult);
232 return result.Succeeded();
233 }
234 };
235
236 class CommandObjectReproducerDump : public CommandObjectParsed {
237 public:
CommandObjectReproducerDump(CommandInterpreter & interpreter)238 CommandObjectReproducerDump(CommandInterpreter &interpreter)
239 : CommandObjectParsed(interpreter, "reproducer dump",
240 "Dump the information contained in a reproducer. "
241 "If no reproducer is specified during replay, it "
242 "dumps the content of the current reproducer.",
243 nullptr) {}
244
245 ~CommandObjectReproducerDump() override = default;
246
GetOptions()247 Options *GetOptions() override { return &m_options; }
248
249 class CommandOptions : public Options {
250 public:
251 CommandOptions() = default;
252
253 ~CommandOptions() override = default;
254
SetOptionValue(uint32_t option_idx,StringRef option_arg,ExecutionContext * execution_context)255 Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
256 ExecutionContext *execution_context) override {
257 Status error;
258 const int short_option = m_getopt_table[option_idx].val;
259
260 switch (short_option) {
261 case 'f':
262 file.SetFile(option_arg, FileSpec::Style::native);
263 FileSystem::Instance().Resolve(file);
264 break;
265 case 'p':
266 provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
267 option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
268 if (!error.Success())
269 error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
270 option_arg.str().c_str());
271 break;
272 default:
273 llvm_unreachable("Unimplemented option");
274 }
275
276 return error;
277 }
278
OptionParsingStarting(ExecutionContext * execution_context)279 void OptionParsingStarting(ExecutionContext *execution_context) override {
280 file.Clear();
281 provider = eReproducerProviderNone;
282 }
283
GetDefinitions()284 ArrayRef<OptionDefinition> GetDefinitions() override {
285 return makeArrayRef(g_reproducer_dump_options);
286 }
287
288 FileSpec file;
289 ReproducerProvider provider = eReproducerProviderNone;
290 };
291
292 protected:
DoExecute(Args & command,CommandReturnObject & result)293 bool DoExecute(Args &command, CommandReturnObject &result) override {
294 llvm::Optional<Loader> loader_storage;
295 Loader *loader =
296 GetLoaderFromPathOrCurrent(loader_storage, result, m_options.file);
297 if (!loader)
298 return false;
299
300 switch (m_options.provider) {
301 case eReproducerProviderFiles: {
302 FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
303
304 // Read the VFS mapping.
305 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
306 vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
307 if (!buffer) {
308 SetError(result, errorCodeToError(buffer.getError()));
309 return false;
310 }
311
312 // Initialize a VFS from the given mapping.
313 IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
314 std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
315
316 // Dump the VFS to a buffer.
317 std::string str;
318 raw_string_ostream os(str);
319 static_cast<vfs::RedirectingFileSystem &>(*vfs).print(os);
320 os.flush();
321
322 // Return the string.
323 result.AppendMessage(str);
324 result.SetStatus(eReturnStatusSuccessFinishResult);
325 return true;
326 }
327 case eReproducerProviderSymbolFiles: {
328 Expected<std::string> symbol_files =
329 loader->LoadBuffer<SymbolFileProvider>();
330 if (!symbol_files) {
331 SetError(result, symbol_files.takeError());
332 return false;
333 }
334
335 std::vector<SymbolFileProvider::Entry> entries;
336 llvm::yaml::Input yin(*symbol_files);
337 yin >> entries;
338
339 for (const auto &entry : entries) {
340 result.AppendMessageWithFormat("- uuid: %s\n",
341 entry.uuid.c_str());
342 result.AppendMessageWithFormat(" module path: %s\n",
343 entry.module_path.c_str());
344 result.AppendMessageWithFormat(" symbol path: %s\n",
345 entry.symbol_path.c_str());
346 }
347 result.SetStatus(eReturnStatusSuccessFinishResult);
348 return true;
349 }
350 case eReproducerProviderVersion: {
351 Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
352 if (!version) {
353 SetError(result, version.takeError());
354 return false;
355 }
356 result.AppendMessage(*version);
357 result.SetStatus(eReturnStatusSuccessFinishResult);
358 return true;
359 }
360 case eReproducerProviderWorkingDirectory: {
361 Expected<std::string> cwd =
362 repro::GetDirectoryFrom<WorkingDirectoryProvider>(loader);
363 if (!cwd) {
364 SetError(result, cwd.takeError());
365 return false;
366 }
367 result.AppendMessage(*cwd);
368 result.SetStatus(eReturnStatusSuccessFinishResult);
369 return true;
370 }
371 case eReproducerProviderHomeDirectory: {
372 Expected<std::string> home =
373 repro::GetDirectoryFrom<HomeDirectoryProvider>(loader);
374 if (!home) {
375 SetError(result, home.takeError());
376 return false;
377 }
378 result.AppendMessage(*home);
379 result.SetStatus(eReturnStatusSuccessFinishResult);
380 return true;
381 }
382 case eReproducerProviderCommands: {
383 std::unique_ptr<repro::MultiLoader<repro::CommandProvider>> multi_loader =
384 repro::MultiLoader<repro::CommandProvider>::Create(loader);
385 if (!multi_loader) {
386 SetError(result,
387 make_error<StringError>("Unable to create command loader.",
388 llvm::inconvertibleErrorCode()));
389 return false;
390 }
391
392 // Iterate over the command files and dump them.
393 llvm::Optional<std::string> command_file;
394 while ((command_file = multi_loader->GetNextFile())) {
395 if (!command_file)
396 break;
397
398 auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
399 if (auto err = command_buffer.getError()) {
400 SetError(result, errorCodeToError(err));
401 return false;
402 }
403 result.AppendMessage((*command_buffer)->getBuffer());
404 }
405
406 result.SetStatus(eReturnStatusSuccessFinishResult);
407 return true;
408 }
409 case eReproducerProviderGDB: {
410 std::unique_ptr<repro::MultiLoader<repro::GDBRemoteProvider>>
411 multi_loader =
412 repro::MultiLoader<repro::GDBRemoteProvider>::Create(loader);
413
414 if (!multi_loader) {
415 SetError(result,
416 make_error<StringError>("Unable to create GDB loader.",
417 llvm::inconvertibleErrorCode()));
418 return false;
419 }
420
421 llvm::Optional<std::string> gdb_file;
422 while ((gdb_file = multi_loader->GetNextFile())) {
423 if (llvm::Expected<std::vector<GDBRemotePacket>> packets =
424 ReadFromYAML<std::vector<GDBRemotePacket>>(*gdb_file)) {
425 for (GDBRemotePacket &packet : *packets) {
426 packet.Dump(result.GetOutputStream());
427 }
428 } else {
429 SetError(result, packets.takeError());
430 return false;
431 }
432 }
433
434 result.SetStatus(eReturnStatusSuccessFinishResult);
435 return true;
436 }
437 case eReproducerProviderProcessInfo: {
438 std::unique_ptr<repro::MultiLoader<repro::ProcessInfoProvider>>
439 multi_loader =
440 repro::MultiLoader<repro::ProcessInfoProvider>::Create(loader);
441
442 if (!multi_loader) {
443 SetError(result, make_error<StringError>(
444 llvm::inconvertibleErrorCode(),
445 "Unable to create process info loader."));
446 return false;
447 }
448
449 llvm::Optional<std::string> process_file;
450 while ((process_file = multi_loader->GetNextFile())) {
451 if (llvm::Expected<ProcessInstanceInfoList> infos =
452 ReadFromYAML<ProcessInstanceInfoList>(*process_file)) {
453 for (ProcessInstanceInfo info : *infos)
454 info.Dump(result.GetOutputStream(), HostInfo::GetUserIDResolver());
455 } else {
456 SetError(result, infos.takeError());
457 return false;
458 }
459 }
460
461 result.SetStatus(eReturnStatusSuccessFinishResult);
462 return true;
463 }
464 case eReproducerProviderNone:
465 result.AppendError("No valid provider specified.");
466 return false;
467 }
468
469 result.SetStatus(eReturnStatusSuccessFinishNoResult);
470 return result.Succeeded();
471 }
472
473 private:
474 CommandOptions m_options;
475 };
476
CommandObjectReproducer(CommandInterpreter & interpreter)477 CommandObjectReproducer::CommandObjectReproducer(
478 CommandInterpreter &interpreter)
479 : CommandObjectMultiword(
480 interpreter, "reproducer",
481 "Commands for manipulating reproducers. Reproducers make it "
482 "possible "
483 "to capture full debug sessions with all its dependencies. The "
484 "resulting reproducer is used to replay the debug session while "
485 "debugging the debugger.\n"
486 "Because reproducers need the whole the debug session from "
487 "beginning to end, you need to launch the debugger in capture or "
488 "replay mode, commonly though the command line driver.\n"
489 "Reproducers are unrelated record-replay debugging, as you cannot "
490 "interact with the debugger during replay.\n",
491 "reproducer <subcommand> [<subcommand-options>]") {
492 LoadSubCommand(
493 "generate",
494 CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
495 LoadSubCommand("status", CommandObjectSP(
496 new CommandObjectReproducerStatus(interpreter)));
497 LoadSubCommand("dump",
498 CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
499 LoadSubCommand("xcrash", CommandObjectSP(
500 new CommandObjectReproducerXCrash(interpreter)));
501 }
502
503 CommandObjectReproducer::~CommandObjectReproducer() = default;
504