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