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