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 fContext.SetInteractive(!fSaveReport); 132 133 error = _RegisterCommands(); 134 if (error != B_OK) 135 return error; 136 137 fShowSemaphore = create_sem(0, "show CLI"); 138 if (fShowSemaphore < 0) 139 return fShowSemaphore; 140 141 team->AddListener(this); 142 143 return B_OK; 144 } 145 146 147 void 148 CommandLineUserInterface::Show() 149 { 150 fShown = true; 151 release_sem(fShowSemaphore); 152 } 153 154 155 void 156 CommandLineUserInterface::Terminate() 157 { 158 fTerminating = true; 159 160 if (fShown) { 161 fContext.Terminating(); 162 163 // Wait for input loop to finish. 164 while (acquire_sem(fShowSemaphore) == B_INTERRUPTED) { 165 } 166 } else { 167 // The main thread will still be blocked in Run(). Unblock it. 168 delete_sem(fShowSemaphore); 169 fShowSemaphore = -1; 170 } 171 172 fContext.Cleanup(); 173 } 174 175 176 bool 177 CommandLineUserInterface::IsInteractive() const 178 { 179 // if we were invoked solely for the purpose of saving a crash report, 180 // then we're not taking user input into account. 181 return !fSaveReport; 182 } 183 184 185 status_t 186 CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings) 187 { 188 return B_OK; 189 } 190 191 192 status_t 193 CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const 194 { 195 return B_OK; 196 } 197 198 199 void 200 CommandLineUserInterface::NotifyUser(const char* title, const char* message, 201 user_notification_type type) 202 { 203 } 204 205 206 int32 207 CommandLineUserInterface::SynchronouslyAskUser(const char* title, 208 const char* message, const char* choice1, const char* choice2, 209 const char* choice3) 210 { 211 return -1; 212 } 213 214 215 status_t 216 CommandLineUserInterface::SynchronouslyAskUserForFile(entry_ref* _ref) 217 { 218 return B_UNSUPPORTED; 219 } 220 221 222 void 223 CommandLineUserInterface::Run() 224 { 225 // Wait for the Show() semaphore to be released. 226 status_t error; 227 do { 228 error = acquire_sem(fShowSemaphore); 229 } while (error == B_INTERRUPTED); 230 231 if (error != B_OK) 232 return; 233 234 if (fSaveReport) { 235 ArgumentVector args; 236 char buffer[256]; 237 const char* parseErrorLocation; 238 if (_ReportTargetThreadStopNeeded()) { 239 snprintf(buffer, sizeof(buffer), "stop %" B_PRId32, 240 fReportTargetThread); 241 args.Parse(buffer, &parseErrorLocation); 242 _ExecuteCommand(args.ArgumentCount(), args.Arguments()); 243 } else 244 _SubmitSaveReport(); 245 } 246 247 _InputLoop(); 248 // Release the Show() semaphore to signal Terminate(). 249 release_sem(fShowSemaphore); 250 } 251 252 253 void 254 CommandLineUserInterface::ThreadStateChanged(const Team::ThreadEvent& event) 255 { 256 if (fSaveReport) { 257 Thread* thread = event.GetThread(); 258 // If we were asked to attach/report on a specific thread 259 // rather than a team, and said thread was still 260 // running, when we attached, we need to wait for its corresponding 261 // stop state before generating a report, else we might not get its 262 // stack trace. 263 if (thread->ID() == fReportTargetThread 264 && thread->State() == THREAD_STATE_STOPPED) { 265 _SubmitSaveReport(); 266 } 267 } 268 } 269 270 271 /*static*/ status_t 272 CommandLineUserInterface::_InputLoopEntry(void* data) 273 { 274 return ((CommandLineUserInterface*)data)->_InputLoop(); 275 } 276 277 278 status_t 279 CommandLineUserInterface::_InputLoop() 280 { 281 thread_id currentThread = -1; 282 283 while (!fTerminating) { 284 // Wait for a thread or Ctrl-C. 285 fContext.WaitForThreadOrUser(); 286 if (fContext.IsTerminating()) 287 break; 288 289 // Print the active thread, if it changed. 290 if (fContext.CurrentThreadID() != currentThread) { 291 fContext.PrintCurrentThread(); 292 currentThread = fContext.CurrentThreadID(); 293 } 294 295 // read a command line 296 const char* line = fContext.PromptUser(kDebuggerPrompt); 297 if (line == NULL) 298 break; 299 300 // parse the command line 301 ArgumentVector args; 302 const char* parseErrorLocation; 303 switch (args.Parse(line, &parseErrorLocation)) { 304 case ArgumentVector::NO_ERROR: 305 break; 306 case ArgumentVector::NO_MEMORY: 307 printf("Insufficient memory parsing the command line.\n"); 308 continue; 309 case ArgumentVector::UNTERMINATED_QUOTED_STRING: 310 printf("Parse error: Unterminated quoted string starting at " 311 "character %zu.\n", parseErrorLocation - line + 1); 312 continue; 313 case ArgumentVector::TRAILING_BACKSPACE: 314 printf("Parse error: trailing backspace.\n"); 315 continue; 316 } 317 318 if (args.ArgumentCount() == 0) 319 continue; 320 321 // add line to history 322 fContext.AddLineToInputHistory(line); 323 324 // execute command 325 _ExecuteCommand(args.ArgumentCount(), args.Arguments()); 326 } 327 328 return B_OK; 329 } 330 331 332 status_t 333 CommandLineUserInterface::_RegisterCommands() 334 { 335 if (_RegisterCommand("bt sc", new(std::nothrow) CliStackTraceCommand) 336 && _RegisterCommand("continue", new(std::nothrow) CliContinueCommand) 337 && _RegisterCommand("db ds dw dl string", new(std::nothrow) 338 CliDumpMemoryCommand) 339 && _RegisterCommand("frame", new(std::nothrow) CliStackFrameCommand) 340 && _RegisterCommand("help", new(std::nothrow) HelpCommand(this)) 341 && _RegisterCommand("print", new(std::nothrow) CliPrintVariableCommand) 342 && _RegisterCommand("quit", new(std::nothrow) CliQuitCommand) 343 && _RegisterCommand("save-report", 344 new(std::nothrow) CliDebugReportCommand) 345 && _RegisterCommand("stop", new(std::nothrow) CliStopCommand) 346 && _RegisterCommand("thread", new(std::nothrow) CliThreadCommand) 347 && _RegisterCommand("threads", new(std::nothrow) CliThreadsCommand) 348 && _RegisterCommand("variables", 349 new(std::nothrow) CliVariablesCommand)) { 350 fCommands.SortItems(&_CompareCommandEntries); 351 return B_OK; 352 } 353 354 return B_NO_MEMORY; 355 } 356 357 358 bool 359 CommandLineUserInterface::_RegisterCommand(const BString& name, 360 CliCommand* command) 361 { 362 BReference<CliCommand> commandReference(command, true); 363 if (name.IsEmpty() || command == NULL) 364 return false; 365 366 BString nextName; 367 int32 startIndex = 0; 368 int32 spaceIndex; 369 do { 370 spaceIndex = name.FindFirst(' ', startIndex); 371 if (spaceIndex == B_ERROR) 372 spaceIndex = name.Length(); 373 name.CopyInto(nextName, startIndex, spaceIndex - startIndex); 374 375 CommandEntry* entry = new(std::nothrow) CommandEntry(nextName, 376 command); 377 if (entry == NULL || !fCommands.AddItem(entry)) { 378 delete entry; 379 return false; 380 } 381 startIndex = spaceIndex + 1; 382 } while (startIndex < name.Length()); 383 384 return true; 385 } 386 387 388 void 389 CommandLineUserInterface::_ExecuteCommand(int argc, const char* const* argv) 390 { 391 CommandEntry* commandEntry = _FindCommand(argv[0]); 392 if (commandEntry != NULL) 393 commandEntry->Command()->Execute(argc, argv, fContext); 394 } 395 396 397 CommandLineUserInterface::CommandEntry* 398 CommandLineUserInterface::_FindCommand(const char* commandName) 399 { 400 size_t commandNameLength = strlen(commandName); 401 402 // try to find an exact match first 403 CommandEntry* commandEntry = NULL; 404 for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) { 405 if (entry->Name() == commandName) { 406 commandEntry = entry; 407 break; 408 } 409 } 410 411 // If nothing found yet, try partial matches, but only, if they are 412 // unambiguous. 413 if (commandEntry == NULL) { 414 for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) { 415 if (entry->Name().Compare(commandName, commandNameLength) == 0) { 416 if (commandEntry != NULL) { 417 printf("Error: Ambiguous command \"%s\".\n", commandName); 418 return NULL; 419 } 420 421 commandEntry = entry; 422 } 423 } 424 } 425 426 if (commandEntry == NULL) { 427 printf("Error: Unknown command \"%s\".\n", commandName); 428 return NULL; 429 } 430 431 return commandEntry; 432 } 433 434 435 void 436 CommandLineUserInterface::_PrintHelp(const char* commandName) 437 { 438 // If a command name is given, print the usage for that one. 439 if (commandName != NULL) { 440 CommandEntry* commandEntry = _FindCommand(commandName); 441 if (commandEntry != NULL) 442 commandEntry->Command()->PrintUsage(commandEntry->Name().String()); 443 return; 444 } 445 446 // No command name given -- print a list of all commands. 447 448 // determine longest command name 449 int32 longestCommandName = 0; 450 for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) { 451 longestCommandName 452 = std::max(longestCommandName, entry->Name().Length()); 453 } 454 455 // print the command list 456 for (int32 i = 0; CommandEntry* entry = fCommands.ItemAt(i); i++) { 457 printf("%*s - %s\n", (int)longestCommandName, entry->Name().String(), 458 entry->Command()->Summary()); 459 } 460 } 461 462 463 /*static */ 464 int 465 CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1, 466 const CommandEntry* command2) 467 { 468 return ::Compare(command1->Name(), command2->Name()); 469 } 470 471 472 bool 473 CommandLineUserInterface::_ReportTargetThreadStopNeeded() const 474 { 475 if (fReportTargetThread < 0) 476 return false; 477 478 Team* team = fContext.GetTeam(); 479 AutoLocker<Team> teamLocker(team); 480 Thread* thread = team->ThreadByID(fReportTargetThread); 481 if (thread == NULL) 482 return false; 483 484 return thread->State() != THREAD_STATE_STOPPED; 485 } 486 487 488 void 489 CommandLineUserInterface::_SubmitSaveReport() 490 { 491 ArgumentVector args; 492 char buffer[256]; 493 const char* parseErrorLocation; 494 snprintf(buffer, sizeof(buffer), "save-report %s", 495 fReportPath != NULL ? fReportPath : ""); 496 args.Parse(buffer, &parseErrorLocation); 497 _ExecuteCommand(args.ArgumentCount(), args.Arguments()); 498 } 499