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