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