1 //===-- CommandObjectReproducer.cpp ---------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "CommandObjectReproducer.h"
11 #include "lldb/Host/HostInfo.h"
12 #include "lldb/Host/OptionParser.h"
13 #include "lldb/Interpreter/CommandInterpreter.h"
14 #include "lldb/Interpreter/CommandReturnObject.h"
15 #include "lldb/Interpreter/OptionArgParser.h"
16 #include "lldb/Utility/GDBRemote.h"
17 #include "lldb/Utility/ProcessInfo.h"
18 #include "lldb/Utility/Reproducer.h"
24 using namespace lldb_private;
25 using namespace lldb_private::repro;
27 enum ReproducerProvider {
28 eReproducerProviderCommands,
29 eReproducerProviderFiles,
30 eReproducerProviderGDB,
31 eReproducerProviderProcessInfo,
32 eReproducerProviderVersion,
33 eReproducerProviderWorkingDirectory,
34 eReproducerProviderNone
37 static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
39 eReproducerProviderCommands,
41 "Command Interpreter Commands",
44 eReproducerProviderFiles,
49 eReproducerProviderGDB,
54 eReproducerProviderProcessInfo,
59 eReproducerProviderVersion,
64 eReproducerProviderWorkingDirectory,
69 eReproducerProviderNone,
75 static constexpr OptionEnumValues ReproducerProviderType() {
76 return OptionEnumValues(g_reproducer_provider_type);
79 #define LLDB_OPTIONS_reproducer_dump
80 #include "CommandOptions.inc"
82 enum ReproducerCrashSignal {
83 eReproducerCrashSigill,
84 eReproducerCrashSigsegv,
87 static constexpr OptionEnumValueElement g_reproducer_signaltype[] = {
89 eReproducerCrashSigill,
91 "Illegal instruction",
94 eReproducerCrashSigsegv,
100 static constexpr OptionEnumValues ReproducerSignalType() {
101 return OptionEnumValues(g_reproducer_signaltype);
104 #define LLDB_OPTIONS_reproducer_xcrash
105 #include "CommandOptions.inc"
107 template <typename T>
108 llvm::Expected<T> static ReadFromYAML(StringRef filename) {
109 auto error_or_file = MemoryBuffer::getFile(filename);
110 if (auto err = error_or_file.getError()) {
111 return errorCodeToError(err);
115 yaml::Input yin((*error_or_file)->getBuffer());
118 if (auto err = yin.error()) {
119 return errorCodeToError(err);
125 class CommandObjectReproducerGenerate : public CommandObjectParsed {
127 CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
128 : CommandObjectParsed(
129 interpreter, "reproducer generate",
130 "Generate reproducer on disk. When the debugger is in capture "
131 "mode, this command will output the reproducer to a directory on "
132 "disk and quit. In replay mode this command in a no-op.",
135 ~CommandObjectReproducerGenerate() override = default;
138 bool DoExecute(Args &command, CommandReturnObject &result) override {
139 if (!command.empty()) {
140 result.AppendErrorWithFormat("'%s' takes no arguments",
145 auto &r = Reproducer::Instance();
146 if (auto generator = r.GetGenerator()) {
148 } else if (r.IsReplaying()) {
149 // Make this operation a NO-OP in replay mode.
150 result.SetStatus(eReturnStatusSuccessFinishNoResult);
151 return result.Succeeded();
153 result.AppendErrorWithFormat("Unable to get the reproducer generator");
154 result.SetStatus(eReturnStatusFailed);
158 result.GetOutputStream()
159 << "Reproducer written to '" << r.GetReproducerPath() << "'\n";
160 result.GetOutputStream()
161 << "Please have a look at the directory to assess if you're willing to "
162 "share the contained information.\n";
164 m_interpreter.BroadcastEvent(
165 CommandInterpreter::eBroadcastBitQuitCommandReceived);
166 result.SetStatus(eReturnStatusQuit);
167 return result.Succeeded();
171 class CommandObjectReproducerXCrash : public CommandObjectParsed {
173 CommandObjectReproducerXCrash(CommandInterpreter &interpreter)
174 : CommandObjectParsed(interpreter, "reproducer xcrash",
175 "Intentionally force the debugger to crash in "
176 "order to trigger and test reproducer generation.",
179 ~CommandObjectReproducerXCrash() override = default;
181 Options *GetOptions() override { return &m_options; }
183 class CommandOptions : public Options {
185 CommandOptions() : Options() {}
187 ~CommandOptions() override = default;
189 Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
190 ExecutionContext *execution_context) override {
192 const int short_option = m_getopt_table[option_idx].val;
194 switch (short_option) {
196 signal = (ReproducerCrashSignal)OptionArgParser::ToOptionEnum(
197 option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
198 if (!error.Success())
199 error.SetErrorStringWithFormat("unrecognized value for signal '%s'",
200 option_arg.str().c_str());
203 llvm_unreachable("Unimplemented option");
209 void OptionParsingStarting(ExecutionContext *execution_context) override {
210 signal = eReproducerCrashSigsegv;
213 ArrayRef<OptionDefinition> GetDefinitions() override {
214 return makeArrayRef(g_reproducer_xcrash_options);
217 ReproducerCrashSignal signal = eReproducerCrashSigsegv;
221 bool DoExecute(Args &command, CommandReturnObject &result) override {
222 if (!command.empty()) {
223 result.AppendErrorWithFormat("'%s' takes no arguments",
228 auto &r = Reproducer::Instance();
230 if (!r.IsCapturing() && !r.IsReplaying()) {
232 "forcing a crash is only supported when capturing a reproducer.");
233 result.SetStatus(eReturnStatusSuccessFinishNoResult);
237 switch (m_options.signal) {
238 case eReproducerCrashSigill:
241 case eReproducerCrashSigsegv:
246 result.SetStatus(eReturnStatusQuit);
247 return result.Succeeded();
251 CommandOptions m_options;
254 class CommandObjectReproducerStatus : public CommandObjectParsed {
256 CommandObjectReproducerStatus(CommandInterpreter &interpreter)
257 : CommandObjectParsed(
258 interpreter, "reproducer status",
259 "Show the current reproducer status. In capture mode the "
261 "is collecting all the information it needs to create a "
262 "reproducer. In replay mode the reproducer is replaying a "
263 "reproducer. When the reproducers are off, no data is collected "
264 "and no reproducer can be generated.",
267 ~CommandObjectReproducerStatus() override = default;
270 bool DoExecute(Args &command, CommandReturnObject &result) override {
271 if (!command.empty()) {
272 result.AppendErrorWithFormat("'%s' takes no arguments",
277 auto &r = Reproducer::Instance();
278 if (r.IsCapturing()) {
279 result.GetOutputStream() << "Reproducer is in capture mode.\n";
280 } else if (r.IsReplaying()) {
281 result.GetOutputStream() << "Reproducer is in replay mode.\n";
283 result.GetOutputStream() << "Reproducer is off.\n";
286 if (r.IsCapturing() || r.IsReplaying()) {
287 result.GetOutputStream()
288 << "Path: " << r.GetReproducerPath().GetPath() << '\n';
291 // Auto generate is hidden unless enabled because this is mostly for
292 // development and testing.
293 if (Generator *g = r.GetGenerator()) {
294 if (g->IsAutoGenerate())
295 result.GetOutputStream() << "Auto generate: on\n";
298 result.SetStatus(eReturnStatusSuccessFinishResult);
299 return result.Succeeded();
303 static void SetError(CommandReturnObject &result, Error err) {
304 result.GetErrorStream().Printf("error: %s\n",
305 toString(std::move(err)).c_str());
306 result.SetStatus(eReturnStatusFailed);
309 class CommandObjectReproducerDump : public CommandObjectParsed {
311 CommandObjectReproducerDump(CommandInterpreter &interpreter)
312 : CommandObjectParsed(interpreter, "reproducer dump",
313 "Dump the information contained in a reproducer. "
314 "If no reproducer is specified during replay, it "
315 "dumps the content of the current reproducer.",
318 ~CommandObjectReproducerDump() override = default;
320 Options *GetOptions() override { return &m_options; }
322 class CommandOptions : public Options {
324 CommandOptions() : Options(), file() {}
326 ~CommandOptions() override = default;
328 Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
329 ExecutionContext *execution_context) override {
331 const int short_option = m_getopt_table[option_idx].val;
333 switch (short_option) {
335 file.SetFile(option_arg, FileSpec::Style::native);
336 FileSystem::Instance().Resolve(file);
339 provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
340 option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
341 if (!error.Success())
342 error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
343 option_arg.str().c_str());
346 llvm_unreachable("Unimplemented option");
352 void OptionParsingStarting(ExecutionContext *execution_context) override {
354 provider = eReproducerProviderNone;
357 ArrayRef<OptionDefinition> GetDefinitions() override {
358 return makeArrayRef(g_reproducer_dump_options);
362 ReproducerProvider provider = eReproducerProviderNone;
366 bool DoExecute(Args &command, CommandReturnObject &result) override {
367 if (!command.empty()) {
368 result.AppendErrorWithFormat("'%s' takes no arguments",
373 // If no reproducer path is specified, use the loader currently used for
374 // replay. Otherwise create a new loader just for dumping.
375 llvm::Optional<Loader> loader_storage;
376 Loader *loader = nullptr;
377 if (!m_options.file) {
378 loader = Reproducer::Instance().GetLoader();
379 if (loader == nullptr) {
381 "Not specifying a reproducer is only support during replay.");
382 result.SetStatus(eReturnStatusSuccessFinishNoResult);
386 loader_storage.emplace(m_options.file);
387 loader = &(*loader_storage);
388 if (Error err = loader->LoadIndex()) {
389 SetError(result, std::move(err));
394 // If we get here we should have a valid loader.
397 switch (m_options.provider) {
398 case eReproducerProviderFiles: {
399 FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
401 // Read the VFS mapping.
402 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
403 vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
405 SetError(result, errorCodeToError(buffer.getError()));
409 // Initialize a VFS from the given mapping.
410 IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
411 std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
413 // Dump the VFS to a buffer.
415 raw_string_ostream os(str);
416 static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
419 // Return the string.
420 result.AppendMessage(str);
421 result.SetStatus(eReturnStatusSuccessFinishResult);
424 case eReproducerProviderVersion: {
425 Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
427 SetError(result, version.takeError());
430 result.AppendMessage(*version);
431 result.SetStatus(eReturnStatusSuccessFinishResult);
434 case eReproducerProviderWorkingDirectory: {
435 Expected<std::string> cwd =
436 loader->LoadBuffer<WorkingDirectoryProvider>();
438 SetError(result, cwd.takeError());
441 result.AppendMessage(*cwd);
442 result.SetStatus(eReturnStatusSuccessFinishResult);
445 case eReproducerProviderCommands: {
446 std::unique_ptr<repro::MultiLoader<repro::CommandProvider>> multi_loader =
447 repro::MultiLoader<repro::CommandProvider>::Create(loader);
450 make_error<StringError>("Unable to create command loader.",
451 llvm::inconvertibleErrorCode()));
455 // Iterate over the command files and dump them.
456 llvm::Optional<std::string> command_file;
457 while ((command_file = multi_loader->GetNextFile())) {
461 auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
462 if (auto err = command_buffer.getError()) {
463 SetError(result, errorCodeToError(err));
466 result.AppendMessage((*command_buffer)->getBuffer());
469 result.SetStatus(eReturnStatusSuccessFinishResult);
472 case eReproducerProviderGDB: {
473 std::unique_ptr<repro::MultiLoader<repro::GDBRemoteProvider>>
475 repro::MultiLoader<repro::GDBRemoteProvider>::Create(loader);
479 make_error<StringError>("Unable to create GDB loader.",
480 llvm::inconvertibleErrorCode()));
484 llvm::Optional<std::string> gdb_file;
485 while ((gdb_file = multi_loader->GetNextFile())) {
486 if (llvm::Expected<std::vector<GDBRemotePacket>> packets =
487 ReadFromYAML<std::vector<GDBRemotePacket>>(*gdb_file)) {
488 for (GDBRemotePacket &packet : *packets) {
489 packet.Dump(result.GetOutputStream());
492 SetError(result, packets.takeError());
497 result.SetStatus(eReturnStatusSuccessFinishResult);
500 case eReproducerProviderProcessInfo: {
501 std::unique_ptr<repro::MultiLoader<repro::ProcessInfoProvider>>
503 repro::MultiLoader<repro::ProcessInfoProvider>::Create(loader);
506 SetError(result, make_error<StringError>(
507 llvm::inconvertibleErrorCode(),
508 "Unable to create process info loader."));
512 llvm::Optional<std::string> process_file;
513 while ((process_file = multi_loader->GetNextFile())) {
514 if (llvm::Expected<ProcessInstanceInfoList> infos =
515 ReadFromYAML<ProcessInstanceInfoList>(*process_file)) {
516 for (ProcessInstanceInfo info : *infos)
517 info.Dump(result.GetOutputStream(), HostInfo::GetUserIDResolver());
519 SetError(result, infos.takeError());
524 result.SetStatus(eReturnStatusSuccessFinishResult);
527 case eReproducerProviderNone:
528 result.SetError("No valid provider specified.");
532 result.SetStatus(eReturnStatusSuccessFinishNoResult);
533 return result.Succeeded();
537 CommandOptions m_options;
540 CommandObjectReproducer::CommandObjectReproducer(
541 CommandInterpreter &interpreter)
542 : CommandObjectMultiword(
543 interpreter, "reproducer",
544 "Commands for manipulating reproducers. Reproducers make it "
546 "to capture full debug sessions with all its dependencies. The "
547 "resulting reproducer is used to replay the debug session while "
548 "debugging the debugger.\n"
549 "Because reproducers need the whole the debug session from "
550 "beginning to end, you need to launch the debugger in capture or "
551 "replay mode, commonly though the command line driver.\n"
552 "Reproducers are unrelated record-replay debugging, as you cannot "
553 "interact with the debugger during replay.\n",
554 "reproducer <subcommand> [<subcommand-options>]") {
557 CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
558 LoadSubCommand("status", CommandObjectSP(
559 new CommandObjectReproducerStatus(interpreter)));
560 LoadSubCommand("dump",
561 CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
562 LoadSubCommand("xcrash", CommandObjectSP(
563 new CommandObjectReproducerXCrash(interpreter)));
566 CommandObjectReproducer::~CommandObjectReproducer() = default;