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