blob: a4c69db492daa9a31c5d048559c21f2ded7c422e [file] [log] [blame]
Jonas Devlieghere9e046f02018-11-13 19:18:16 +00001//===-- CommandObjectReproducer.cpp -----------------------------*- C++ -*-===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Jonas Devlieghere9e046f02018-11-13 19:18:16 +00006//
7//===----------------------------------------------------------------------===//
8
9#include "CommandObjectReproducer.h"
10
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +000011#include "lldb/Host/OptionParser.h"
Jonas Devlieghere8fc8d3f2019-09-16 23:31:06 +000012#include "lldb/Utility/GDBRemote.h"
Jonas Devliegheref4f12012019-10-17 00:02:00 +000013#include "lldb/Utility/Reproducer.h"
Jonas Devlieghere9e046f02018-11-13 19:18:16 +000014
Jonas Devliegheredf14b942018-11-15 01:05:40 +000015#include "lldb/Interpreter/CommandInterpreter.h"
Jonas Devlieghere9e046f02018-11-13 19:18:16 +000016#include "lldb/Interpreter/CommandReturnObject.h"
17#include "lldb/Interpreter/OptionArgParser.h"
18#include "lldb/Interpreter/OptionGroupBoolean.h"
19
Jonas Devliegherec8dfe902019-11-19 17:28:46 -080020#include <csignal>
21
Jonas Devlieghere9e046f02018-11-13 19:18:16 +000022using namespace lldb;
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +000023using namespace llvm;
Jonas Devlieghere9e046f02018-11-13 19:18:16 +000024using namespace lldb_private;
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +000025using namespace lldb_private::repro;
26
27enum ReproducerProvider {
28 eReproducerProviderCommands,
29 eReproducerProviderFiles,
30 eReproducerProviderGDB,
31 eReproducerProviderVersion,
Jonas Devliegheref4f12012019-10-17 00:02:00 +000032 eReproducerProviderWorkingDirectory,
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +000033 eReproducerProviderNone
34};
35
36static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
37 {
38 eReproducerProviderCommands,
39 "commands",
40 "Command Interpreter Commands",
41 },
42 {
43 eReproducerProviderFiles,
44 "files",
45 "Files",
46 },
47 {
48 eReproducerProviderGDB,
49 "gdb",
50 "GDB Remote Packets",
51 },
52 {
53 eReproducerProviderVersion,
54 "version",
55 "Version",
56 },
57 {
Jonas Devliegheref4f12012019-10-17 00:02:00 +000058 eReproducerProviderWorkingDirectory,
59 "cwd",
60 "Working Directory",
61 },
62 {
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +000063 eReproducerProviderNone,
64 "none",
65 "None",
66 },
67};
68
69static constexpr OptionEnumValues ReproducerProviderType() {
70 return OptionEnumValues(g_reproducer_provider_type);
71}
72
Jonas Devlieghere36eea5c2019-11-19 16:18:19 -080073#define LLDB_OPTIONS_reproducer_dump
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +000074#include "CommandOptions.inc"
Jonas Devlieghere9e046f02018-11-13 19:18:16 +000075
Jonas Devliegherec8dfe902019-11-19 17:28:46 -080076enum ReproducerCrashSignal {
Jonas Devliegherec8dfe902019-11-19 17:28:46 -080077 eReproducerCrashSigill,
78 eReproducerCrashSigsegv,
79};
80
81static constexpr OptionEnumValueElement g_reproducer_signaltype[] = {
82 {
Jonas Devliegherec8dfe902019-11-19 17:28:46 -080083 eReproducerCrashSigill,
84 "SIGILL",
85 "Illegal instruction",
86 },
87 {
88 eReproducerCrashSigsegv,
89 "SIGSEGV",
90 "Segmentation fault",
91 },
92};
93
94static constexpr OptionEnumValues ReproducerSignalType() {
95 return OptionEnumValues(g_reproducer_signaltype);
96}
97
98#define LLDB_OPTIONS_reproducer_xcrash
99#include "CommandOptions.inc"
100
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000101class CommandObjectReproducerGenerate : public CommandObjectParsed {
102public:
103 CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
Jonas Devlieghere973d66e2019-05-03 00:10:31 +0000104 : CommandObjectParsed(
105 interpreter, "reproducer generate",
106 "Generate reproducer on disk. When the debugger is in capture "
107 "mode, this command will output the reproducer to a directory on "
Jonas Devlieghere0cf86da2019-11-11 14:16:52 -0800108 "disk and quit. In replay mode this command in a no-op.",
Jonas Devlieghere973d66e2019-05-03 00:10:31 +0000109 nullptr) {}
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000110
111 ~CommandObjectReproducerGenerate() override = default;
112
113protected:
114 bool DoExecute(Args &command, CommandReturnObject &result) override {
115 if (!command.empty()) {
116 result.AppendErrorWithFormat("'%s' takes no arguments",
117 m_cmd_name.c_str());
118 return false;
119 }
120
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000121 auto &r = Reproducer::Instance();
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000122 if (auto generator = r.GetGenerator()) {
123 generator->Keep();
Jonas Devlieghere865cd092019-10-09 21:47:49 +0000124 } else if (r.IsReplaying()) {
Jonas Devliegherebb090bb2019-11-21 13:34:01 -0800125 // Make this operation a NO-OP in replay mode.
Jonas Devlieghere2dca6532019-02-27 17:47:06 +0000126 result.SetStatus(eReturnStatusSuccessFinishNoResult);
127 return result.Succeeded();
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000128 } else {
129 result.AppendErrorWithFormat("Unable to get the reproducer generator");
Jonas Devlieghere2dca6532019-02-27 17:47:06 +0000130 result.SetStatus(eReturnStatusFailed);
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000131 return false;
132 }
133
134 result.GetOutputStream()
135 << "Reproducer written to '" << r.GetReproducerPath() << "'\n";
Jonas Devlieghere1c5250a2019-04-02 18:23:16 +0000136 result.GetOutputStream()
137 << "Please have a look at the directory to assess if you're willing to "
138 "share the contained information.\n";
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000139
Jonas Devlieghere0cf86da2019-11-11 14:16:52 -0800140 m_interpreter.BroadcastEvent(
141 CommandInterpreter::eBroadcastBitQuitCommandReceived);
142 result.SetStatus(eReturnStatusQuit);
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000143 return result.Succeeded();
144 }
145};
146
Jonas Devliegherec8dfe902019-11-19 17:28:46 -0800147class CommandObjectReproducerXCrash : public CommandObjectParsed {
148public:
149 CommandObjectReproducerXCrash(CommandInterpreter &interpreter)
150 : CommandObjectParsed(interpreter, "reproducer xcrash",
151 "Intentionally force the debugger to crash in "
152 "order to trigger and test reproducer generation.",
153 nullptr) {}
154
155 ~CommandObjectReproducerXCrash() override = default;
156
157 Options *GetOptions() override { return &m_options; }
158
159 class CommandOptions : public Options {
160 public:
161 CommandOptions() : Options() {}
162
163 ~CommandOptions() override = default;
164
165 Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
166 ExecutionContext *execution_context) override {
167 Status error;
168 const int short_option = m_getopt_table[option_idx].val;
169
170 switch (short_option) {
171 case 's':
172 signal = (ReproducerCrashSignal)OptionArgParser::ToOptionEnum(
173 option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
174 if (!error.Success())
175 error.SetErrorStringWithFormat("unrecognized value for signal '%s'",
176 option_arg.str().c_str());
177 break;
178 default:
179 llvm_unreachable("Unimplemented option");
180 }
181
182 return error;
183 }
184
185 void OptionParsingStarting(ExecutionContext *execution_context) override {
186 signal = eReproducerCrashSigsegv;
187 }
188
189 ArrayRef<OptionDefinition> GetDefinitions() override {
190 return makeArrayRef(g_reproducer_xcrash_options);
191 }
192
193 ReproducerCrashSignal signal = eReproducerCrashSigsegv;
194 };
195
196protected:
197 bool DoExecute(Args &command, CommandReturnObject &result) override {
198 if (!command.empty()) {
199 result.AppendErrorWithFormat("'%s' takes no arguments",
200 m_cmd_name.c_str());
201 return false;
202 }
203
204 auto &r = Reproducer::Instance();
Jonas Devliegherebb090bb2019-11-21 13:34:01 -0800205
206 if (!r.IsCapturing() && !r.IsReplaying()) {
Jonas Devliegherec8dfe902019-11-19 17:28:46 -0800207 result.SetError(
208 "forcing a crash is only supported when capturing a reproducer.");
209 result.SetStatus(eReturnStatusSuccessFinishNoResult);
210 return false;
211 }
212
213 switch (m_options.signal) {
214 case eReproducerCrashSigill:
215 std::raise(SIGILL);
216 break;
Jonas Devliegherec8dfe902019-11-19 17:28:46 -0800217 case eReproducerCrashSigsegv:
218 std::raise(SIGSEGV);
219 break;
220 }
221
222 result.SetStatus(eReturnStatusQuit);
223 return result.Succeeded();
224 }
225
226private:
227 CommandOptions m_options;
228};
229
Jonas Devlieghere15eacd72018-12-03 17:28:29 +0000230class CommandObjectReproducerStatus : public CommandObjectParsed {
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000231public:
Jonas Devlieghere15eacd72018-12-03 17:28:29 +0000232 CommandObjectReproducerStatus(CommandInterpreter &interpreter)
Jonas Devlieghere973d66e2019-05-03 00:10:31 +0000233 : CommandObjectParsed(
234 interpreter, "reproducer status",
Jonas Devliegherec8dfe902019-11-19 17:28:46 -0800235 "Show the current reproducer status. In capture mode the "
236 "debugger "
Jonas Devlieghere973d66e2019-05-03 00:10:31 +0000237 "is collecting all the information it needs to create a "
238 "reproducer. In replay mode the reproducer is replaying a "
239 "reproducer. When the reproducers are off, no data is collected "
240 "and no reproducer can be generated.",
241 nullptr) {}
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000242
Jonas Devlieghere15eacd72018-12-03 17:28:29 +0000243 ~CommandObjectReproducerStatus() override = default;
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000244
245protected:
246 bool DoExecute(Args &command, CommandReturnObject &result) override {
Jonas Devlieghere15eacd72018-12-03 17:28:29 +0000247 if (!command.empty()) {
248 result.AppendErrorWithFormat("'%s' takes no arguments",
249 m_cmd_name.c_str());
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000250 return false;
251 }
252
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000253 auto &r = Reproducer::Instance();
Jonas Devlieghere865cd092019-10-09 21:47:49 +0000254 if (r.IsCapturing()) {
Jonas Devlieghere15eacd72018-12-03 17:28:29 +0000255 result.GetOutputStream() << "Reproducer is in capture mode.\n";
Jonas Devlieghere865cd092019-10-09 21:47:49 +0000256 } else if (r.IsReplaying()) {
Jonas Devlieghere15eacd72018-12-03 17:28:29 +0000257 result.GetOutputStream() << "Reproducer is in replay mode.\n";
258 } else {
Jonas Devlieghere15eacd72018-12-03 17:28:29 +0000259 result.GetOutputStream() << "Reproducer is off.\n";
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000260 }
261
Jonas Devlieghere15eacd72018-12-03 17:28:29 +0000262 result.SetStatus(eReturnStatusSuccessFinishResult);
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000263 return result.Succeeded();
264 }
265};
266
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000267static void SetError(CommandReturnObject &result, Error err) {
268 result.GetErrorStream().Printf("error: %s\n",
269 toString(std::move(err)).c_str());
270 result.SetStatus(eReturnStatusFailed);
271}
272
273class CommandObjectReproducerDump : public CommandObjectParsed {
274public:
275 CommandObjectReproducerDump(CommandInterpreter &interpreter)
276 : CommandObjectParsed(interpreter, "reproducer dump",
Jonas Devlieghere64b7d952019-10-18 21:47:31 +0000277 "Dump the information contained in a reproducer. "
278 "If no reproducer is specified during replay, it "
279 "dumps the content of the current reproducer.",
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000280 nullptr) {}
281
282 ~CommandObjectReproducerDump() override = default;
283
284 Options *GetOptions() override { return &m_options; }
285
286 class CommandOptions : public Options {
287 public:
288 CommandOptions() : Options(), file() {}
289
290 ~CommandOptions() override = default;
291
292 Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
293 ExecutionContext *execution_context) override {
294 Status error;
295 const int short_option = m_getopt_table[option_idx].val;
296
297 switch (short_option) {
298 case 'f':
299 file.SetFile(option_arg, FileSpec::Style::native);
300 FileSystem::Instance().Resolve(file);
301 break;
302 case 'p':
303 provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
304 option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
305 if (!error.Success())
306 error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
307 option_arg.str().c_str());
308 break;
309 default:
310 llvm_unreachable("Unimplemented option");
311 }
312
313 return error;
314 }
315
316 void OptionParsingStarting(ExecutionContext *execution_context) override {
317 file.Clear();
318 provider = eReproducerProviderNone;
319 }
320
321 ArrayRef<OptionDefinition> GetDefinitions() override {
Jonas Devlieghere36eea5c2019-11-19 16:18:19 -0800322 return makeArrayRef(g_reproducer_dump_options);
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000323 }
324
325 FileSpec file;
326 ReproducerProvider provider = eReproducerProviderNone;
327 };
328
329protected:
330 bool DoExecute(Args &command, CommandReturnObject &result) override {
331 if (!command.empty()) {
332 result.AppendErrorWithFormat("'%s' takes no arguments",
333 m_cmd_name.c_str());
334 return false;
335 }
336
337 // If no reproducer path is specified, use the loader currently used for
338 // replay. Otherwise create a new loader just for dumping.
339 llvm::Optional<Loader> loader_storage;
340 Loader *loader = nullptr;
341 if (!m_options.file) {
342 loader = Reproducer::Instance().GetLoader();
343 if (loader == nullptr) {
344 result.SetError(
345 "Not specifying a reproducer is only support during replay.");
346 result.SetStatus(eReturnStatusSuccessFinishNoResult);
347 return false;
348 }
349 } else {
350 loader_storage.emplace(m_options.file);
351 loader = &(*loader_storage);
352 if (Error err = loader->LoadIndex()) {
353 SetError(result, std::move(err));
354 return false;
355 }
356 }
357
358 // If we get here we should have a valid loader.
359 assert(loader);
360
361 switch (m_options.provider) {
362 case eReproducerProviderFiles: {
363 FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
364
365 // Read the VFS mapping.
366 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
367 vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
368 if (!buffer) {
369 SetError(result, errorCodeToError(buffer.getError()));
370 return false;
371 }
372
373 // Initialize a VFS from the given mapping.
374 IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
375 std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
376
377 // Dump the VFS to a buffer.
378 std::string str;
379 raw_string_ostream os(str);
380 static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
381 os.flush();
382
383 // Return the string.
384 result.AppendMessage(str);
385 result.SetStatus(eReturnStatusSuccessFinishResult);
386 return true;
387 }
388 case eReproducerProviderVersion: {
Jonas Devlieghereb2575da2019-10-17 00:01:57 +0000389 Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
390 if (!version) {
391 SetError(result, version.takeError());
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000392 return false;
393 }
Jonas Devlieghereb2575da2019-10-17 00:01:57 +0000394 result.AppendMessage(*version);
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000395 result.SetStatus(eReturnStatusSuccessFinishResult);
396 return true;
397 }
Jonas Devliegheref4f12012019-10-17 00:02:00 +0000398 case eReproducerProviderWorkingDirectory: {
399 Expected<std::string> cwd =
400 loader->LoadBuffer<WorkingDirectoryProvider>();
401 if (!cwd) {
402 SetError(result, cwd.takeError());
403 return false;
404 }
405 result.AppendMessage(*cwd);
406 result.SetStatus(eReturnStatusSuccessFinishResult);
407 return true;
408 }
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000409 case eReproducerProviderCommands: {
410 // Create a new command loader.
411 std::unique_ptr<repro::CommandLoader> command_loader =
412 repro::CommandLoader::Create(loader);
413 if (!command_loader) {
414 SetError(result,
415 make_error<StringError>(llvm::inconvertibleErrorCode(),
416 "Unable to create command loader."));
417 return false;
418 }
419
420 // Iterate over the command files and dump them.
421 while (true) {
422 llvm::Optional<std::string> command_file =
423 command_loader->GetNextFile();
424 if (!command_file)
425 break;
426
427 auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
428 if (auto err = command_buffer.getError()) {
429 SetError(result, errorCodeToError(err));
430 return false;
431 }
432 result.AppendMessage((*command_buffer)->getBuffer());
433 }
434
435 result.SetStatus(eReturnStatusSuccessFinishResult);
436 return true;
437 }
438 case eReproducerProviderGDB: {
Jonas Devlieghere8fc8d3f2019-09-16 23:31:06 +0000439 FileSpec gdb_file = loader->GetFile<ProcessGDBRemoteProvider::Info>();
440 auto error_or_file = MemoryBuffer::getFile(gdb_file.GetPath());
441 if (auto err = error_or_file.getError()) {
442 SetError(result, errorCodeToError(err));
443 return false;
444 }
445
446 std::vector<GDBRemotePacket> packets;
447 yaml::Input yin((*error_or_file)->getBuffer());
448 yin >> packets;
449
450 if (auto err = yin.error()) {
451 SetError(result, errorCodeToError(err));
452 return false;
453 }
454
Jonas Devliegheref4f12012019-10-17 00:02:00 +0000455 for (GDBRemotePacket &packet : packets) {
Jonas Devlieghere8fc8d3f2019-09-16 23:31:06 +0000456 packet.Dump(result.GetOutputStream());
457 }
458
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000459 result.SetStatus(eReturnStatusSuccessFinishResult);
460 return true;
461 }
462 case eReproducerProviderNone:
463 result.SetError("No valid provider specified.");
464 return false;
465 }
466
467 result.SetStatus(eReturnStatusSuccessFinishNoResult);
468 return result.Succeeded();
469 }
470
471private:
472 CommandOptions m_options;
473};
474
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000475CommandObjectReproducer::CommandObjectReproducer(
476 CommandInterpreter &interpreter)
Jonas Devlieghere973d66e2019-05-03 00:10:31 +0000477 : CommandObjectMultiword(
478 interpreter, "reproducer",
Jonas Devliegherec8dfe902019-11-19 17:28:46 -0800479 "Commands for manipulating reproducers. Reproducers make it "
480 "possible "
Jonas Devlieghere64b7d952019-10-18 21:47:31 +0000481 "to capture full debug sessions with all its dependencies. The "
482 "resulting reproducer is used to replay the debug session while "
483 "debugging the debugger.\n"
484 "Because reproducers need the whole the debug session from "
485 "beginning to end, you need to launch the debugger in capture or "
486 "replay mode, commonly though the command line driver.\n"
487 "Reproducers are unrelated record-replay debugging, as you cannot "
488 "interact with the debugger during replay.\n",
Jonas Devlieghere130ec062019-07-30 18:06:38 +0000489 "reproducer <subcommand> [<subcommand-options>]") {
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000490 LoadSubCommand(
491 "generate",
492 CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
Jonas Devlieghere15eacd72018-12-03 17:28:29 +0000493 LoadSubCommand("status", CommandObjectSP(
494 new CommandObjectReproducerStatus(interpreter)));
Jonas Devlieghere97fc8eb2019-09-13 23:27:31 +0000495 LoadSubCommand("dump",
496 CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
Jonas Devliegherec8dfe902019-11-19 17:28:46 -0800497 LoadSubCommand("xcrash", CommandObjectSP(
498 new CommandObjectReproducerXCrash(interpreter)));
Jonas Devlieghere9e046f02018-11-13 19:18:16 +0000499}
500
501CommandObjectReproducer::~CommandObjectReproducer() = default;