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 } 437 break; 438 } 439 case MSG_START_NEW_TEAM: 440 { 441 TargetHostInterface* interface; 442 if (message->FindPointer("interface", reinterpret_cast<void**>( 443 &interface)) != B_OK) { 444 break; 445 } 446 447 const char* teamPath = NULL; 448 const char* args = NULL; 449 450 message->FindString("path", &teamPath); 451 message->FindString("arguments", &args); 452 453 status_t result = _StartNewTeam(interface, teamPath, args); 454 BMessage reply; 455 reply.AddInt32("status", result); 456 message->SendReply(&reply); 457 break; 458 } 459 case MSG_LOAD_CORE_TEAM: 460 { 461 TargetHostInterface* interface; 462 if (message->FindPointer("interface", reinterpret_cast<void**>( 463 &interface)) != B_OK) { 464 break; 465 } 466 467 entry_ref ref; 468 if (message->FindRef("core", &ref) != B_OK) 469 break; 470 471 BPath path(&ref); 472 if (path.InitCheck() != B_OK) 473 break; 474 475 Options options; 476 options.coreFilePath = path.Path(); 477 _HandleOptions(options); 478 break; 479 } 480 default: 481 BApplication::MessageReceived(message); 482 break; 483 } 484 } 485 486 487 void 488 Debugger::ReadyToRun() 489 { 490 TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default(); 491 AutoLocker<TargetHostInterfaceRoster> lock(roster); 492 if (roster->CountRunningTeamDebuggers() == 0) 493 PostMessage(MSG_SHOW_TEAMS_WINDOW); 494 } 495 496 497 void 498 Debugger::ArgvReceived(int32 argc, char** argv) 499 { 500 Options options; 501 if (!parse_arguments(argc, argv, true, options)) { 502 printf("Debugger::ArgvReceived(): parsing args failed!\n"); 503 return; 504 } 505 506 _HandleOptions(options); 507 } 508 509 510 void 511 Debugger::RefsReceived(BMessage* message) 512 { 513 // iterate through the refs and handle the files we can handle 514 entry_ref ref; 515 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { 516 BPath path; 517 if (path.SetTo(&ref) != B_OK) 518 continue; 519 520 // check, whether this is a core file 521 { 522 ElfFile elfFile; 523 if (elfFile.Init(path.Path()) != B_OK || elfFile.Type() != ET_CORE) 524 continue; 525 } 526 527 // handle the core file 528 Options options; 529 options.coreFilePath = path.Path(); 530 _HandleOptions(options); 531 } 532 } 533 534 535 bool 536 Debugger::QuitRequested() 537 { 538 // NOTE: The default implementation will just ask all windows' 539 // QuitRequested() hooks. This in turn will ask the TeamWindows. 540 // For now, this is what we want. If we have more windows later, 541 // like the global TeamsWindow, then we want to just ask the 542 // TeamDebuggers, the TeamsWindow should of course not go away already 543 // if one or more TeamDebuggers want to stay later. There are multiple 544 // ways how to do this. For example, TeamDebugger could get a 545 // QuitRequested() hook or the TeamsWindow and other global windows 546 // could always return false in their QuitRequested(). 547 return BApplication::QuitRequested(); 548 // TODO: This is ugly. The team debuggers own the windows, not the 549 // other way around. 550 } 551 552 void 553 Debugger::Quit() 554 { 555 TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default(); 556 AutoLocker<TargetHostInterfaceRoster> lock(roster); 557 // don't quit before all team debuggers have been quit 558 if (roster->CountRunningTeamDebuggers() == 0 && fTeamsWindow == NULL) 559 BApplication::Quit(); 560 } 561 562 563 void 564 Debugger::TeamDebuggerCountChanged(int32 count) 565 { 566 if (count == 0) { 567 AutoLocker<Debugger> lock(this); 568 Quit(); 569 } 570 } 571 572 573 status_t 574 Debugger::_StartNewTeam(TargetHostInterface* interface, const char* path, 575 const char* args) 576 { 577 if (path == NULL) 578 return B_BAD_VALUE; 579 580 BString data; 581 data.SetToFormat("\"%s\" %s", path, args); 582 if (data.Length() == 0) 583 return B_NO_MEMORY; 584 585 ArgumentVector argVector; 586 argVector.Parse(data.String()); 587 588 TeamDebuggerOptions options; 589 options.requestType = TEAM_DEBUGGER_REQUEST_CREATE; 590 options.settingsManager = &fSettingsManager; 591 options.userInterface = new(std::nothrow) GraphicalUserInterface; 592 if (options.userInterface == NULL) 593 return B_NO_MEMORY; 594 BReference<UserInterface> uiReference(options.userInterface, true); 595 options.commandLineArgc = argVector.ArgumentCount(); 596 if (options.commandLineArgc <= 0) 597 return B_BAD_VALUE; 598 599 char** argv = argVector.DetachArguments(); 600 601 options.commandLineArgv = argv; 602 MemoryDeleter deleter(argv); 603 604 status_t error = interface->StartTeamDebugger(options); 605 if (error == B_OK) { 606 deleter.Detach(); 607 } 608 609 return error; 610 } 611 612 613 status_t 614 Debugger::_HandleOptions(const Options& options) 615 { 616 TeamDebuggerOptions debuggerOptions; 617 set_debugger_options_from_options(debuggerOptions, options); 618 debuggerOptions.settingsManager = &fSettingsManager; 619 debuggerOptions.userInterface = new(std::nothrow) GraphicalUserInterface; 620 if (debuggerOptions.userInterface == NULL) 621 return B_NO_MEMORY; 622 BReference<UserInterface> uiReference(debuggerOptions.userInterface, true); 623 TargetHostInterface* hostInterface 624 = TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0); 625 return hostInterface->StartTeamDebugger(debuggerOptions); 626 } 627 628 629 // #pragma mark - CliDebugger 630 631 632 CliDebugger::CliDebugger() 633 { 634 } 635 636 637 CliDebugger::~CliDebugger() 638 { 639 DebuggerUiSettingsFactory::DeleteDefault(); 640 debugger_global_uninit(); 641 } 642 643 644 bool 645 CliDebugger::Run(const Options& options) 646 { 647 // Block SIGINT, in this thread so all threads created by it inherit the 648 // a block mask with the signal blocked. In the input loop the signal will 649 // be unblocked again. 650 SignalSet(SIGINT).BlockInCurrentThread(); 651 652 // initialize global objects and settings manager 653 status_t error = debugger_global_init(this); 654 if (error != B_OK) { 655 fprintf(stderr, "Error: Global initialization failed: %s\n", 656 strerror(error)); 657 return false; 658 } 659 660 error = DebuggerUiSettingsFactory::CreateDefault(); 661 if (error != B_OK) { 662 fprintf(stderr, "Error: Failed to create default settings factory: " 663 "%s\n", strerror(error)); 664 return false; 665 } 666 667 668 DebuggerSettingsManager settingsManager; 669 error = settingsManager.Init(DebuggerUiSettingsFactory::Default()); 670 if (error != B_OK) { 671 fprintf(stderr, "Error: Settings manager initialization failed: " 672 "%s\n", strerror(error)); 673 return false; 674 } 675 676 // create the command line UI 677 CommandLineUserInterface* userInterface 678 = new(std::nothrow) CommandLineUserInterface(); 679 if (userInterface == NULL) { 680 fprintf(stderr, "Error: Out of memory!\n"); 681 return false; 682 } 683 BReference<UserInterface> userInterfaceReference(userInterface, true); 684 685 // TODO: once we support specifying a remote interface via command line 686 // args, this needs to be adjusted. For now, always assume actions 687 // are being taken via the local interface. 688 TargetHostInterface* hostInterface 689 = TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0); 690 691 TeamDebuggerOptions debuggerOptions; 692 set_debugger_options_from_options(debuggerOptions, options); 693 debuggerOptions.userInterface = userInterface; 694 debuggerOptions.settingsManager = &settingsManager; 695 error = hostInterface->StartTeamDebugger(debuggerOptions); 696 if (error != B_OK) 697 return false; 698 699 TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0); 700 thread_id teamDebuggerThread = teamDebugger->Thread(); 701 702 // run the input loop 703 userInterface->Run(); 704 705 // wait for the team debugger thread to terminate 706 wait_for_thread(teamDebuggerThread, NULL); 707 708 return true; 709 } 710 711 712 // #pragma mark - ReportDebugger 713 714 715 ReportDebugger::ReportDebugger() 716 { 717 } 718 719 720 ReportDebugger::~ReportDebugger() 721 { 722 debugger_global_uninit(); 723 } 724 725 726 bool 727 ReportDebugger::Run(const Options& options) 728 { 729 // initialize global objects and settings manager 730 status_t error = debugger_global_init(this); 731 if (error != B_OK) { 732 fprintf(stderr, "Error: Global initialization failed: %s\n", 733 strerror(error)); 734 return false; 735 } 736 737 // create the report UI 738 ReportUserInterface* userInterface 739 = new(std::nothrow) ReportUserInterface(options.thread, options.reportPath); 740 if (userInterface == NULL) { 741 fprintf(stderr, "Error: Out of memory!\n"); 742 return false; 743 } 744 BReference<UserInterface> userInterfaceReference(userInterface, true); 745 746 TargetHostInterface* hostInterface 747 = TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0); 748 749 TeamDebuggerOptions debuggerOptions; 750 set_debugger_options_from_options(debuggerOptions, options); 751 debuggerOptions.userInterface = userInterface; 752 error = hostInterface->StartTeamDebugger(debuggerOptions); 753 if (error != B_OK) 754 return false; 755 756 TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0); 757 thread_id teamDebuggerThread = teamDebugger->Thread(); 758 759 // run the input loop 760 userInterface->Run(); 761 762 // wait for the team debugger thread to terminate 763 wait_for_thread(teamDebuggerThread, NULL); 764 765 return true; 766 } 767 768 769 // #pragma mark - 770 771 772 int 773 main(int argc, const char* const* argv) 774 { 775 // We test-parse the arguments here, so that, when we're started from the 776 // terminal and there's an instance already running, we can print an error 777 // message to the terminal, if something's wrong with the arguments. 778 // Otherwise, the arguments are reparsed in the actual application, 779 // unless the option to use the command line interface was chosen. 780 781 Options options; 782 parse_arguments(argc, argv, false, options); 783 784 if (options.useCLI) { 785 CliDebugger debugger; 786 return debugger.Run(options) ? 0 : 1; 787 } else if (options.saveReport) { 788 ReportDebugger debugger; 789 return debugger.Run(options) ? 0 : 1; 790 } 791 792 Debugger app; 793 status_t error = app.Init(); 794 if (error != B_OK) { 795 fprintf(stderr, "Error: Failed to init application: %s\n", 796 strerror(error)); 797 return 1; 798 } 799 800 app.Run(); 801 802 return 0; 803 } 804