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 #include "lldb/Interpreter/OptionGroupBoolean.h" 19 20 using namespace lldb; 21 using namespace llvm; 22 using namespace lldb_private; 23 using namespace lldb_private::repro; 24 25 enum ReproducerProvider { 26 eReproducerProviderCommands, 27 eReproducerProviderFiles, 28 eReproducerProviderGDB, 29 eReproducerProviderVersion, 30 eReproducerProviderWorkingDirectory, 31 eReproducerProviderNone 32 }; 33 34 static constexpr OptionEnumValueElement g_reproducer_provider_type[] = { 35 { 36 eReproducerProviderCommands, 37 "commands", 38 "Command Interpreter Commands", 39 }, 40 { 41 eReproducerProviderFiles, 42 "files", 43 "Files", 44 }, 45 { 46 eReproducerProviderGDB, 47 "gdb", 48 "GDB Remote Packets", 49 }, 50 { 51 eReproducerProviderVersion, 52 "version", 53 "Version", 54 }, 55 { 56 eReproducerProviderWorkingDirectory, 57 "cwd", 58 "Working Directory", 59 }, 60 { 61 eReproducerProviderNone, 62 "none", 63 "None", 64 }, 65 }; 66 67 static constexpr OptionEnumValues ReproducerProviderType() { 68 return OptionEnumValues(g_reproducer_provider_type); 69 } 70 71 #define LLDB_OPTIONS_reproducer 72 #include "CommandOptions.inc" 73 74 class CommandObjectReproducerGenerate : public CommandObjectParsed { 75 public: 76 CommandObjectReproducerGenerate(CommandInterpreter &interpreter) 77 : CommandObjectParsed( 78 interpreter, "reproducer generate", 79 "Generate reproducer on disk. When the debugger is in capture " 80 "mode, this command will output the reproducer to a directory on " 81 "disk and quit. In replay mode this command in a no-op.", 82 nullptr) {} 83 84 ~CommandObjectReproducerGenerate() override = default; 85 86 protected: 87 bool DoExecute(Args &command, CommandReturnObject &result) override { 88 if (!command.empty()) { 89 result.AppendErrorWithFormat("'%s' takes no arguments", 90 m_cmd_name.c_str()); 91 return false; 92 } 93 94 auto &r = Reproducer::Instance(); 95 if (auto generator = r.GetGenerator()) { 96 generator->Keep(); 97 } else if (r.IsReplaying()) { 98 // Make this operation a NOP in replay mode. 99 result.SetStatus(eReturnStatusSuccessFinishNoResult); 100 return result.Succeeded(); 101 } else { 102 result.AppendErrorWithFormat("Unable to get the reproducer generator"); 103 result.SetStatus(eReturnStatusFailed); 104 return false; 105 } 106 107 result.GetOutputStream() 108 << "Reproducer written to '" << r.GetReproducerPath() << "'\n"; 109 result.GetOutputStream() 110 << "Please have a look at the directory to assess if you're willing to " 111 "share the contained information.\n"; 112 113 m_interpreter.BroadcastEvent( 114 CommandInterpreter::eBroadcastBitQuitCommandReceived); 115 result.SetStatus(eReturnStatusQuit); 116 return result.Succeeded(); 117 } 118 }; 119 120 class CommandObjectReproducerStatus : public CommandObjectParsed { 121 public: 122 CommandObjectReproducerStatus(CommandInterpreter &interpreter) 123 : CommandObjectParsed( 124 interpreter, "reproducer status", 125 "Show the current reproducer status. In capture mode the debugger " 126 "is collecting all the information it needs to create a " 127 "reproducer. In replay mode the reproducer is replaying a " 128 "reproducer. When the reproducers are off, no data is collected " 129 "and no reproducer can be generated.", 130 nullptr) {} 131 132 ~CommandObjectReproducerStatus() override = default; 133 134 protected: 135 bool DoExecute(Args &command, CommandReturnObject &result) override { 136 if (!command.empty()) { 137 result.AppendErrorWithFormat("'%s' takes no arguments", 138 m_cmd_name.c_str()); 139 return false; 140 } 141 142 auto &r = Reproducer::Instance(); 143 if (r.IsCapturing()) { 144 result.GetOutputStream() << "Reproducer is in capture mode.\n"; 145 } else if (r.IsReplaying()) { 146 result.GetOutputStream() << "Reproducer is in replay mode.\n"; 147 } else { 148 result.GetOutputStream() << "Reproducer is off.\n"; 149 } 150 151 result.SetStatus(eReturnStatusSuccessFinishResult); 152 return result.Succeeded(); 153 } 154 }; 155 156 static void SetError(CommandReturnObject &result, Error err) { 157 result.GetErrorStream().Printf("error: %s\n", 158 toString(std::move(err)).c_str()); 159 result.SetStatus(eReturnStatusFailed); 160 } 161 162 class CommandObjectReproducerDump : public CommandObjectParsed { 163 public: 164 CommandObjectReproducerDump(CommandInterpreter &interpreter) 165 : CommandObjectParsed(interpreter, "reproducer dump", 166 "Dump the information contained in a reproducer. " 167 "If no reproducer is specified during replay, it " 168 "dumps the content of the current reproducer.", 169 nullptr) {} 170 171 ~CommandObjectReproducerDump() override = default; 172 173 Options *GetOptions() override { return &m_options; } 174 175 class CommandOptions : public Options { 176 public: 177 CommandOptions() : Options(), file() {} 178 179 ~CommandOptions() override = default; 180 181 Status SetOptionValue(uint32_t option_idx, StringRef option_arg, 182 ExecutionContext *execution_context) override { 183 Status error; 184 const int short_option = m_getopt_table[option_idx].val; 185 186 switch (short_option) { 187 case 'f': 188 file.SetFile(option_arg, FileSpec::Style::native); 189 FileSystem::Instance().Resolve(file); 190 break; 191 case 'p': 192 provider = (ReproducerProvider)OptionArgParser::ToOptionEnum( 193 option_arg, GetDefinitions()[option_idx].enum_values, 0, error); 194 if (!error.Success()) 195 error.SetErrorStringWithFormat("unrecognized value for provider '%s'", 196 option_arg.str().c_str()); 197 break; 198 default: 199 llvm_unreachable("Unimplemented option"); 200 } 201 202 return error; 203 } 204 205 void OptionParsingStarting(ExecutionContext *execution_context) override { 206 file.Clear(); 207 provider = eReproducerProviderNone; 208 } 209 210 ArrayRef<OptionDefinition> GetDefinitions() override { 211 return makeArrayRef(g_reproducer_options); 212 } 213 214 FileSpec file; 215 ReproducerProvider provider = eReproducerProviderNone; 216 }; 217 218 protected: 219 bool DoExecute(Args &command, CommandReturnObject &result) override { 220 if (!command.empty()) { 221 result.AppendErrorWithFormat("'%s' takes no arguments", 222 m_cmd_name.c_str()); 223 return false; 224 } 225 226 // If no reproducer path is specified, use the loader currently used for 227 // replay. Otherwise create a new loader just for dumping. 228 llvm::Optional<Loader> loader_storage; 229 Loader *loader = nullptr; 230 if (!m_options.file) { 231 loader = Reproducer::Instance().GetLoader(); 232 if (loader == nullptr) { 233 result.SetError( 234 "Not specifying a reproducer is only support during replay."); 235 result.SetStatus(eReturnStatusSuccessFinishNoResult); 236 return false; 237 } 238 } else { 239 loader_storage.emplace(m_options.file); 240 loader = &(*loader_storage); 241 if (Error err = loader->LoadIndex()) { 242 SetError(result, std::move(err)); 243 return false; 244 } 245 } 246 247 // If we get here we should have a valid loader. 248 assert(loader); 249 250 switch (m_options.provider) { 251 case eReproducerProviderFiles: { 252 FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>(); 253 254 // Read the VFS mapping. 255 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer = 256 vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath()); 257 if (!buffer) { 258 SetError(result, errorCodeToError(buffer.getError())); 259 return false; 260 } 261 262 // Initialize a VFS from the given mapping. 263 IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML( 264 std::move(buffer.get()), nullptr, vfs_mapping.GetPath()); 265 266 // Dump the VFS to a buffer. 267 std::string str; 268 raw_string_ostream os(str); 269 static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os); 270 os.flush(); 271 272 // Return the string. 273 result.AppendMessage(str); 274 result.SetStatus(eReturnStatusSuccessFinishResult); 275 return true; 276 } 277 case eReproducerProviderVersion: { 278 Expected<std::string> version = loader->LoadBuffer<VersionProvider>(); 279 if (!version) { 280 SetError(result, version.takeError()); 281 return false; 282 } 283 result.AppendMessage(*version); 284 result.SetStatus(eReturnStatusSuccessFinishResult); 285 return true; 286 } 287 case eReproducerProviderWorkingDirectory: { 288 Expected<std::string> cwd = 289 loader->LoadBuffer<WorkingDirectoryProvider>(); 290 if (!cwd) { 291 SetError(result, cwd.takeError()); 292 return false; 293 } 294 result.AppendMessage(*cwd); 295 result.SetStatus(eReturnStatusSuccessFinishResult); 296 return true; 297 } 298 case eReproducerProviderCommands: { 299 // Create a new command loader. 300 std::unique_ptr<repro::CommandLoader> command_loader = 301 repro::CommandLoader::Create(loader); 302 if (!command_loader) { 303 SetError(result, 304 make_error<StringError>(llvm::inconvertibleErrorCode(), 305 "Unable to create command loader.")); 306 return false; 307 } 308 309 // Iterate over the command files and dump them. 310 while (true) { 311 llvm::Optional<std::string> command_file = 312 command_loader->GetNextFile(); 313 if (!command_file) 314 break; 315 316 auto command_buffer = llvm::MemoryBuffer::getFile(*command_file); 317 if (auto err = command_buffer.getError()) { 318 SetError(result, errorCodeToError(err)); 319 return false; 320 } 321 result.AppendMessage((*command_buffer)->getBuffer()); 322 } 323 324 result.SetStatus(eReturnStatusSuccessFinishResult); 325 return true; 326 } 327 case eReproducerProviderGDB: { 328 FileSpec gdb_file = loader->GetFile<ProcessGDBRemoteProvider::Info>(); 329 auto error_or_file = MemoryBuffer::getFile(gdb_file.GetPath()); 330 if (auto err = error_or_file.getError()) { 331 SetError(result, errorCodeToError(err)); 332 return false; 333 } 334 335 std::vector<GDBRemotePacket> packets; 336 yaml::Input yin((*error_or_file)->getBuffer()); 337 yin >> packets; 338 339 if (auto err = yin.error()) { 340 SetError(result, errorCodeToError(err)); 341 return false; 342 } 343 344 for (GDBRemotePacket &packet : packets) { 345 packet.Dump(result.GetOutputStream()); 346 } 347 348 result.SetStatus(eReturnStatusSuccessFinishResult); 349 return true; 350 } 351 case eReproducerProviderNone: 352 result.SetError("No valid provider specified."); 353 return false; 354 } 355 356 result.SetStatus(eReturnStatusSuccessFinishNoResult); 357 return result.Succeeded(); 358 } 359 360 private: 361 CommandOptions m_options; 362 }; 363 364 CommandObjectReproducer::CommandObjectReproducer( 365 CommandInterpreter &interpreter) 366 : CommandObjectMultiword( 367 interpreter, "reproducer", 368 "Commands for manipulating reproducers. Reproducers make it possible " 369 "to capture full debug sessions with all its dependencies. The " 370 "resulting reproducer is used to replay the debug session while " 371 "debugging the debugger.\n" 372 "Because reproducers need the whole the debug session from " 373 "beginning to end, you need to launch the debugger in capture or " 374 "replay mode, commonly though the command line driver.\n" 375 "Reproducers are unrelated record-replay debugging, as you cannot " 376 "interact with the debugger during replay.\n", 377 "reproducer <subcommand> [<subcommand-options>]") { 378 LoadSubCommand( 379 "generate", 380 CommandObjectSP(new CommandObjectReproducerGenerate(interpreter))); 381 LoadSubCommand("status", CommandObjectSP( 382 new CommandObjectReproducerStatus(interpreter))); 383 LoadSubCommand("dump", 384 CommandObjectSP(new CommandObjectReproducerDump(interpreter))); 385 } 386 387 CommandObjectReproducer::~CommandObjectReproducer() = default; 388