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 nullptr) {} 166 167 ~CommandObjectReproducerDump() override = default; 168 169 Options *GetOptions() override { return &m_options; } 170 171 class CommandOptions : public Options { 172 public: 173 CommandOptions() : Options(), file() {} 174 175 ~CommandOptions() override = default; 176 177 Status SetOptionValue(uint32_t option_idx, StringRef option_arg, 178 ExecutionContext *execution_context) override { 179 Status error; 180 const int short_option = m_getopt_table[option_idx].val; 181 182 switch (short_option) { 183 case 'f': 184 file.SetFile(option_arg, FileSpec::Style::native); 185 FileSystem::Instance().Resolve(file); 186 break; 187 case 'p': 188 provider = (ReproducerProvider)OptionArgParser::ToOptionEnum( 189 option_arg, GetDefinitions()[option_idx].enum_values, 0, error); 190 if (!error.Success()) 191 error.SetErrorStringWithFormat("unrecognized value for provider '%s'", 192 option_arg.str().c_str()); 193 break; 194 default: 195 llvm_unreachable("Unimplemented option"); 196 } 197 198 return error; 199 } 200 201 void OptionParsingStarting(ExecutionContext *execution_context) override { 202 file.Clear(); 203 provider = eReproducerProviderNone; 204 } 205 206 ArrayRef<OptionDefinition> GetDefinitions() override { 207 return makeArrayRef(g_reproducer_options); 208 } 209 210 FileSpec file; 211 ReproducerProvider provider = eReproducerProviderNone; 212 }; 213 214 protected: 215 bool DoExecute(Args &command, CommandReturnObject &result) override { 216 if (!command.empty()) { 217 result.AppendErrorWithFormat("'%s' takes no arguments", 218 m_cmd_name.c_str()); 219 return false; 220 } 221 222 // If no reproducer path is specified, use the loader currently used for 223 // replay. Otherwise create a new loader just for dumping. 224 llvm::Optional<Loader> loader_storage; 225 Loader *loader = nullptr; 226 if (!m_options.file) { 227 loader = Reproducer::Instance().GetLoader(); 228 if (loader == nullptr) { 229 result.SetError( 230 "Not specifying a reproducer is only support during replay."); 231 result.SetStatus(eReturnStatusSuccessFinishNoResult); 232 return false; 233 } 234 } else { 235 loader_storage.emplace(m_options.file); 236 loader = &(*loader_storage); 237 if (Error err = loader->LoadIndex()) { 238 SetError(result, std::move(err)); 239 return false; 240 } 241 } 242 243 // If we get here we should have a valid loader. 244 assert(loader); 245 246 switch (m_options.provider) { 247 case eReproducerProviderFiles: { 248 FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>(); 249 250 // Read the VFS mapping. 251 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer = 252 vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath()); 253 if (!buffer) { 254 SetError(result, errorCodeToError(buffer.getError())); 255 return false; 256 } 257 258 // Initialize a VFS from the given mapping. 259 IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML( 260 std::move(buffer.get()), nullptr, vfs_mapping.GetPath()); 261 262 // Dump the VFS to a buffer. 263 std::string str; 264 raw_string_ostream os(str); 265 static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os); 266 os.flush(); 267 268 // Return the string. 269 result.AppendMessage(str); 270 result.SetStatus(eReturnStatusSuccessFinishResult); 271 return true; 272 } 273 case eReproducerProviderVersion: { 274 Expected<std::string> version = loader->LoadBuffer<VersionProvider>(); 275 if (!version) { 276 SetError(result, version.takeError()); 277 return false; 278 } 279 result.AppendMessage(*version); 280 result.SetStatus(eReturnStatusSuccessFinishResult); 281 return true; 282 } 283 case eReproducerProviderWorkingDirectory: { 284 Expected<std::string> cwd = 285 loader->LoadBuffer<WorkingDirectoryProvider>(); 286 if (!cwd) { 287 SetError(result, cwd.takeError()); 288 return false; 289 } 290 result.AppendMessage(*cwd); 291 result.SetStatus(eReturnStatusSuccessFinishResult); 292 return true; 293 } 294 case eReproducerProviderCommands: { 295 // Create a new command loader. 296 std::unique_ptr<repro::CommandLoader> command_loader = 297 repro::CommandLoader::Create(loader); 298 if (!command_loader) { 299 SetError(result, 300 make_error<StringError>(llvm::inconvertibleErrorCode(), 301 "Unable to create command loader.")); 302 return false; 303 } 304 305 // Iterate over the command files and dump them. 306 while (true) { 307 llvm::Optional<std::string> command_file = 308 command_loader->GetNextFile(); 309 if (!command_file) 310 break; 311 312 auto command_buffer = llvm::MemoryBuffer::getFile(*command_file); 313 if (auto err = command_buffer.getError()) { 314 SetError(result, errorCodeToError(err)); 315 return false; 316 } 317 result.AppendMessage((*command_buffer)->getBuffer()); 318 } 319 320 result.SetStatus(eReturnStatusSuccessFinishResult); 321 return true; 322 } 323 case eReproducerProviderGDB: { 324 FileSpec gdb_file = loader->GetFile<ProcessGDBRemoteProvider::Info>(); 325 auto error_or_file = MemoryBuffer::getFile(gdb_file.GetPath()); 326 if (auto err = error_or_file.getError()) { 327 SetError(result, errorCodeToError(err)); 328 return false; 329 } 330 331 std::vector<GDBRemotePacket> packets; 332 yaml::Input yin((*error_or_file)->getBuffer()); 333 yin >> packets; 334 335 if (auto err = yin.error()) { 336 SetError(result, errorCodeToError(err)); 337 return false; 338 } 339 340 for (GDBRemotePacket &packet : packets) { 341 packet.Dump(result.GetOutputStream()); 342 } 343 344 result.SetStatus(eReturnStatusSuccessFinishResult); 345 return true; 346 } 347 case eReproducerProviderNone: 348 result.SetError("No valid provider specified."); 349 return false; 350 } 351 352 result.SetStatus(eReturnStatusSuccessFinishNoResult); 353 return result.Succeeded(); 354 } 355 356 private: 357 CommandOptions m_options; 358 }; 359 360 CommandObjectReproducer::CommandObjectReproducer( 361 CommandInterpreter &interpreter) 362 : CommandObjectMultiword( 363 interpreter, "reproducer", 364 "Commands for manipulate the reproducer functionality.", 365 "reproducer <subcommand> [<subcommand-options>]") { 366 LoadSubCommand( 367 "generate", 368 CommandObjectSP(new CommandObjectReproducerGenerate(interpreter))); 369 LoadSubCommand("status", CommandObjectSP( 370 new CommandObjectReproducerStatus(interpreter))); 371 LoadSubCommand("dump", 372 CommandObjectSP(new CommandObjectReproducerDump(interpreter))); 373 } 374 375 CommandObjectReproducer::~CommandObjectReproducer() = default; 376