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