1 //===-- CommandObjectReproducer.cpp -----------------------------*- C++ -*-===//
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/OptionParser.h"
12 #include "lldb/Utility/GDBRemote.h"
13 #include "lldb/Utility/Reproducer.h"
15 #include "lldb/Interpreter/CommandInterpreter.h"
16 #include "lldb/Interpreter/CommandReturnObject.h"
17 #include "lldb/Interpreter/OptionArgParser.h"
23 using namespace lldb_private
;
24 using namespace lldb_private::repro
;
26 enum ReproducerProvider
{
27 eReproducerProviderCommands
,
28 eReproducerProviderFiles
,
29 eReproducerProviderGDB
,
30 eReproducerProviderVersion
,
31 eReproducerProviderWorkingDirectory
,
32 eReproducerProviderNone
35 static constexpr OptionEnumValueElement g_reproducer_provider_type
[] = {
37 eReproducerProviderCommands
,
39 "Command Interpreter Commands",
42 eReproducerProviderFiles
,
47 eReproducerProviderGDB
,
52 eReproducerProviderVersion
,
57 eReproducerProviderWorkingDirectory
,
62 eReproducerProviderNone
,
68 static constexpr OptionEnumValues
ReproducerProviderType() {
69 return OptionEnumValues(g_reproducer_provider_type
);
72 #define LLDB_OPTIONS_reproducer_dump
73 #include "CommandOptions.inc"
75 enum ReproducerCrashSignal
{
76 eReproducerCrashSigill
,
77 eReproducerCrashSigsegv
,
80 static constexpr OptionEnumValueElement g_reproducer_signaltype
[] = {
82 eReproducerCrashSigill
,
84 "Illegal instruction",
87 eReproducerCrashSigsegv
,
93 static constexpr OptionEnumValues
ReproducerSignalType() {
94 return OptionEnumValues(g_reproducer_signaltype
);
97 #define LLDB_OPTIONS_reproducer_xcrash
98 #include "CommandOptions.inc"
100 class CommandObjectReproducerGenerate
: public CommandObjectParsed
{
102 CommandObjectReproducerGenerate(CommandInterpreter
&interpreter
)
103 : CommandObjectParsed(
104 interpreter
, "reproducer generate",
105 "Generate reproducer on disk. When the debugger is in capture "
106 "mode, this command will output the reproducer to a directory on "
107 "disk and quit. In replay mode this command in a no-op.",
110 ~CommandObjectReproducerGenerate() override
= default;
113 bool DoExecute(Args
&command
, CommandReturnObject
&result
) override
{
114 if (!command
.empty()) {
115 result
.AppendErrorWithFormat("'%s' takes no arguments",
120 auto &r
= Reproducer::Instance();
121 if (auto generator
= r
.GetGenerator()) {
123 } else if (r
.IsReplaying()) {
124 // Make this operation a NO-OP in replay mode.
125 result
.SetStatus(eReturnStatusSuccessFinishNoResult
);
126 return result
.Succeeded();
128 result
.AppendErrorWithFormat("Unable to get the reproducer generator");
129 result
.SetStatus(eReturnStatusFailed
);
133 result
.GetOutputStream()
134 << "Reproducer written to '" << r
.GetReproducerPath() << "'\n";
135 result
.GetOutputStream()
136 << "Please have a look at the directory to assess if you're willing to "
137 "share the contained information.\n";
139 m_interpreter
.BroadcastEvent(
140 CommandInterpreter::eBroadcastBitQuitCommandReceived
);
141 result
.SetStatus(eReturnStatusQuit
);
142 return result
.Succeeded();
146 class CommandObjectReproducerXCrash
: public CommandObjectParsed
{
148 CommandObjectReproducerXCrash(CommandInterpreter
&interpreter
)
149 : CommandObjectParsed(interpreter
, "reproducer xcrash",
150 "Intentionally force the debugger to crash in "
151 "order to trigger and test reproducer generation.",
154 ~CommandObjectReproducerXCrash() override
= default;
156 Options
*GetOptions() override
{ return &m_options
; }
158 class CommandOptions
: public Options
{
160 CommandOptions() : Options() {}
162 ~CommandOptions() override
= default;
164 Status
SetOptionValue(uint32_t option_idx
, StringRef option_arg
,
165 ExecutionContext
*execution_context
) override
{
167 const int short_option
= m_getopt_table
[option_idx
].val
;
169 switch (short_option
) {
171 signal
= (ReproducerCrashSignal
)OptionArgParser::ToOptionEnum(
172 option_arg
, GetDefinitions()[option_idx
].enum_values
, 0, error
);
173 if (!error
.Success())
174 error
.SetErrorStringWithFormat("unrecognized value for signal '%s'",
175 option_arg
.str().c_str());
178 llvm_unreachable("Unimplemented option");
184 void OptionParsingStarting(ExecutionContext
*execution_context
) override
{
185 signal
= eReproducerCrashSigsegv
;
188 ArrayRef
<OptionDefinition
> GetDefinitions() override
{
189 return makeArrayRef(g_reproducer_xcrash_options
);
192 ReproducerCrashSignal signal
= eReproducerCrashSigsegv
;
196 bool DoExecute(Args
&command
, CommandReturnObject
&result
) override
{
197 if (!command
.empty()) {
198 result
.AppendErrorWithFormat("'%s' takes no arguments",
203 auto &r
= Reproducer::Instance();
205 if (!r
.IsCapturing() && !r
.IsReplaying()) {
207 "forcing a crash is only supported when capturing a reproducer.");
208 result
.SetStatus(eReturnStatusSuccessFinishNoResult
);
212 switch (m_options
.signal
) {
213 case eReproducerCrashSigill
:
216 case eReproducerCrashSigsegv
:
221 result
.SetStatus(eReturnStatusQuit
);
222 return result
.Succeeded();
226 CommandOptions m_options
;
229 class CommandObjectReproducerStatus
: public CommandObjectParsed
{
231 CommandObjectReproducerStatus(CommandInterpreter
&interpreter
)
232 : CommandObjectParsed(
233 interpreter
, "reproducer status",
234 "Show the current reproducer status. In capture mode the "
236 "is collecting all the information it needs to create a "
237 "reproducer. In replay mode the reproducer is replaying a "
238 "reproducer. When the reproducers are off, no data is collected "
239 "and no reproducer can be generated.",
242 ~CommandObjectReproducerStatus() override
= default;
245 bool DoExecute(Args
&command
, CommandReturnObject
&result
) override
{
246 if (!command
.empty()) {
247 result
.AppendErrorWithFormat("'%s' takes no arguments",
252 auto &r
= Reproducer::Instance();
253 if (r
.IsCapturing()) {
254 result
.GetOutputStream() << "Reproducer is in capture mode.\n";
255 } else if (r
.IsReplaying()) {
256 result
.GetOutputStream() << "Reproducer is in replay mode.\n";
258 result
.GetOutputStream() << "Reproducer is off.\n";
261 if (r
.IsCapturing() || r
.IsReplaying()) {
262 result
.GetOutputStream()
263 << "Path: " << r
.GetReproducerPath().GetPath() << '\n';
266 // Auto generate is hidden unless enabled because this is mostly for
267 // development and testing.
268 if (Generator
*g
= r
.GetGenerator()) {
269 if (g
->IsAutoGenerate())
270 result
.GetOutputStream() << "Auto generate: on\n";
273 result
.SetStatus(eReturnStatusSuccessFinishResult
);
274 return result
.Succeeded();
278 static void SetError(CommandReturnObject
&result
, Error err
) {
279 result
.GetErrorStream().Printf("error: %s\n",
280 toString(std::move(err
)).c_str());
281 result
.SetStatus(eReturnStatusFailed
);
284 class CommandObjectReproducerDump
: public CommandObjectParsed
{
286 CommandObjectReproducerDump(CommandInterpreter
&interpreter
)
287 : CommandObjectParsed(interpreter
, "reproducer dump",
288 "Dump the information contained in a reproducer. "
289 "If no reproducer is specified during replay, it "
290 "dumps the content of the current reproducer.",
293 ~CommandObjectReproducerDump() override
= default;
295 Options
*GetOptions() override
{ return &m_options
; }
297 class CommandOptions
: public Options
{
299 CommandOptions() : Options(), file() {}
301 ~CommandOptions() override
= default;
303 Status
SetOptionValue(uint32_t option_idx
, StringRef option_arg
,
304 ExecutionContext
*execution_context
) override
{
306 const int short_option
= m_getopt_table
[option_idx
].val
;
308 switch (short_option
) {
310 file
.SetFile(option_arg
, FileSpec::Style::native
);
311 FileSystem::Instance().Resolve(file
);
314 provider
= (ReproducerProvider
)OptionArgParser::ToOptionEnum(
315 option_arg
, GetDefinitions()[option_idx
].enum_values
, 0, error
);
316 if (!error
.Success())
317 error
.SetErrorStringWithFormat("unrecognized value for provider '%s'",
318 option_arg
.str().c_str());
321 llvm_unreachable("Unimplemented option");
327 void OptionParsingStarting(ExecutionContext
*execution_context
) override
{
329 provider
= eReproducerProviderNone
;
332 ArrayRef
<OptionDefinition
> GetDefinitions() override
{
333 return makeArrayRef(g_reproducer_dump_options
);
337 ReproducerProvider provider
= eReproducerProviderNone
;
341 bool DoExecute(Args
&command
, CommandReturnObject
&result
) override
{
342 if (!command
.empty()) {
343 result
.AppendErrorWithFormat("'%s' takes no arguments",
348 // If no reproducer path is specified, use the loader currently used for
349 // replay. Otherwise create a new loader just for dumping.
350 llvm::Optional
<Loader
> loader_storage
;
351 Loader
*loader
= nullptr;
352 if (!m_options
.file
) {
353 loader
= Reproducer::Instance().GetLoader();
354 if (loader
== nullptr) {
356 "Not specifying a reproducer is only support during replay.");
357 result
.SetStatus(eReturnStatusSuccessFinishNoResult
);
361 loader_storage
.emplace(m_options
.file
);
362 loader
= &(*loader_storage
);
363 if (Error err
= loader
->LoadIndex()) {
364 SetError(result
, std::move(err
));
369 // If we get here we should have a valid loader.
372 switch (m_options
.provider
) {
373 case eReproducerProviderFiles
: {
374 FileSpec vfs_mapping
= loader
->GetFile
<FileProvider::Info
>();
376 // Read the VFS mapping.
377 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> buffer
=
378 vfs::getRealFileSystem()->getBufferForFile(vfs_mapping
.GetPath());
380 SetError(result
, errorCodeToError(buffer
.getError()));
384 // Initialize a VFS from the given mapping.
385 IntrusiveRefCntPtr
<vfs::FileSystem
> vfs
= vfs::getVFSFromYAML(
386 std::move(buffer
.get()), nullptr, vfs_mapping
.GetPath());
388 // Dump the VFS to a buffer.
390 raw_string_ostream
os(str
);
391 static_cast<vfs::RedirectingFileSystem
&>(*vfs
).dump(os
);
394 // Return the string.
395 result
.AppendMessage(str
);
396 result
.SetStatus(eReturnStatusSuccessFinishResult
);
399 case eReproducerProviderVersion
: {
400 Expected
<std::string
> version
= loader
->LoadBuffer
<VersionProvider
>();
402 SetError(result
, version
.takeError());
405 result
.AppendMessage(*version
);
406 result
.SetStatus(eReturnStatusSuccessFinishResult
);
409 case eReproducerProviderWorkingDirectory
: {
410 Expected
<std::string
> cwd
=
411 loader
->LoadBuffer
<WorkingDirectoryProvider
>();
413 SetError(result
, cwd
.takeError());
416 result
.AppendMessage(*cwd
);
417 result
.SetStatus(eReturnStatusSuccessFinishResult
);
420 case eReproducerProviderCommands
: {
421 std::unique_ptr
<repro::MultiLoader
<repro::CommandProvider
>> multi_loader
=
422 repro::MultiLoader
<repro::CommandProvider
>::Create(loader
);
425 make_error
<StringError
>(llvm::inconvertibleErrorCode(),
426 "Unable to create command loader."));
430 // Iterate over the command files and dump them.
431 llvm::Optional
<std::string
> command_file
;
432 while ((command_file
= multi_loader
->GetNextFile())) {
436 auto command_buffer
= llvm::MemoryBuffer::getFile(*command_file
);
437 if (auto err
= command_buffer
.getError()) {
438 SetError(result
, errorCodeToError(err
));
441 result
.AppendMessage((*command_buffer
)->getBuffer());
444 result
.SetStatus(eReturnStatusSuccessFinishResult
);
447 case eReproducerProviderGDB
: {
448 std::unique_ptr
<repro::MultiLoader
<repro::GDBRemoteProvider
>>
450 repro::MultiLoader
<repro::GDBRemoteProvider
>::Create(loader
);
451 llvm::Optional
<std::string
> gdb_file
;
452 while ((gdb_file
= multi_loader
->GetNextFile())) {
453 auto error_or_file
= MemoryBuffer::getFile(*gdb_file
);
454 if (auto err
= error_or_file
.getError()) {
455 SetError(result
, errorCodeToError(err
));
459 std::vector
<GDBRemotePacket
> packets
;
460 yaml::Input
yin((*error_or_file
)->getBuffer());
463 if (auto err
= yin
.error()) {
464 SetError(result
, errorCodeToError(err
));
468 for (GDBRemotePacket
&packet
: packets
) {
469 packet
.Dump(result
.GetOutputStream());
473 result
.SetStatus(eReturnStatusSuccessFinishResult
);
476 case eReproducerProviderNone
:
477 result
.SetError("No valid provider specified.");
481 result
.SetStatus(eReturnStatusSuccessFinishNoResult
);
482 return result
.Succeeded();
486 CommandOptions m_options
;
489 CommandObjectReproducer::CommandObjectReproducer(
490 CommandInterpreter
&interpreter
)
491 : CommandObjectMultiword(
492 interpreter
, "reproducer",
493 "Commands for manipulating reproducers. Reproducers make it "
495 "to capture full debug sessions with all its dependencies. The "
496 "resulting reproducer is used to replay the debug session while "
497 "debugging the debugger.\n"
498 "Because reproducers need the whole the debug session from "
499 "beginning to end, you need to launch the debugger in capture or "
500 "replay mode, commonly though the command line driver.\n"
501 "Reproducers are unrelated record-replay debugging, as you cannot "
502 "interact with the debugger during replay.\n",
503 "reproducer <subcommand> [<subcommand-options>]") {
506 CommandObjectSP(new CommandObjectReproducerGenerate(interpreter
)));
507 LoadSubCommand("status", CommandObjectSP(
508 new CommandObjectReproducerStatus(interpreter
)));
509 LoadSubCommand("dump",
510 CommandObjectSP(new CommandObjectReproducerDump(interpreter
)));
511 LoadSubCommand("xcrash", CommandObjectSP(
512 new CommandObjectReproducerXCrash(interpreter
)));
515 CommandObjectReproducer::~CommandObjectReproducer() = default;