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