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