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