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. 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 result.SetStatus(eReturnStatusSuccessFinishResult); 114 return result.Succeeded(); 115 } 116 }; 117 118 class CommandObjectReproducerStatus : public CommandObjectParsed { 119 public: 120 CommandObjectReproducerStatus(CommandInterpreter &interpreter) 121 : CommandObjectParsed( 122 interpreter, "reproducer status", 123 "Show the current reproducer status. In capture mode the debugger " 124 "is collecting all the information it needs to create a " 125 "reproducer. In replay mode the reproducer is replaying a " 126 "reproducer. When the reproducers are off, no data is collected " 127 "and no reproducer can be generated.", 128 nullptr) {} 129 130 ~CommandObjectReproducerStatus() override = default; 131 132 protected: 133 bool DoExecute(Args &command, CommandReturnObject &result) override { 134 if (!command.empty()) { 135 result.AppendErrorWithFormat("'%s' takes no arguments", 136 m_cmd_name.c_str()); 137 return false; 138 } 139 140 auto &r = Reproducer::Instance(); 141 if (r.IsCapturing()) { 142 result.GetOutputStream() << "Reproducer is in capture mode.\n"; 143 } else if (r.IsReplaying()) { 144 result.GetOutputStream() << "Reproducer is in replay mode.\n"; 145 } else { 146 result.GetOutputStream() << "Reproducer is off.\n"; 147 } 148 149 result.SetStatus(eReturnStatusSuccessFinishResult); 150 return result.Succeeded(); 151 } 152 }; 153 154 static void SetError(CommandReturnObject &result, Error err) { 155 result.GetErrorStream().Printf("error: %s\n", 156 toString(std::move(err)).c_str()); 157 result.SetStatus(eReturnStatusFailed); 158 } 159 160 class CommandObjectReproducerDump : public CommandObjectParsed { 161 public: 162 CommandObjectReproducerDump(CommandInterpreter &interpreter) 163 : CommandObjectParsed(interpreter, "reproducer dump", 164 "Dump the information contained in a reproducer. " 165 "If no reproducer is specified during replay, it " 166 "dumps the content of the current reproducer.", 167 nullptr) {} 168 169 ~CommandObjectReproducerDump() override = default; 170 171 Options *GetOptions() override { return &m_options; } 172 173 class CommandOptions : public Options { 174 public: 175 CommandOptions() : Options(), file() {} 176 177 ~CommandOptions() override = default; 178 179 Status SetOptionValue(uint32_t option_idx, StringRef option_arg, 180 ExecutionContext *execution_context) override { 181 Status error; 182 const int short_option = m_getopt_table[option_idx].val; 183 184 switch (short_option) { 185 case 'f': 186 file.SetFile(option_arg, FileSpec::Style::native); 187 FileSystem::Instance().Resolve(file); 188 break; 189 case 'p': 190 provider = (ReproducerProvider)OptionArgParser::ToOptionEnum( 191 option_arg, GetDefinitions()[option_idx].enum_values, 0, error); 192 if (!error.Success()) 193 error.SetErrorStringWithFormat("unrecognized value for provider '%s'", 194 option_arg.str().c_str()); 195 break; 196 default: 197 llvm_unreachable("Unimplemented option"); 198 } 199 200 return error; 201 } 202 203 void OptionParsingStarting(ExecutionContext *execution_context) override { 204 file.Clear(); 205 provider = eReproducerProviderNone; 206 } 207 208 ArrayRef<OptionDefinition> GetDefinitions() override { 209 return makeArrayRef(g_reproducer_options); 210 } 211 212 FileSpec file; 213 ReproducerProvider provider = eReproducerProviderNone; 214 }; 215 216 protected: 217 bool DoExecute(Args &command, CommandReturnObject &result) override { 218 if (!command.empty()) { 219 result.AppendErrorWithFormat("'%s' takes no arguments", 220 m_cmd_name.c_str()); 221 return false; 222 } 223 224 // If no reproducer path is specified, use the loader currently used for 225 // replay. Otherwise create a new loader just for dumping. 226 llvm::Optional<Loader> loader_storage; 227 Loader *loader = nullptr; 228 if (!m_options.file) { 229 loader = Reproducer::Instance().GetLoader(); 230 if (loader == nullptr) { 231 result.SetError( 232 "Not specifying a reproducer is only support during replay."); 233 result.SetStatus(eReturnStatusSuccessFinishNoResult); 234 return false; 235 } 236 } else { 237 loader_storage.emplace(m_options.file); 238 loader = &(*loader_storage); 239 if (Error err = loader->LoadIndex()) { 240 SetError(result, std::move(err)); 241 return false; 242 } 243 } 244 245 // If we get here we should have a valid loader. 246 assert(loader); 247 248 switch (m_options.provider) { 249 case eReproducerProviderFiles: { 250 FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>(); 251 252 // Read the VFS mapping. 253 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer = 254 vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath()); 255 if (!buffer) { 256 SetError(result, errorCodeToError(buffer.getError())); 257 return false; 258 } 259 260 // Initialize a VFS from the given mapping. 261 IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML( 262 std::move(buffer.get()), nullptr, vfs_mapping.GetPath()); 263 264 // Dump the VFS to a buffer. 265 std::string str; 266 raw_string_ostream os(str); 267 static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os); 268 os.flush(); 269 270 // Return the string. 271 result.AppendMessage(str); 272 result.SetStatus(eReturnStatusSuccessFinishResult); 273 return true; 274 } 275 case eReproducerProviderVersion: { 276 Expected<std::string> version = loader->LoadBuffer<VersionProvider>(); 277 if (!version) { 278 SetError(result, version.takeError()); 279 return false; 280 } 281 result.AppendMessage(*version); 282 result.SetStatus(eReturnStatusSuccessFinishResult); 283 return true; 284 } 285 case eReproducerProviderWorkingDirectory: { 286 Expected<std::string> cwd = 287 loader->LoadBuffer<WorkingDirectoryProvider>(); 288 if (!cwd) { 289 SetError(result, cwd.takeError()); 290 return false; 291 } 292 result.AppendMessage(*cwd); 293 result.SetStatus(eReturnStatusSuccessFinishResult); 294 return true; 295 } 296 case eReproducerProviderCommands: { 297 // Create a new command loader. 298 std::unique_ptr<repro::CommandLoader> command_loader = 299 repro::CommandLoader::Create(loader); 300 if (!command_loader) { 301 SetError(result, 302 make_error<StringError>(llvm::inconvertibleErrorCode(), 303 "Unable to create command loader.")); 304 return false; 305 } 306 307 // Iterate over the command files and dump them. 308 while (true) { 309 llvm::Optional<std::string> command_file = 310 command_loader->GetNextFile(); 311 if (!command_file) 312 break; 313 314 auto command_buffer = llvm::MemoryBuffer::getFile(*command_file); 315 if (auto err = command_buffer.getError()) { 316 SetError(result, errorCodeToError(err)); 317 return false; 318 } 319 result.AppendMessage((*command_buffer)->getBuffer()); 320 } 321 322 result.SetStatus(eReturnStatusSuccessFinishResult); 323 return true; 324 } 325 case eReproducerProviderGDB: { 326 FileSpec gdb_file = loader->GetFile<ProcessGDBRemoteProvider::Info>(); 327 auto error_or_file = MemoryBuffer::getFile(gdb_file.GetPath()); 328 if (auto err = error_or_file.getError()) { 329 SetError(result, errorCodeToError(err)); 330 return false; 331 } 332 333 std::vector<GDBRemotePacket> packets; 334 yaml::Input yin((*error_or_file)->getBuffer()); 335 yin >> packets; 336 337 if (auto err = yin.error()) { 338 SetError(result, errorCodeToError(err)); 339 return false; 340 } 341 342 for (GDBRemotePacket &packet : packets) { 343 packet.Dump(result.GetOutputStream()); 344 } 345 346 result.SetStatus(eReturnStatusSuccessFinishResult); 347 return true; 348 } 349 case eReproducerProviderNone: 350 result.SetError("No valid provider specified."); 351 return false; 352 } 353 354 result.SetStatus(eReturnStatusSuccessFinishNoResult); 355 return result.Succeeded(); 356 } 357 358 private: 359 CommandOptions m_options; 360 }; 361 362 CommandObjectReproducer::CommandObjectReproducer( 363 CommandInterpreter &interpreter) 364 : CommandObjectMultiword( 365 interpreter, "reproducer", 366 "Commands for manipulating reproducers. Reproducers make it possible " 367 "to capture full debug sessions with all its dependencies. The " 368 "resulting reproducer is used to replay the debug session while " 369 "debugging the debugger.\n" 370 "Because reproducers need the whole the debug session from " 371 "beginning to end, you need to launch the debugger in capture or " 372 "replay mode, commonly though the command line driver.\n" 373 "Reproducers are unrelated record-replay debugging, as you cannot " 374 "interact with the debugger during replay.\n", 375 "reproducer <subcommand> [<subcommand-options>]") { 376 LoadSubCommand( 377 "generate", 378 CommandObjectSP(new CommandObjectReproducerGenerate(interpreter))); 379 LoadSubCommand("status", CommandObjectSP( 380 new CommandObjectReproducerStatus(interpreter))); 381 LoadSubCommand("dump", 382 CommandObjectSP(new CommandObjectReproducerDump(interpreter))); 383 } 384 385 CommandObjectReproducer::~CommandObjectReproducer() = default; 386