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