1 //===-- CommandObjectReproducer.cpp -----------------------------*- 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 #include "CommandObjectReproducer.h"
10
11 #include "lldb/Host/OptionParser.h"
12 #include "lldb/Utility/GDBRemote.h"
13 #include "lldb/Utility/Reproducer.h"
14
15 #include "lldb/Interpreter/CommandInterpreter.h"
16 #include "lldb/Interpreter/CommandReturnObject.h"
17 #include "lldb/Interpreter/OptionArgParser.h"
18
19 #include <csignal>
20
21 using namespace lldb;
22 using namespace llvm;
23 using namespace lldb_private;
24 using namespace lldb_private::repro;
25
26 enum ReproducerProvider {
27 eReproducerProviderCommands,
28 eReproducerProviderFiles,
29 eReproducerProviderGDB,
30 eReproducerProviderVersion,
31 eReproducerProviderWorkingDirectory,
32 eReproducerProviderNone
33 };
34
35 static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
36 {
37 eReproducerProviderCommands,
38 "commands",
39 "Command Interpreter Commands",
40 },
41 {
42 eReproducerProviderFiles,
43 "files",
44 "Files",
45 },
46 {
47 eReproducerProviderGDB,
48 "gdb",
49 "GDB Remote Packets",
50 },
51 {
52 eReproducerProviderVersion,
53 "version",
54 "Version",
55 },
56 {
57 eReproducerProviderWorkingDirectory,
58 "cwd",
59 "Working Directory",
60 },
61 {
62 eReproducerProviderNone,
63 "none",
64 "None",
65 },
66 };
67
ReproducerProviderType()68 static constexpr OptionEnumValues ReproducerProviderType() {
69 return OptionEnumValues(g_reproducer_provider_type);
70 }
71
72 #define LLDB_OPTIONS_reproducer_dump
73 #include "CommandOptions.inc"
74
75 enum ReproducerCrashSignal {
76 eReproducerCrashSigill,
77 eReproducerCrashSigsegv,
78 };
79
80 static constexpr OptionEnumValueElement g_reproducer_signaltype[] = {
81 {
82 eReproducerCrashSigill,
83 "SIGILL",
84 "Illegal instruction",
85 },
86 {
87 eReproducerCrashSigsegv,
88 "SIGSEGV",
89 "Segmentation fault",
90 },
91 };
92
ReproducerSignalType()93 static constexpr OptionEnumValues ReproducerSignalType() {
94 return OptionEnumValues(g_reproducer_signaltype);
95 }
96
97 #define LLDB_OPTIONS_reproducer_xcrash
98 #include "CommandOptions.inc"
99
100 class CommandObjectReproducerGenerate : public CommandObjectParsed {
101 public:
CommandObjectReproducerGenerate(CommandInterpreter & interpreter)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.",
108 nullptr) {}
109
110 ~CommandObjectReproducerGenerate() override = default;
111
112 protected:
DoExecute(Args & command,CommandReturnObject & result)113 bool DoExecute(Args &command, CommandReturnObject &result) override {
114 if (!command.empty()) {
115 result.AppendErrorWithFormat("'%s' takes no arguments",
116 m_cmd_name.c_str());
117 return false;
118 }
119
120 auto &r = Reproducer::Instance();
121 if (auto generator = r.GetGenerator()) {
122 generator->Keep();
123 } else if (r.IsReplaying()) {
124 // Make this operation a NO-OP in replay mode.
125 result.SetStatus(eReturnStatusSuccessFinishNoResult);
126 return result.Succeeded();
127 } else {
128 result.AppendErrorWithFormat("Unable to get the reproducer generator");
129 result.SetStatus(eReturnStatusFailed);
130 return false;
131 }
132
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";
138
139 m_interpreter.BroadcastEvent(
140 CommandInterpreter::eBroadcastBitQuitCommandReceived);
141 result.SetStatus(eReturnStatusQuit);
142 return result.Succeeded();
143 }
144 };
145
146 class CommandObjectReproducerXCrash : public CommandObjectParsed {
147 public:
CommandObjectReproducerXCrash(CommandInterpreter & interpreter)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.",
152 nullptr) {}
153
154 ~CommandObjectReproducerXCrash() override = default;
155
GetOptions()156 Options *GetOptions() override { return &m_options; }
157
158 class CommandOptions : public Options {
159 public:
CommandOptions()160 CommandOptions() : Options() {}
161
162 ~CommandOptions() override = default;
163
SetOptionValue(uint32_t option_idx,StringRef option_arg,ExecutionContext * execution_context)164 Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
165 ExecutionContext *execution_context) override {
166 Status error;
167 const int short_option = m_getopt_table[option_idx].val;
168
169 switch (short_option) {
170 case 's':
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());
176 break;
177 default:
178 llvm_unreachable("Unimplemented option");
179 }
180
181 return error;
182 }
183
OptionParsingStarting(ExecutionContext * execution_context)184 void OptionParsingStarting(ExecutionContext *execution_context) override {
185 signal = eReproducerCrashSigsegv;
186 }
187
GetDefinitions()188 ArrayRef<OptionDefinition> GetDefinitions() override {
189 return makeArrayRef(g_reproducer_xcrash_options);
190 }
191
192 ReproducerCrashSignal signal = eReproducerCrashSigsegv;
193 };
194
195 protected:
DoExecute(Args & command,CommandReturnObject & result)196 bool DoExecute(Args &command, CommandReturnObject &result) override {
197 if (!command.empty()) {
198 result.AppendErrorWithFormat("'%s' takes no arguments",
199 m_cmd_name.c_str());
200 return false;
201 }
202
203 auto &r = Reproducer::Instance();
204
205 if (!r.IsCapturing() && !r.IsReplaying()) {
206 result.SetError(
207 "forcing a crash is only supported when capturing a reproducer.");
208 result.SetStatus(eReturnStatusSuccessFinishNoResult);
209 return false;
210 }
211
212 switch (m_options.signal) {
213 case eReproducerCrashSigill:
214 std::raise(SIGILL);
215 break;
216 case eReproducerCrashSigsegv:
217 std::raise(SIGSEGV);
218 break;
219 }
220
221 result.SetStatus(eReturnStatusQuit);
222 return result.Succeeded();
223 }
224
225 private:
226 CommandOptions m_options;
227 };
228
229 class CommandObjectReproducerStatus : public CommandObjectParsed {
230 public:
CommandObjectReproducerStatus(CommandInterpreter & interpreter)231 CommandObjectReproducerStatus(CommandInterpreter &interpreter)
232 : CommandObjectParsed(
233 interpreter, "reproducer status",
234 "Show the current reproducer status. In capture mode the "
235 "debugger "
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.",
240 nullptr) {}
241
242 ~CommandObjectReproducerStatus() override = default;
243
244 protected:
DoExecute(Args & command,CommandReturnObject & result)245 bool DoExecute(Args &command, CommandReturnObject &result) override {
246 if (!command.empty()) {
247 result.AppendErrorWithFormat("'%s' takes no arguments",
248 m_cmd_name.c_str());
249 return false;
250 }
251
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";
257 } else {
258 result.GetOutputStream() << "Reproducer is off.\n";
259 }
260
261 result.SetStatus(eReturnStatusSuccessFinishResult);
262 return result.Succeeded();
263 }
264 };
265
SetError(CommandReturnObject & result,Error err)266 static void SetError(CommandReturnObject &result, Error err) {
267 result.GetErrorStream().Printf("error: %s\n",
268 toString(std::move(err)).c_str());
269 result.SetStatus(eReturnStatusFailed);
270 }
271
272 class CommandObjectReproducerDump : public CommandObjectParsed {
273 public:
CommandObjectReproducerDump(CommandInterpreter & interpreter)274 CommandObjectReproducerDump(CommandInterpreter &interpreter)
275 : CommandObjectParsed(interpreter, "reproducer dump",
276 "Dump the information contained in a reproducer. "
277 "If no reproducer is specified during replay, it "
278 "dumps the content of the current reproducer.",
279 nullptr) {}
280
281 ~CommandObjectReproducerDump() override = default;
282
GetOptions()283 Options *GetOptions() override { return &m_options; }
284
285 class CommandOptions : public Options {
286 public:
CommandOptions()287 CommandOptions() : Options(), file() {}
288
289 ~CommandOptions() override = default;
290
SetOptionValue(uint32_t option_idx,StringRef option_arg,ExecutionContext * execution_context)291 Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
292 ExecutionContext *execution_context) override {
293 Status error;
294 const int short_option = m_getopt_table[option_idx].val;
295
296 switch (short_option) {
297 case 'f':
298 file.SetFile(option_arg, FileSpec::Style::native);
299 FileSystem::Instance().Resolve(file);
300 break;
301 case 'p':
302 provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
303 option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
304 if (!error.Success())
305 error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
306 option_arg.str().c_str());
307 break;
308 default:
309 llvm_unreachable("Unimplemented option");
310 }
311
312 return error;
313 }
314
OptionParsingStarting(ExecutionContext * execution_context)315 void OptionParsingStarting(ExecutionContext *execution_context) override {
316 file.Clear();
317 provider = eReproducerProviderNone;
318 }
319
GetDefinitions()320 ArrayRef<OptionDefinition> GetDefinitions() override {
321 return makeArrayRef(g_reproducer_dump_options);
322 }
323
324 FileSpec file;
325 ReproducerProvider provider = eReproducerProviderNone;
326 };
327
328 protected:
DoExecute(Args & command,CommandReturnObject & result)329 bool DoExecute(Args &command, CommandReturnObject &result) override {
330 if (!command.empty()) {
331 result.AppendErrorWithFormat("'%s' takes no arguments",
332 m_cmd_name.c_str());
333 return false;
334 }
335
336 // If no reproducer path is specified, use the loader currently used for
337 // replay. Otherwise create a new loader just for dumping.
338 llvm::Optional<Loader> loader_storage;
339 Loader *loader = nullptr;
340 if (!m_options.file) {
341 loader = Reproducer::Instance().GetLoader();
342 if (loader == nullptr) {
343 result.SetError(
344 "Not specifying a reproducer is only support during replay.");
345 result.SetStatus(eReturnStatusSuccessFinishNoResult);
346 return false;
347 }
348 } else {
349 loader_storage.emplace(m_options.file);
350 loader = &(*loader_storage);
351 if (Error err = loader->LoadIndex()) {
352 SetError(result, std::move(err));
353 return false;
354 }
355 }
356
357 // If we get here we should have a valid loader.
358 assert(loader);
359
360 switch (m_options.provider) {
361 case eReproducerProviderFiles: {
362 FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
363
364 // Read the VFS mapping.
365 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
366 vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
367 if (!buffer) {
368 SetError(result, errorCodeToError(buffer.getError()));
369 return false;
370 }
371
372 // Initialize a VFS from the given mapping.
373 IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
374 std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
375
376 // Dump the VFS to a buffer.
377 std::string str;
378 raw_string_ostream os(str);
379 static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
380 os.flush();
381
382 // Return the string.
383 result.AppendMessage(str);
384 result.SetStatus(eReturnStatusSuccessFinishResult);
385 return true;
386 }
387 case eReproducerProviderVersion: {
388 Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
389 if (!version) {
390 SetError(result, version.takeError());
391 return false;
392 }
393 result.AppendMessage(*version);
394 result.SetStatus(eReturnStatusSuccessFinishResult);
395 return true;
396 }
397 case eReproducerProviderWorkingDirectory: {
398 Expected<std::string> cwd =
399 loader->LoadBuffer<WorkingDirectoryProvider>();
400 if (!cwd) {
401 SetError(result, cwd.takeError());
402 return false;
403 }
404 result.AppendMessage(*cwd);
405 result.SetStatus(eReturnStatusSuccessFinishResult);
406 return true;
407 }
408 case eReproducerProviderCommands: {
409 std::unique_ptr<repro::MultiLoader<repro::CommandProvider>> multi_loader =
410 repro::MultiLoader<repro::CommandProvider>::Create(loader);
411 if (!multi_loader) {
412 SetError(result,
413 make_error<StringError>(llvm::inconvertibleErrorCode(),
414 "Unable to create command loader."));
415 return false;
416 }
417
418 // Iterate over the command files and dump them.
419 llvm::Optional<std::string> command_file;
420 while ((command_file = multi_loader->GetNextFile())) {
421 if (!command_file)
422 break;
423
424 auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
425 if (auto err = command_buffer.getError()) {
426 SetError(result, errorCodeToError(err));
427 return false;
428 }
429 result.AppendMessage((*command_buffer)->getBuffer());
430 }
431
432 result.SetStatus(eReturnStatusSuccessFinishResult);
433 return true;
434 }
435 case eReproducerProviderGDB: {
436 std::unique_ptr<repro::MultiLoader<repro::GDBRemoteProvider>>
437 multi_loader =
438 repro::MultiLoader<repro::GDBRemoteProvider>::Create(loader);
439 llvm::Optional<std::string> gdb_file;
440 while ((gdb_file = multi_loader->GetNextFile())) {
441 auto error_or_file = MemoryBuffer::getFile(*gdb_file);
442 if (auto err = error_or_file.getError()) {
443 SetError(result, errorCodeToError(err));
444 return false;
445 }
446
447 std::vector<GDBRemotePacket> packets;
448 yaml::Input yin((*error_or_file)->getBuffer());
449 yin >> packets;
450
451 if (auto err = yin.error()) {
452 SetError(result, errorCodeToError(err));
453 return false;
454 }
455
456 for (GDBRemotePacket &packet : packets) {
457 packet.Dump(result.GetOutputStream());
458 }
459 }
460
461 result.SetStatus(eReturnStatusSuccessFinishResult);
462 return true;
463 }
464 case eReproducerProviderNone:
465 result.SetError("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