libroot/posix/stdio: Remove unused portions.
[haiku.git] / src / apps / debugger / Debugger.cpp
blob2519781402836e130103f9f3ea02b2ad0377959b
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 */
8 #include <getopt.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
13 #include <new>
15 #include <Application.h>
16 #include <Entry.h>
17 #include <Message.h>
18 #include <ObjectList.h>
19 #include <Path.h>
21 #include <ArgumentVector.h>
22 #include <AutoDeleter.h>
23 #include <AutoLocker.h>
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"
45 extern const char* __progname;
46 const char* kProgramName = __progname;
48 static const char* const kDebuggerSignature
49 = "application/x-vnd.Haiku-Debugger";
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"
80 static void
81 print_usage_and_exit(bool error)
83 fprintf(error ? stderr : stdout, kUsage, kProgramName, kProgramName,
84 kProgramName, kProgramName, kProgramName);
85 exit(error ? 1 : 0);
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;
99 Options()
101 commandLineArgc(0),
102 commandLineArgv(NULL),
103 team(-1),
104 thread(-1),
105 useCLI(false),
106 saveReport(false),
107 reportPath(NULL),
108 coreFilePath(NULL)
114 static void
115 set_debugger_options_from_options(TeamDebuggerOptions& _debuggerOptions,
116 const Options& options)
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;
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;
134 static bool
135 parse_arguments(int argc, const char* const* argv, bool noOutput,
136 Options& options)
138 optind = 1;
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 }
151 opterr = 0; // don't print errors
153 int c = getopt_long(argc, (char**)argv, "+chs", sLongOptions, NULL);
154 if (c == -1)
155 break;
157 switch (c) {
158 case 'c':
159 options.useCLI = true;
160 break;
162 case 'C':
163 options.coreFilePath = optarg;
164 break;
166 case 'h':
167 if (noOutput)
168 return false;
169 print_usage_and_exit(false);
170 break;
172 case 's':
174 options.saveReport = true;
175 options.reportPath = optarg;
176 break;
179 case 't':
181 options.team = strtol(optarg, NULL, 0);
182 if (options.team <= 0) {
183 if (noOutput)
184 return false;
185 print_usage_and_exit(true);
187 break;
190 case 'T':
192 options.thread = strtol(optarg, NULL, 0);
193 if (options.thread <= 0) {
194 if (noOutput)
195 return false;
196 print_usage_and_exit(true);
198 break;
201 default:
202 if (noOutput)
203 return false;
204 print_usage_and_exit(true);
205 break;
209 if (optind < argc) {
210 options.commandLineArgc = argc - optind;
211 options.commandLineArgv = argv + optind;
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++;
222 if (exclusiveParams == 0) {
223 return true;
224 } else if (exclusiveParams != 1) {
225 if (noOutput)
226 return false;
227 print_usage_and_exit(true);
230 return true;
234 // #pragma mark - Debugger application class
237 class Debugger : public BApplication,
238 private TargetHostInterfaceRoster::Listener {
239 public:
240 Debugger();
241 ~Debugger();
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);
249 private:
250 virtual bool QuitRequested();
251 virtual void Quit();
253 // TargetHostInterfaceRoster::Listener
254 virtual void TeamDebuggerCountChanged(int32 count);
256 private:
257 status_t _StartNewTeam(TargetHostInterface* interface,
258 const char* teamPath, const char* args);
259 status_t _HandleOptions(const Options& options);
261 private:
262 DebuggerSettingsManager fSettingsManager;
263 ConnectionConfigWindow* fConnectionWindow;
264 TeamsWindow* fTeamsWindow;
265 StartTeamWindow* fStartTeamWindow;
269 // #pragma mark - CliDebugger
272 class CliDebugger : private TargetHostInterfaceRoster::Listener {
273 public:
274 CliDebugger();
275 ~CliDebugger();
277 bool Run(const Options& options);
281 class ReportDebugger : private TargetHostInterfaceRoster::Listener {
282 public:
283 ReportDebugger();
284 ~ReportDebugger();
285 bool Run(const Options& options);
289 // #pragma mark - Debugger application class
292 Debugger::Debugger()
294 BApplication(kDebuggerSignature),
295 TargetHostInterfaceRoster::Listener(),
296 fConnectionWindow(NULL),
297 fTeamsWindow(NULL),
298 fStartTeamWindow(NULL)
303 Debugger::~Debugger()
305 DebuggerUiSettingsFactory::DeleteDefault();
306 ValueHandlerRoster::DeleteDefault();
307 ConnectionConfigHandlerRoster::DeleteDefault();
309 debugger_global_uninit();
313 status_t
314 Debugger::Init()
316 status_t error = debugger_global_init(this);
317 if (error != B_OK)
318 return error;
320 error = DebuggerUiSettingsFactory::CreateDefault();
321 if (error != B_OK)
322 return error;
324 error = ValueHandlerRoster::CreateDefault();
325 if (error != B_OK)
326 return error;
328 error = ConnectionConfigHandlerRoster::CreateDefault();
329 if (error != B_OK)
330 return error;
332 return fSettingsManager.Init(DebuggerUiSettingsFactory::Default());
336 void
337 Debugger::MessageReceived(BMessage* message)
339 switch (message->what) {
340 case MSG_SHOW_TEAMS_WINDOW:
342 if (fTeamsWindow) {
343 fTeamsWindow->Activate(true);
344 break;
347 try {
348 fTeamsWindow = TeamsWindow::Create(&fSettingsManager);
349 if (fTeamsWindow != NULL)
350 fTeamsWindow->Show();
351 } catch (...) {
352 // TODO: Notify the user!
353 fprintf(stderr, "Error: Failed to create Teams window\n");
355 break;
357 case MSG_TEAMS_WINDOW_CLOSED:
359 fTeamsWindow = NULL;
360 Quit();
361 break;
363 case MSG_SHOW_START_TEAM_WINDOW:
365 TargetHostInterface* hostInterface;
366 if (message->FindPointer("interface",
367 reinterpret_cast<void**>(&hostInterface)) != B_OK) {
368 // if an interface isn't explicitly supplied, fall back to
369 // the default local interface.
370 hostInterface = TargetHostInterfaceRoster::Default()
371 ->ActiveInterfaceAt(0);
374 BMessenger messenger(fStartTeamWindow);
375 if (!messenger.IsValid()) {
376 fStartTeamWindow = StartTeamWindow::Create(hostInterface);
377 if (fStartTeamWindow == NULL)
378 break;
379 fStartTeamWindow->Show();
380 } else
381 fStartTeamWindow->Activate();
382 break;
384 case MSG_START_TEAM_WINDOW_CLOSED:
386 fStartTeamWindow = NULL;
387 break;
389 case MSG_SHOW_CONNECTION_CONFIG_WINDOW:
391 if (fConnectionWindow != NULL) {
392 fConnectionWindow->Activate(true);
393 break;
396 try {
397 fConnectionWindow = ConnectionConfigWindow::Create();
398 if (fConnectionWindow != NULL)
399 fConnectionWindow->Show();
400 } catch (...) {
401 // TODO: Notify the user!
402 fprintf(stderr, "Error: Failed to create Teams window\n");
404 break;
406 case MSG_CONNECTION_CONFIG_WINDOW_CLOSED:
408 fConnectionWindow = NULL;
409 break;
411 case MSG_DEBUG_THIS_TEAM:
413 team_id teamID;
414 if (message->FindInt32("team", &teamID) != B_OK)
415 break;
417 TargetHostInterface* interface;
418 if (message->FindPointer("interface", reinterpret_cast<void**>(
419 &interface)) != B_OK) {
420 break;
423 TeamDebuggerOptions options;
424 options.requestType = TEAM_DEBUGGER_REQUEST_ATTACH;
425 options.settingsManager = &fSettingsManager;
426 options.team = teamID;
427 options.userInterface = new(std::nothrow) GraphicalUserInterface;
428 if (options.userInterface == NULL) {
429 // TODO: notify user.
430 break;
432 BReference<UserInterface> uiReference(options.userInterface, true);
433 status_t error = interface->StartTeamDebugger(options);
434 if (error != B_OK) {
435 // TODO: notify user.
436 } else
437 uiReference.Detach();
438 break;
440 case MSG_START_NEW_TEAM:
442 TargetHostInterface* interface;
443 if (message->FindPointer("interface", reinterpret_cast<void**>(
444 &interface)) != B_OK) {
445 break;
448 const char* teamPath = NULL;
449 const char* args = NULL;
451 message->FindString("path", &teamPath);
452 message->FindString("arguments", &args);
454 status_t result = _StartNewTeam(interface, teamPath, args);
455 BMessage reply;
456 reply.AddInt32("status", result);
457 message->SendReply(&reply);
458 break;
460 case MSG_LOAD_CORE_TEAM:
462 TargetHostInterface* interface;
463 if (message->FindPointer("interface", reinterpret_cast<void**>(
464 &interface)) != B_OK) {
465 break;
468 entry_ref ref;
469 if (message->FindRef("core", &ref) != B_OK)
470 break;
472 BPath path(&ref);
473 if (path.InitCheck() != B_OK)
474 break;
476 Options options;
477 options.coreFilePath = path.Path();
478 _HandleOptions(options);
479 break;
481 default:
482 BApplication::MessageReceived(message);
483 break;
488 void
489 Debugger::ReadyToRun()
491 TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default();
492 AutoLocker<TargetHostInterfaceRoster> lock(roster);
493 if (roster->CountRunningTeamDebuggers() == 0)
494 PostMessage(MSG_SHOW_TEAMS_WINDOW);
498 void
499 Debugger::ArgvReceived(int32 argc, char** argv)
501 Options options;
502 if (!parse_arguments(argc, argv, true, options)) {
503 printf("Debugger::ArgvReceived(): parsing args failed!\n");
504 return;
507 _HandleOptions(options);
511 void
512 Debugger::RefsReceived(BMessage* message)
514 // iterate through the refs and handle the files we can handle
515 entry_ref ref;
516 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
517 BPath path;
518 if (path.SetTo(&ref) != B_OK)
519 continue;
521 // check, whether this is a core file
523 ElfFile elfFile;
524 if (elfFile.Init(path.Path()) != B_OK || elfFile.Type() != ET_CORE)
525 continue;
528 // handle the core file
529 Options options;
530 options.coreFilePath = path.Path();
531 _HandleOptions(options);
536 bool
537 Debugger::QuitRequested()
539 // NOTE: The default implementation will just ask all windows'
540 // QuitRequested() hooks. This in turn will ask the TeamWindows.
541 // For now, this is what we want. If we have more windows later,
542 // like the global TeamsWindow, then we want to just ask the
543 // TeamDebuggers, the TeamsWindow should of course not go away already
544 // if one or more TeamDebuggers want to stay later. There are multiple
545 // ways how to do this. For example, TeamDebugger could get a
546 // QuitRequested() hook or the TeamsWindow and other global windows
547 // could always return false in their QuitRequested().
548 return BApplication::QuitRequested();
549 // TODO: This is ugly. The team debuggers own the windows, not the
550 // other way around.
553 void
554 Debugger::Quit()
556 TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default();
557 AutoLocker<TargetHostInterfaceRoster> lock(roster);
558 // don't quit before all team debuggers have been quit
559 if (roster->CountRunningTeamDebuggers() == 0 && fTeamsWindow == NULL)
560 BApplication::Quit();
564 void
565 Debugger::TeamDebuggerCountChanged(int32 count)
567 if (count == 0) {
568 AutoLocker<Debugger> lock(this);
569 Quit();
574 status_t
575 Debugger::_StartNewTeam(TargetHostInterface* interface, const char* path,
576 const char* args)
578 if (path == NULL)
579 return B_BAD_VALUE;
581 BString data;
582 data.SetToFormat("\"%s\" %s", path, args);
583 if (data.Length() == 0)
584 return B_NO_MEMORY;
586 ArgumentVector argVector;
587 argVector.Parse(data.String());
589 TeamDebuggerOptions options;
590 options.requestType = TEAM_DEBUGGER_REQUEST_CREATE;
591 options.settingsManager = &fSettingsManager;
592 options.userInterface = new(std::nothrow) GraphicalUserInterface;
593 if (options.userInterface == NULL)
594 return B_NO_MEMORY;
595 BReference<UserInterface> uiReference(options.userInterface, true);
596 options.commandLineArgc = argVector.ArgumentCount();
597 if (options.commandLineArgc <= 0)
598 return B_BAD_VALUE;
600 char** argv = argVector.DetachArguments();
602 options.commandLineArgv = argv;
603 MemoryDeleter deleter(argv);
605 status_t error = interface->StartTeamDebugger(options);
606 if (error == B_OK) {
607 deleter.Detach();
608 uiReference.Detach();
611 return error;
615 status_t
616 Debugger::_HandleOptions(const Options& options)
618 TeamDebuggerOptions debuggerOptions;
619 set_debugger_options_from_options(debuggerOptions, options);
620 debuggerOptions.settingsManager = &fSettingsManager;
621 debuggerOptions.userInterface = new(std::nothrow) GraphicalUserInterface;
622 if (debuggerOptions.userInterface == NULL)
623 return B_NO_MEMORY;
624 BReference<UserInterface> uiReference(debuggerOptions.userInterface, true);
625 TargetHostInterface* hostInterface
626 = TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0);
627 status_t error = hostInterface->StartTeamDebugger(debuggerOptions);
628 if (error == B_OK)
629 uiReference.Detach();
631 return error;
635 // #pragma mark - CliDebugger
638 CliDebugger::CliDebugger()
643 CliDebugger::~CliDebugger()
645 DebuggerUiSettingsFactory::DeleteDefault();
646 debugger_global_uninit();
650 bool
651 CliDebugger::Run(const Options& options)
653 // Block SIGINT, in this thread so all threads created by it inherit the
654 // a block mask with the signal blocked. In the input loop the signal will
655 // be unblocked again.
656 SignalSet(SIGINT).BlockInCurrentThread();
658 // initialize global objects and settings manager
659 status_t error = debugger_global_init(this);
660 if (error != B_OK) {
661 fprintf(stderr, "Error: Global initialization failed: %s\n",
662 strerror(error));
663 return false;
666 error = DebuggerUiSettingsFactory::CreateDefault();
667 if (error != B_OK) {
668 fprintf(stderr, "Error: Failed to create default settings factory: "
669 "%s\n", strerror(error));
670 return false;
674 DebuggerSettingsManager settingsManager;
675 error = settingsManager.Init(DebuggerUiSettingsFactory::Default());
676 if (error != B_OK) {
677 fprintf(stderr, "Error: Settings manager initialization failed: "
678 "%s\n", strerror(error));
679 return false;
682 // create the command line UI
683 CommandLineUserInterface* userInterface
684 = new(std::nothrow) CommandLineUserInterface();
685 if (userInterface == NULL) {
686 fprintf(stderr, "Error: Out of memory!\n");
687 return false;
689 BReference<UserInterface> userInterfaceReference(userInterface, true);
691 // TODO: once we support specifying a remote interface via command line
692 // args, this needs to be adjusted. For now, always assume actions
693 // are being taken via the local interface.
694 TargetHostInterface* hostInterface
695 = TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0);
697 TeamDebuggerOptions debuggerOptions;
698 set_debugger_options_from_options(debuggerOptions, options);
699 debuggerOptions.userInterface = userInterface;
700 debuggerOptions.settingsManager = &settingsManager;
701 error = hostInterface->StartTeamDebugger(debuggerOptions);
702 if (error != B_OK)
703 return false;
705 TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0);
706 thread_id teamDebuggerThread = teamDebugger->Thread();
708 // run the input loop
709 userInterface->Run();
711 // wait for the team debugger thread to terminate
712 wait_for_thread(teamDebuggerThread, NULL);
714 return true;
718 // #pragma mark - ReportDebugger
721 ReportDebugger::ReportDebugger()
726 ReportDebugger::~ReportDebugger()
728 debugger_global_uninit();
732 bool
733 ReportDebugger::Run(const Options& options)
735 // initialize global objects and settings manager
736 status_t error = debugger_global_init(this);
737 if (error != B_OK) {
738 fprintf(stderr, "Error: Global initialization failed: %s\n",
739 strerror(error));
740 return false;
743 // create the report UI
744 ReportUserInterface* userInterface
745 = new(std::nothrow) ReportUserInterface(options.thread, options.reportPath);
746 if (userInterface == NULL) {
747 fprintf(stderr, "Error: Out of memory!\n");
748 return false;
750 BReference<UserInterface> userInterfaceReference(userInterface, true);
752 TargetHostInterface* hostInterface
753 = TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0);
755 TeamDebuggerOptions debuggerOptions;
756 set_debugger_options_from_options(debuggerOptions, options);
757 debuggerOptions.userInterface = userInterface;
758 error = hostInterface->StartTeamDebugger(debuggerOptions);
759 if (error != B_OK)
760 return false;
762 TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0);
763 thread_id teamDebuggerThread = teamDebugger->Thread();
765 // run the input loop
766 userInterface->Run();
768 // wait for the team debugger thread to terminate
769 wait_for_thread(teamDebuggerThread, NULL);
771 return true;
775 // #pragma mark -
779 main(int argc, const char* const* argv)
781 // We test-parse the arguments here, so that, when we're started from the
782 // terminal and there's an instance already running, we can print an error
783 // message to the terminal, if something's wrong with the arguments.
784 // Otherwise, the arguments are reparsed in the actual application,
785 // unless the option to use the command line interface was chosen.
787 Options options;
788 parse_arguments(argc, argv, false, options);
790 if (options.useCLI) {
791 CliDebugger debugger;
792 return debugger.Run(options) ? 0 : 1;
793 } else if (options.saveReport) {
794 ReportDebugger debugger;
795 return debugger.Run(options) ? 0 : 1;
798 Debugger app;
799 status_t error = app.Init();
800 if (error != B_OK) {
801 fprintf(stderr, "Error: Failed to init application: %s\n",
802 strerror(error));
803 return 1;
806 app.Run();
808 return 0;