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