1 /* 2 * Copyright 2009-2016, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Copyright 2011-2016, Rene Gollent, rene@gollent.com. 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include <getopt.h> 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 13 #include <new> 14 15 #include <Application.h> 16 #include <Entry.h> 17 #include <Message.h> 18 #include <ObjectList.h> 19 #include <Path.h> 20 21 #include <ArgumentVector.h> 22 #include <AutoDeleter.h> 23 #include <AutoLocker.h> 24 25 #include "CommandLineUserInterface.h" 26 #include "DebuggerGlobals.h" 27 #include "DebuggerSettingsManager.h" 28 #include "DebuggerUiSettingsFactory.h" 29 #include "ElfFile.h" 30 #include "GraphicalUserInterface.h" 31 #include "MessageCodes.h" 32 #include "ReportUserInterface.h" 33 #include "SignalSet.h" 34 #include "StartTeamWindow.h" 35 #include "TargetHostInterface.h" 36 #include "TargetHostInterfaceRoster.h" 37 #include "TeamDebugger.h" 38 #include "TeamsWindow.h" 39 #include "ValueHandlerRoster.h" 40 41 42 extern const char* __progname; 43 const char* kProgramName = __progname; 44 45 static const char* const kDebuggerSignature 46 = "application/x-vnd.Haiku-Debugger"; 47 48 49 static const char* const kUsage = 50 "Usage: %s [ <options> ]\n" 51 " %s [ <options> ] <command line>\n" 52 " %s [ <options> ] --team <team>\n" 53 " %s [ <options> ] --thread <thread>\n" 54 " %s [ <options> ] --core <file>\n" 55 "\n" 56 "The first form starts the debugger displaying a requester to choose a\n" 57 "running team to debug respectively to specify the program to run and\n" 58 "debug.\n" 59 "\n" 60 "The second form runs the given command line and attaches the debugger to\n" 61 "the new team. Unless specified otherwise the program will be stopped at\n" 62 "the beginning of its main() function.\n" 63 "\n" 64 "The third and fourth forms attach the debugger to a running team. The\n" 65 "fourth form additionally stops the specified thread.\n" 66 "\n" 67 "The fifth form loads a core file.\n" 68 "\n" 69 "Options:\n" 70 " -h, --help - Print this usage info and exit.\n" 71 " -c, --cli - Use command line user interface\n" 72 " -s, --save-report - Save crash report for the targetted team and exit.\n" 73 " Implies --cli.\n" 74 ; 75 76 77 static void 78 print_usage_and_exit(bool error) 79 { 80 fprintf(error ? stderr : stdout, kUsage, kProgramName, kProgramName, 81 kProgramName, kProgramName, kProgramName); 82 exit(error ? 1 : 0); 83 } 84 85 86 struct Options { 87 int commandLineArgc; 88 const char* const* commandLineArgv; 89 team_id team; 90 thread_id thread; 91 bool useCLI; 92 bool saveReport; 93 const char* reportPath; 94 const char* coreFilePath; 95 96 Options() 97 : 98 commandLineArgc(0), 99 commandLineArgv(NULL), 100 team(-1), 101 thread(-1), 102 useCLI(false), 103 saveReport(false), 104 reportPath(NULL), 105 coreFilePath(NULL) 106 { 107 } 108 }; 109 110 111 static void 112 set_debugger_options_from_options(TeamDebuggerOptions& _debuggerOptions, 113 const Options& options) 114 { 115 _debuggerOptions.commandLineArgc = options.commandLineArgc; 116 _debuggerOptions.commandLineArgv = options.commandLineArgv; 117 _debuggerOptions.team = options.team; 118 _debuggerOptions.thread = options.thread; 119 _debuggerOptions.coreFilePath = options.coreFilePath; 120 121 if (options.coreFilePath != NULL) 122 _debuggerOptions.requestType = TEAM_DEBUGGER_REQUEST_LOAD_CORE; 123 else if (options.commandLineArgc != 0) 124 _debuggerOptions.requestType = TEAM_DEBUGGER_REQUEST_CREATE; 125 else 126 _debuggerOptions.requestType = TEAM_DEBUGGER_REQUEST_ATTACH; 127 } 128 129 130 131 static bool 132 parse_arguments(int argc, const char* const* argv, bool noOutput, 133 Options& options) 134 { 135 optind = 1; 136 137 while (true) { 138 static struct option sLongOptions[] = { 139 { "help", no_argument, 0, 'h' }, 140 { "cli", no_argument, 0, 'c' }, 141 { "save-report", optional_argument, 0, 's' }, 142 { "team", required_argument, 0, 't' }, 143 { "thread", required_argument, 0, 'T' }, 144 { "core", required_argument, 0, 'C' }, 145 { 0, 0, 0, 0 } 146 }; 147 148 opterr = 0; // don't print errors 149 150 int c = getopt_long(argc, (char**)argv, "+chs", sLongOptions, NULL); 151 if (c == -1) 152 break; 153 154 switch (c) { 155 case 'c': 156 options.useCLI = true; 157 break; 158 159 case 'C': 160 options.coreFilePath = optarg; 161 break; 162 163 case 'h': 164 if (noOutput) 165 return false; 166 print_usage_and_exit(false); 167 break; 168 169 case 's': 170 { 171 options.saveReport = true; 172 options.reportPath = optarg; 173 break; 174 } 175 176 case 't': 177 { 178 options.team = strtol(optarg, NULL, 0); 179 if (options.team <= 0) { 180 if (noOutput) 181 return false; 182 print_usage_and_exit(true); 183 } 184 break; 185 } 186 187 case 'T': 188 { 189 options.thread = strtol(optarg, NULL, 0); 190 if (options.thread <= 0) { 191 if (noOutput) 192 return false; 193 print_usage_and_exit(true); 194 } 195 break; 196 } 197 198 default: 199 if (noOutput) 200 return false; 201 print_usage_and_exit(true); 202 break; 203 } 204 } 205 206 if (optind < argc) { 207 options.commandLineArgc = argc - optind; 208 options.commandLineArgv = argv + optind; 209 } 210 211 int exclusiveParams = 0; 212 if (options.team > 0) 213 exclusiveParams++; 214 if (options.thread > 0) 215 exclusiveParams++; 216 if (options.commandLineArgc > 0) 217 exclusiveParams++; 218 219 if (exclusiveParams == 0) { 220 return true; 221 } else if (exclusiveParams != 1) { 222 if (noOutput) 223 return false; 224 print_usage_and_exit(true); 225 } 226 227 return true; 228 } 229 230 231 // #pragma mark - Debugger application class 232 233 234 class Debugger : public BApplication, 235 private TargetHostInterfaceRoster::Listener { 236 public: 237 Debugger(); 238 ~Debugger(); 239 240 status_t Init(); 241 virtual void MessageReceived(BMessage* message); 242 virtual void ReadyToRun(); 243 virtual void ArgvReceived(int32 argc, char** argv); 244 virtual void RefsReceived(BMessage* message); 245 246 private: 247 virtual bool QuitRequested(); 248 virtual void Quit(); 249 250 // TargetHostInterfaceRoster::Listener 251 virtual void TeamDebuggerCountChanged(int32 count); 252 253 private: 254 status_t _StartNewTeam(TargetHostInterface* interface, 255 const char* teamPath, const char* args); 256 status_t _HandleOptions(const Options& options); 257 258 private: 259 DebuggerSettingsManager fSettingsManager; 260 TeamsWindow* fTeamsWindow; 261 StartTeamWindow* fStartTeamWindow; 262 }; 263 264 265 // #pragma mark - CliDebugger 266 267 268 class CliDebugger : private TargetHostInterfaceRoster::Listener { 269 public: 270 CliDebugger(); 271 ~CliDebugger(); 272 273 bool Run(const Options& options); 274 }; 275 276 277 class ReportDebugger : private TargetHostInterfaceRoster::Listener { 278 public: 279 ReportDebugger(); 280 ~ReportDebugger(); 281 bool Run(const Options& options); 282 }; 283 284 285 // #pragma mark - Debugger application class 286 287 288 Debugger::Debugger() 289 : 290 BApplication(kDebuggerSignature), 291 TargetHostInterfaceRoster::Listener(), 292 fTeamsWindow(NULL), 293 fStartTeamWindow(NULL) 294 { 295 } 296 297 298 Debugger::~Debugger() 299 { 300 DebuggerUiSettingsFactory::DeleteDefault(); 301 ValueHandlerRoster::DeleteDefault(); 302 303 debugger_global_uninit(); 304 } 305 306 307 status_t 308 Debugger::Init() 309 { 310 status_t error = debugger_global_init(this); 311 if (error != B_OK) 312 return error; 313 314 error = DebuggerUiSettingsFactory::CreateDefault(); 315 if (error != B_OK) 316 return error; 317 318 error = ValueHandlerRoster::CreateDefault(); 319 if (error != B_OK) 320 return error; 321 322 return fSettingsManager.Init(DebuggerUiSettingsFactory::Default()); 323 } 324 325 326 void 327 Debugger::MessageReceived(BMessage* message) 328 { 329 switch (message->what) { 330 case MSG_SHOW_TEAMS_WINDOW: 331 { 332 if (fTeamsWindow) { 333 fTeamsWindow->Activate(true); 334 break; 335 } 336 337 try { 338 fTeamsWindow = TeamsWindow::Create(&fSettingsManager); 339 if (fTeamsWindow != NULL) 340 fTeamsWindow->Show(); 341 } catch (...) { 342 // TODO: Notify the user! 343 fprintf(stderr, "Error: Failed to create Teams window\n"); 344 } 345 break; 346 } 347 case MSG_TEAMS_WINDOW_CLOSED: 348 { 349 fTeamsWindow = NULL; 350 Quit(); 351 break; 352 } 353 case MSG_SHOW_START_TEAM_WINDOW: 354 { 355 TargetHostInterface* hostInterface; 356 if (message->FindPointer("interface", 357 reinterpret_cast<void**>(&hostInterface)) != B_OK) { 358 // if an interface isn't explicitly supplied, fall back to 359 // the default local interface. 360 hostInterface = TargetHostInterfaceRoster::Default() 361 ->ActiveInterfaceAt(0); 362 } 363 364 BMessenger messenger(fStartTeamWindow); 365 if (!messenger.IsValid()) { 366 fStartTeamWindow = StartTeamWindow::Create(hostInterface); 367 if (fStartTeamWindow == NULL) 368 break; 369 fStartTeamWindow->Show(); 370 } else 371 fStartTeamWindow->Activate(); 372 break; 373 } 374 case MSG_START_TEAM_WINDOW_CLOSED: 375 { 376 fStartTeamWindow = NULL; 377 break; 378 } 379 case MSG_DEBUG_THIS_TEAM: 380 { 381 team_id teamID; 382 if (message->FindInt32("team", &teamID) != B_OK) 383 break; 384 385 TargetHostInterface* interface; 386 if (message->FindPointer("interface", reinterpret_cast<void**>( 387 &interface)) != B_OK) { 388 break; 389 } 390 391 TeamDebuggerOptions options; 392 options.requestType = TEAM_DEBUGGER_REQUEST_ATTACH; 393 options.settingsManager = &fSettingsManager; 394 options.team = teamID; 395 options.userInterface = new(std::nothrow) GraphicalUserInterface; 396 if (options.userInterface == NULL) { 397 // TODO: notify user. 398 break; 399 } 400 BReference<UserInterface> uiReference(options.userInterface, true); 401 status_t error = interface->StartTeamDebugger(options); 402 if (error != B_OK) { 403 // TODO: notify user. 404 } else 405 uiReference.Detach(); 406 break; 407 } 408 case MSG_START_NEW_TEAM: 409 { 410 TargetHostInterface* interface; 411 if (message->FindPointer("interface", reinterpret_cast<void**>( 412 &interface)) != B_OK) { 413 break; 414 } 415 416 const char* teamPath = NULL; 417 const char* args = NULL; 418 419 message->FindString("path", &teamPath); 420 message->FindString("arguments", &args); 421 422 status_t result = _StartNewTeam(interface, teamPath, args); 423 BMessage reply; 424 reply.AddInt32("status", result); 425 message->SendReply(&reply); 426 break; 427 } 428 case MSG_LOAD_CORE_TEAM: 429 { 430 TargetHostInterface* interface; 431 if (message->FindPointer("interface", reinterpret_cast<void**>( 432 &interface)) != B_OK) { 433 break; 434 } 435 436 entry_ref ref; 437 if (message->FindRef("core", &ref) != B_OK) 438 break; 439 440 BPath path(&ref); 441 if (path.InitCheck() != B_OK) 442 break; 443 444 Options options; 445 options.coreFilePath = path.Path(); 446 _HandleOptions(options); 447 break; 448 } 449 default: 450 BApplication::MessageReceived(message); 451 break; 452 } 453 } 454 455 456 void 457 Debugger::ReadyToRun() 458 { 459 TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default(); 460 AutoLocker<TargetHostInterfaceRoster> lock(roster); 461 if (roster->CountRunningTeamDebuggers() == 0) 462 PostMessage(MSG_SHOW_TEAMS_WINDOW); 463 } 464 465 466 void 467 Debugger::ArgvReceived(int32 argc, char** argv) 468 { 469 Options options; 470 if (!parse_arguments(argc, argv, true, options)) { 471 printf("Debugger::ArgvReceived(): parsing args failed!\n"); 472 return; 473 } 474 475 _HandleOptions(options); 476 } 477 478 479 void 480 Debugger::RefsReceived(BMessage* message) 481 { 482 // iterate through the refs and handle the files we can handle 483 entry_ref ref; 484 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { 485 BPath path; 486 if (path.SetTo(&ref) != B_OK) 487 continue; 488 489 // check, whether this is a core file 490 { 491 ElfFile elfFile; 492 if (elfFile.Init(path.Path()) != B_OK || elfFile.Type() != ET_CORE) 493 continue; 494 } 495 496 // handle the core file 497 Options options; 498 options.coreFilePath = path.Path(); 499 _HandleOptions(options); 500 } 501 } 502 503 504 bool 505 Debugger::QuitRequested() 506 { 507 // NOTE: The default implementation will just ask all windows' 508 // QuitRequested() hooks. This in turn will ask the TeamWindows. 509 // For now, this is what we want. If we have more windows later, 510 // like the global TeamsWindow, then we want to just ask the 511 // TeamDebuggers, the TeamsWindow should of course not go away already 512 // if one or more TeamDebuggers want to stay later. There are multiple 513 // ways how to do this. For example, TeamDebugger could get a 514 // QuitRequested() hook or the TeamsWindow and other global windows 515 // could always return false in their QuitRequested(). 516 return BApplication::QuitRequested(); 517 // TODO: This is ugly. The team debuggers own the windows, not the 518 // other way around. 519 } 520 521 void 522 Debugger::Quit() 523 { 524 TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default(); 525 AutoLocker<TargetHostInterfaceRoster> lock(roster); 526 // don't quit before all team debuggers have been quit 527 if (roster->CountRunningTeamDebuggers() == 0 && fTeamsWindow == NULL) 528 BApplication::Quit(); 529 } 530 531 532 void 533 Debugger::TeamDebuggerCountChanged(int32 count) 534 { 535 if (count == 0) { 536 AutoLocker<Debugger> lock(this); 537 Quit(); 538 } 539 } 540 541 542 status_t 543 Debugger::_StartNewTeam(TargetHostInterface* interface, const char* path, 544 const char* args) 545 { 546 if (path == NULL) 547 return B_BAD_VALUE; 548 549 BString data; 550 data.SetToFormat("\"%s\" %s", path, args); 551 if (data.Length() == 0) 552 return B_NO_MEMORY; 553 554 ArgumentVector argVector; 555 argVector.Parse(data.String()); 556 557 TeamDebuggerOptions options; 558 options.requestType = TEAM_DEBUGGER_REQUEST_CREATE; 559 options.settingsManager = &fSettingsManager; 560 options.userInterface = new(std::nothrow) GraphicalUserInterface; 561 if (options.userInterface == NULL) 562 return B_NO_MEMORY; 563 BReference<UserInterface> uiReference(options.userInterface, true); 564 options.commandLineArgc = argVector.ArgumentCount(); 565 if (options.commandLineArgc <= 0) 566 return B_BAD_VALUE; 567 568 char** argv = argVector.DetachArguments(); 569 570 options.commandLineArgv = argv; 571 MemoryDeleter deleter(argv); 572 573 status_t error = interface->StartTeamDebugger(options); 574 if (error == B_OK) { 575 deleter.Detach(); 576 uiReference.Detach(); 577 } 578 579 return error; 580 } 581 582 583 status_t 584 Debugger::_HandleOptions(const Options& options) 585 { 586 TeamDebuggerOptions debuggerOptions; 587 set_debugger_options_from_options(debuggerOptions, options); 588 debuggerOptions.settingsManager = &fSettingsManager; 589 debuggerOptions.userInterface = new(std::nothrow) GraphicalUserInterface; 590 if (debuggerOptions.userInterface == NULL) 591 return B_NO_MEMORY; 592 BReference<UserInterface> uiReference(debuggerOptions.userInterface, true); 593 TargetHostInterface* hostInterface 594 = TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0); 595 status_t error = hostInterface->StartTeamDebugger(debuggerOptions); 596 if (error == B_OK) 597 uiReference.Detach(); 598 599 return error; 600 } 601 602 603 // #pragma mark - CliDebugger 604 605 606 CliDebugger::CliDebugger() 607 { 608 } 609 610 611 CliDebugger::~CliDebugger() 612 { 613 DebuggerUiSettingsFactory::DeleteDefault(); 614 debugger_global_uninit(); 615 } 616 617 618 bool 619 CliDebugger::Run(const Options& options) 620 { 621 // Block SIGINT, in this thread so all threads created by it inherit the 622 // a block mask with the signal blocked. In the input loop the signal will 623 // be unblocked again. 624 SignalSet(SIGINT).BlockInCurrentThread(); 625 626 // initialize global objects and settings manager 627 status_t error = debugger_global_init(this); 628 if (error != B_OK) { 629 fprintf(stderr, "Error: Global initialization failed: %s\n", 630 strerror(error)); 631 return false; 632 } 633 634 error = DebuggerUiSettingsFactory::CreateDefault(); 635 if (error != B_OK) { 636 fprintf(stderr, "Error: Failed to create default settings factory: " 637 "%s\n", strerror(error)); 638 return false; 639 } 640 641 642 DebuggerSettingsManager settingsManager; 643 error = settingsManager.Init(DebuggerUiSettingsFactory::Default()); 644 if (error != B_OK) { 645 fprintf(stderr, "Error: Settings manager initialization failed: " 646 "%s\n", strerror(error)); 647 return false; 648 } 649 650 // create the command line UI 651 CommandLineUserInterface* userInterface 652 = new(std::nothrow) CommandLineUserInterface(); 653 if (userInterface == NULL) { 654 fprintf(stderr, "Error: Out of memory!\n"); 655 return false; 656 } 657 BReference<UserInterface> userInterfaceReference(userInterface, true); 658 659 // TODO: once we support specifying a remote interface via command line 660 // args, this needs to be adjusted. For now, always assume actions 661 // are being taken via the local interface. 662 TargetHostInterface* hostInterface 663 = TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0); 664 665 TeamDebuggerOptions debuggerOptions; 666 set_debugger_options_from_options(debuggerOptions, options); 667 debuggerOptions.userInterface = userInterface; 668 debuggerOptions.settingsManager = &settingsManager; 669 error = hostInterface->StartTeamDebugger(debuggerOptions); 670 if (error != B_OK) 671 return false; 672 673 TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0); 674 thread_id teamDebuggerThread = teamDebugger->Thread(); 675 676 // run the input loop 677 userInterface->Run(); 678 679 // wait for the team debugger thread to terminate 680 wait_for_thread(teamDebuggerThread, NULL); 681 682 return true; 683 } 684 685 686 // #pragma mark - ReportDebugger 687 688 689 ReportDebugger::ReportDebugger() 690 { 691 } 692 693 694 ReportDebugger::~ReportDebugger() 695 { 696 debugger_global_uninit(); 697 } 698 699 700 bool 701 ReportDebugger::Run(const Options& options) 702 { 703 // initialize global objects and settings manager 704 status_t error = debugger_global_init(this); 705 if (error != B_OK) { 706 fprintf(stderr, "Error: Global initialization failed: %s\n", 707 strerror(error)); 708 return false; 709 } 710 711 // create the report UI 712 ReportUserInterface* userInterface 713 = new(std::nothrow) ReportUserInterface(options.thread, options.reportPath); 714 if (userInterface == NULL) { 715 fprintf(stderr, "Error: Out of memory!\n"); 716 return false; 717 } 718 BReference<UserInterface> userInterfaceReference(userInterface, true); 719 720 TargetHostInterface* hostInterface 721 = TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0); 722 723 TeamDebuggerOptions debuggerOptions; 724 set_debugger_options_from_options(debuggerOptions, options); 725 debuggerOptions.userInterface = userInterface; 726 error = hostInterface->StartTeamDebugger(debuggerOptions); 727 if (error != B_OK) 728 return false; 729 730 TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0); 731 thread_id teamDebuggerThread = teamDebugger->Thread(); 732 733 // run the input loop 734 userInterface->Run(); 735 736 // wait for the team debugger thread to terminate 737 wait_for_thread(teamDebuggerThread, NULL); 738 739 return true; 740 } 741 742 743 // #pragma mark - 744 745 746 int 747 main(int argc, const char* const* argv) 748 { 749 // We test-parse the arguments here, so that, when we're started from the 750 // terminal and there's an instance already running, we can print an error 751 // message to the terminal, if something's wrong with the arguments. 752 // Otherwise, the arguments are reparsed in the actual application, 753 // unless the option to use the command line interface was chosen. 754 755 Options options; 756 parse_arguments(argc, argv, false, options); 757 758 if (options.useCLI) { 759 CliDebugger debugger; 760 return debugger.Run(options) ? 0 : 1; 761 } else if (options.saveReport) { 762 ReportDebugger debugger; 763 return debugger.Run(options) ? 0 : 1; 764 } 765 766 Debugger app; 767 status_t error = app.Init(); 768 if (error != B_OK) { 769 fprintf(stderr, "Error: Failed to init application: %s\n", 770 strerror(error)); 771 return 1; 772 } 773 774 app.Run(); 775 776 return 0; 777 } 778