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