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