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 35static const char* kDebuggerPrompt = "debugger> "; 36 37 38// #pragma mark - CommandEntry 39 40 41struct 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 59private: 60 BString fName; 61 BReference<CliCommand> fCommand; 62}; 63 64 65// #pragma mark - HelpCommand 66 67 68struct 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 90private: 91 CommandLineUserInterface* fUserInterface; 92}; 93 94 95// #pragma mark - CommandLineUserInterface 96 97 98CommandLineUserInterface::CommandLineUserInterface() 99 : 100 fContext(new CliContext()), 101 fCommands(20, true), 102 fShowSemaphore(-1), 103 fShown(false), 104 fTerminating(false) 105{ 106} 107 108 109CommandLineUserInterface::~CommandLineUserInterface() 110{ 111 if (fShowSemaphore >= 0) 112 delete_sem(fShowSemaphore); 113} 114 115 116const char* 117CommandLineUserInterface::ID() const 118{ 119 return "BasicCommandLineUserInterface"; 120} 121 122 123status_t 124CommandLineUserInterface::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 142void 143CommandLineUserInterface::Show() 144{ 145 fShown = true; 146 release_sem(fShowSemaphore); 147} 148 149 150void 151CommandLineUserInterface::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 174UserInterface* 175CommandLineUserInterface::Clone() const 176{ 177 return new(std::nothrow) CommandLineUserInterface; 178} 179 180 181bool 182CommandLineUserInterface::IsInteractive() const 183{ 184 return true; 185} 186 187 188status_t 189CommandLineUserInterface::LoadSettings(const TeamUiSettings* settings) 190{ 191 return B_OK; 192} 193 194 195status_t 196CommandLineUserInterface::SaveSettings(TeamUiSettings*& settings) const 197{ 198 return B_OK; 199} 200 201 202void 203CommandLineUserInterface::NotifyUser(const char* title, const char* message, 204 user_notification_type type) 205{ 206} 207 208 209void 210CommandLineUserInterface::NotifyBackgroundWorkStatus(const char* message) 211{ 212} 213 214 215int32 216CommandLineUserInterface::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 224status_t 225CommandLineUserInterface::SynchronouslyAskUserForFile(entry_ref* _ref) 226{ 227 return B_UNSUPPORTED; 228} 229 230 231void 232CommandLineUserInterface::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 250status_t 251CommandLineUserInterface::_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 304status_t 305CommandLineUserInterface::_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 340bool 341CommandLineUserInterface::_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 370void 371CommandLineUserInterface::_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 379CommandLineUserInterface::CommandEntry* 380CommandLineUserInterface::_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 417void 418CommandLineUserInterface::_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 */ 446int 447CommandLineUserInterface::_CompareCommandEntries(const CommandEntry* command1, 448 const CommandEntry* command2) 449{ 450 return ::Compare(command1->Name(), command2->Name()); 451} 452