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