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 32static const char* kDebuggerPrompt = "debugger> "; 33 34 35// #pragma mark - CommandEntry 36 37 38struct 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 56private: 57 BString fName; 58 BReference<CliCommand> fCommand; 59}; 60 61 62// #pragma mark - HelpCommand 63 64 65struct 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 87private: 88 CommandLineUserInterface* fUserInterface; 89}; 90 91 92// #pragma mark - CommandLineUserInterface 93 94 95CommandLineUserInterface::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 108CommandLineUserInterface::~CommandLineUserInterface() 109{ 110 if (fShowSemaphore >= 0) 111 delete_sem(fShowSemaphore); 112} 113 114 115const char* 116CommandLineUserInterface::ID() const 117{ 118 return "BasicCommandLineUserInterface"; 119} 120 121 122status_t 123CommandLineUserInterface::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 143void 144CommandLineUserInterface::Show() 145{ 146 fShown = true; 147 release_sem(fShowSemaphore); 148} 149 150 151void 152CommandLineUserInterface::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 172status_t 173CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings) 174{ 175 return B_OK; 176} 177 178 179status_t 180CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const 181{ 182 return B_OK; 183} 184 185 186void 187CommandLineUserInterface::NotifyUser(const char* title, const char* message, 188 user_notification_type type) 189{ 190} 191 192 193int32 194CommandLineUserInterface::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 202void 203CommandLineUserInterface::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 230void 231CommandLineUserInterface::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 246CommandLineUserInterface::_InputLoopEntry(void* data) 247{ 248 return ((CommandLineUserInterface*)data)->_InputLoop(); 249} 250 251 252status_t 253CommandLineUserInterface::_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 306status_t 307CommandLineUserInterface::_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 332bool 333CommandLineUserInterface::_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 362void 363CommandLineUserInterface::_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 371CommandLineUserInterface::CommandEntry* 372CommandLineUserInterface::_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 409void 410CommandLineUserInterface::_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 */ 438int 439CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1, 440 const CommandEntry* command2) 441{ 442 return ::Compare(command1->Name(), command2->Name()); 443} 444 445 446