1 /* 2 * Copyright 2011-2014, Rene Gollent, rene@gollent.com. 3 * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de. 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include "CommandLineUserInterface.h" 9 10 #include <stdio.h> 11 12 #include <algorithm> 13 14 #include <ArgumentVector.h> 15 #include <AutoDeleter.h> 16 #include <AutoLocker.h> 17 #include <Referenceable.h> 18 19 #include "CliContext.h" 20 #include "CliContinueCommand.h" 21 #include "CliDebugReportCommand.h" 22 #include "CliDumpMemoryCommand.h" 23 #include "CliPrintVariableCommand.h" 24 #include "CliQuitCommand.h" 25 #include "CliStackFrameCommand.h" 26 #include "CliStackTraceCommand.h" 27 #include "CliStopCommand.h" 28 #include "CliThreadCommand.h" 29 #include "CliThreadsCommand.h" 30 #include "CliVariablesCommand.h" 31 32 33 static const char* kDebuggerPrompt = "debugger> "; 34 35 36 // #pragma mark - CommandEntry 37 38 39 struct CommandLineUserInterface::CommandEntry { 40 CommandEntry(const BString& name, CliCommand* command) 41 : 42 fName(name), 43 fCommand(command) 44 { 45 } 46 47 const BString& Name() const 48 { 49 return fName; 50 } 51 52 CliCommand* Command() const 53 { 54 return fCommand.Get(); 55 } 56 57 private: 58 BString fName; 59 BReference<CliCommand> fCommand; 60 }; 61 62 63 // #pragma mark - HelpCommand 64 65 66 struct CommandLineUserInterface::HelpCommand : CliCommand { 67 HelpCommand(CommandLineUserInterface* userInterface) 68 : 69 CliCommand("print help for a command or a list of all commands", 70 "%s [ <command> ]\n" 71 "Prints help for command <command>, if given, or a list of all " 72 "commands\n" 73 "otherwise."), 74 fUserInterface(userInterface) 75 { 76 } 77 78 virtual void Execute(int argc, const char* const* argv, CliContext& context) 79 { 80 if (argc > 2) { 81 PrintUsage(argv[0]); 82 return; 83 } 84 85 fUserInterface->_PrintHelp(argc == 2 ? argv[1] : NULL); 86 } 87 88 private: 89 CommandLineUserInterface* fUserInterface; 90 }; 91 92 93 // #pragma mark - CommandLineUserInterface 94 95 96 CommandLineUserInterface::CommandLineUserInterface(bool saveReport, 97 const char* reportPath, thread_id reportTargetThread) 98 : 99 fCommands(20, true), 100 fReportPath(reportPath), 101 fSaveReport(saveReport), 102 fReportTargetThread(reportTargetThread), 103 fShowSemaphore(-1), 104 fShown(false), 105 fTerminating(false) 106 { 107 } 108 109 110 CommandLineUserInterface::~CommandLineUserInterface() 111 { 112 if (fShowSemaphore >= 0) 113 delete_sem(fShowSemaphore); 114 } 115 116 117 const char* 118 CommandLineUserInterface::ID() const 119 { 120 return "BasicCommandLineUserInterface"; 121 } 122 123 124 status_t 125 CommandLineUserInterface::Init(Team* team, UserInterfaceListener* listener) 126 { 127 status_t error = fContext.Init(team, listener); 128 if (error != B_OK) 129 return error; 130 131 error = _RegisterCommands(); 132 if (error != B_OK) 133 return error; 134 135 fShowSemaphore = create_sem(0, "show CLI"); 136 if (fShowSemaphore < 0) 137 return fShowSemaphore; 138 139 team->AddListener(this); 140 141 return B_OK; 142 } 143 144 145 void 146 CommandLineUserInterface::Show() 147 { 148 fShown = true; 149 release_sem(fShowSemaphore); 150 } 151 152 153 void 154 CommandLineUserInterface::Terminate() 155 { 156 fTerminating = true; 157 158 if (fShown) { 159 fContext.Terminating(); 160 161 // Wait for input loop to finish. 162 while (acquire_sem(fShowSemaphore) == B_INTERRUPTED) { 163 } 164 } else { 165 // The main thread will still be blocked in Run(). Unblock it. 166 delete_sem(fShowSemaphore); 167 fShowSemaphore = -1; 168 } 169 170 fContext.Cleanup(); 171 } 172 173 174 bool 175 CommandLineUserInterface::IsInteractive() const 176 { 177 // if we were invoked solely for the purpose of saving a crash report, 178 // then we're not taking user input into account. 179 return !fSaveReport; 180 } 181 182 183 status_t 184 CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings) 185 { 186 return B_OK; 187 } 188 189 190 status_t 191 CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const 192 { 193 return B_OK; 194 } 195 196 197 void 198 CommandLineUserInterface::NotifyUser(const char* title, const char* message, 199 user_notification_type type) 200 { 201 } 202 203 204 int32 205 CommandLineUserInterface::SynchronouslyAskUser(const char* title, 206 const char* message, const char* choice1, const char* choice2, 207 const char* choice3) 208 { 209 return -1; 210 } 211 212 213 status_t 214 CommandLineUserInterface::SynchronouslyAskUserForFile(entry_ref* _ref) 215 { 216 return B_UNSUPPORTED; 217 } 218 219 220 void 221 CommandLineUserInterface::Run() 222 { 223 // Wait for the Show() semaphore to be released. 224 status_t error; 225 do { 226 error = acquire_sem(fShowSemaphore); 227 } while (error == B_INTERRUPTED); 228 229 if (error != B_OK) 230 return; 231 232 if (!fSaveReport) { 233 _InputLoop(); 234 // Release the Show() semaphore to signal Terminate(). 235 release_sem(fShowSemaphore); 236 } else { 237 ArgumentVector args; 238 char buffer[256]; 239 const char* parseErrorLocation; 240 if (_ReportTargetThreadStopNeeded()) { 241 snprintf(buffer, sizeof(buffer), "stop %" B_PRId32, 242 fReportTargetThread); 243 args.Parse(buffer, &parseErrorLocation); 244 _ExecuteCommand(args.ArgumentCount(), args.Arguments()); 245 } else 246 _SubmitSaveReport(); 247 } 248 } 249 250 251 void 252 CommandLineUserInterface::ThreadStateChanged(const Team::ThreadEvent& event) 253 { 254 if (fSaveReport) { 255 Thread* thread = event.GetThread(); 256 // If we were asked to attach/report on a specific thread 257 // rather than a team, and said thread was still 258 // running, when we attached, we need to wait for its corresponding 259 // stop state before generating a report, else we might not get its 260 // stack trace. 261 if (thread->ID() == fReportTargetThread 262 && thread->State() == THREAD_STATE_STOPPED) { 263 _SubmitSaveReport(); 264 } 265 } 266 } 267 268 269 void 270 CommandLineUserInterface::DebugReportChanged( 271 const Team::DebugReportEvent& event) 272 { 273 printf("Successfully saved debug report to %s\n", 274 event.GetReportPath()); 275 276 if (fSaveReport) { 277 fContext.QuitSession(true); 278 // Release the Show() semaphore to signal Terminate(). 279 release_sem(fShowSemaphore); 280 } 281 } 282 283 284 /*static*/ status_t 285 CommandLineUserInterface::_InputLoopEntry(void* data) 286 { 287 return ((CommandLineUserInterface*)data)->_InputLoop(); 288 } 289 290 291 status_t 292 CommandLineUserInterface::_InputLoop() 293 { 294 thread_id currentThread = -1; 295 296 while (!fTerminating) { 297 // Wait for a thread or Ctrl-C. 298 fContext.WaitForThreadOrUser(); 299 if (fContext.IsTerminating()) 300 break; 301 302 // Print the active thread, if it changed. 303 if (fContext.CurrentThreadID() != currentThread) { 304 fContext.PrintCurrentThread(); 305 currentThread = fContext.CurrentThreadID(); 306 } 307 308 // read a command line 309 const char* line = fContext.PromptUser(kDebuggerPrompt); 310 if (line == NULL) 311 break; 312 313 // parse the command line 314 ArgumentVector args; 315 const char* parseErrorLocation; 316 switch (args.Parse(line, &parseErrorLocation)) { 317 case ArgumentVector::NO_ERROR: 318 break; 319 case ArgumentVector::NO_MEMORY: 320 printf("Insufficient memory parsing the command line.\n"); 321 continue; 322 case ArgumentVector::UNTERMINATED_QUOTED_STRING: 323 printf("Parse error: Unterminated quoted string starting at " 324 "character %zu.\n", parseErrorLocation - line + 1); 325 continue; 326 case ArgumentVector::TRAILING_BACKSPACE: 327 printf("Parse error: trailing backspace.\n"); 328 continue; 329 } 330 331 if (args.ArgumentCount() == 0) 332 continue; 333 334 // add line to history 335 fContext.AddLineToInputHistory(line); 336 337 // execute command 338 _ExecuteCommand(args.ArgumentCount(), args.Arguments()); 339 } 340 341 return B_OK; 342 } 343 344 345 status_t 346 CommandLineUserInterface::_RegisterCommands() 347 { 348 if (_RegisterCommand("bt sc", new(std::nothrow) CliStackTraceCommand) 349 && _RegisterCommand("continue", new(std::nothrow) CliContinueCommand) 350 && _RegisterCommand("db ds dw dl string", new(std::nothrow) 351 CliDumpMemoryCommand) 352 && _RegisterCommand("frame", new(std::nothrow) CliStackFrameCommand) 353 && _RegisterCommand("help", new(std::nothrow) HelpCommand(this)) 354 && _RegisterCommand("print", new(std::nothrow) CliPrintVariableCommand) 355 && _RegisterCommand("quit", new(std::nothrow) CliQuitCommand) 356 && _RegisterCommand("save-report", 357 new(std::nothrow) CliDebugReportCommand) 358 && _RegisterCommand("stop", new(std::nothrow) CliStopCommand) 359 && _RegisterCommand("thread", new(std::nothrow) CliThreadCommand) 360 && _RegisterCommand("threads", new(std::nothrow) CliThreadsCommand) 361 && _RegisterCommand("variables", 362 new(std::nothrow) CliVariablesCommand)) { 363 fCommands.SortItems(&_CompareCommandEntries); 364 return B_OK; 365 } 366 367 return B_NO_MEMORY; 368 } 369 370 371 bool 372 CommandLineUserInterface::_RegisterCommand(const BString& name, 373 CliCommand* command) 374 { 375 BReference<CliCommand> commandReference(command, true); 376 if (name.IsEmpty() || command == NULL) 377 return false; 378 379 BString nextName; 380 int32 startIndex = 0; 381 int32 spaceIndex; 382 do { 383 spaceIndex = name.FindFirst(' ', startIndex); 384 if (spaceIndex == B_ERROR) 385 spaceIndex = name.Length(); 386 name.CopyInto(nextName, startIndex, spaceIndex - startIndex); 387 388 CommandEntry* entry = new(std::nothrow) CommandEntry(nextName, 389 command); 390 if (entry == NULL || !fCommands.AddItem(entry)) { 391 delete entry; 392 return false; 393 } 394 startIndex = spaceIndex + 1; 395 } while (startIndex < name.Length()); 396 397 return true; 398 } 399 400 401 void 402 CommandLineUserInterface::_ExecuteCommand(int argc, const char* const* argv) 403 { 404 CommandEntry* commandEntry = _FindCommand(argv[0]); 405 if (commandEntry != NULL) 406 commandEntry->Command()->Execute(argc, argv, fContext); 407 } 408 409 410 CommandLineUserInterface::CommandEntry* 411 CommandLineUserInterface::_FindCommand(const char* commandName) 412 { 413 size_t commandNameLength = strlen(commandName); 414 415 // try to find an exact match first 416 CommandEntry* commandEntry = NULL; 417 for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) { 418 if (entry->Name() == commandName) { 419 commandEntry = entry; 420 break; 421 } 422 } 423 424 // If nothing found yet, try partial matches, but only, if they are 425 // unambiguous. 426 if (commandEntry == NULL) { 427 for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) { 428 if (entry->Name().Compare(commandName, commandNameLength) == 0) { 429 if (commandEntry != NULL) { 430 printf("Error: Ambiguous command \"%s\".\n", commandName); 431 return NULL; 432 } 433 434 commandEntry = entry; 435 } 436 } 437 } 438 439 if (commandEntry == NULL) { 440 printf("Error: Unknown command \"%s\".\n", commandName); 441 return NULL; 442 } 443 444 return commandEntry; 445 } 446 447 448 void 449 CommandLineUserInterface::_PrintHelp(const char* commandName) 450 { 451 // If a command name is given, print the usage for that one. 452 if (commandName != NULL) { 453 CommandEntry* commandEntry = _FindCommand(commandName); 454 if (commandEntry != NULL) 455 commandEntry->Command()->PrintUsage(commandEntry->Name().String()); 456 return; 457 } 458 459 // No command name given -- print a list of all commands. 460 461 // determine longest command name 462 int32 longestCommandName = 0; 463 for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) { 464 longestCommandName 465 = std::max(longestCommandName, entry->Name().Length()); 466 } 467 468 // print the command list 469 for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) { 470 printf("%*s - %s\n", (int)longestCommandName, entry->Name().String(), 471 entry->Command()->Summary()); 472 } 473 } 474 475 476 /*static */ 477 int 478 CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1, 479 const CommandEntry* command2) 480 { 481 return ::Compare(command1->Name(), command2->Name()); 482 } 483 484 485 bool 486 CommandLineUserInterface::_ReportTargetThreadStopNeeded() const 487 { 488 if (fReportTargetThread < 0) 489 return false; 490 491 Team* team = fContext.GetTeam(); 492 AutoLocker<Team> teamLocker(team); 493 Thread* thread = team->ThreadByID(fReportTargetThread); 494 if (thread == NULL) 495 return false; 496 497 return thread->State() != THREAD_STATE_STOPPED; 498 } 499 500 501 void 502 CommandLineUserInterface::_SubmitSaveReport() 503 { 504 ArgumentVector args; 505 char buffer[256]; 506 const char* parseErrorLocation; 507 snprintf(buffer, sizeof(buffer), "save-report %s", 508 fReportPath != NULL ? fReportPath : ""); 509 args.Parse(buffer, &parseErrorLocation); 510 _ExecuteCommand(args.ArgumentCount(), args.Arguments()); 511 } 512