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
45extern const char* __progname;
46const char* kProgramName = __progname;
47
48static const char* const kDebuggerSignature
49	= "application/x-vnd.Haiku-Debugger";
50
51
52static 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
80static void
81print_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
89struct 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
114static void
115set_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
134static bool
135parse_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
237class Debugger : public BApplication,
238	private TargetHostInterfaceRoster::Listener {
239public:
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
249private:
250	virtual bool 				QuitRequested();
251	virtual void 				Quit();
252
253	// TargetHostInterfaceRoster::Listener
254	virtual	void				TeamDebuggerCountChanged(int32 count);
255
256private:
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
262private:
263			DebuggerSettingsManager fSettingsManager;
264			ConnectionConfigWindow* fConnectionWindow;
265			TeamsWindow*		fTeamsWindow;
266			StartTeamWindow*	fStartTeamWindow;
267};
268
269
270// #pragma mark - CliDebugger
271
272
273class CliDebugger : private TargetHostInterfaceRoster::Listener {
274public:
275								CliDebugger();
276								~CliDebugger();
277
278			bool				Run(const Options& options);
279};
280
281
282class ReportDebugger : private TargetHostInterfaceRoster::Listener  {
283public:
284								ReportDebugger();
285								~ReportDebugger();
286			bool				Run(const Options& options);
287};
288
289
290// #pragma mark - Debugger application class
291
292
293Debugger::Debugger()
294	:
295	BApplication(kDebuggerSignature),
296	TargetHostInterfaceRoster::Listener(),
297	fConnectionWindow(NULL),
298	fTeamsWindow(NULL),
299	fStartTeamWindow(NULL)
300{
301}
302
303
304Debugger::~Debugger()
305{
306	DebuggerUiSettingsFactory::DeleteDefault();
307	ValueHandlerRoster::DeleteDefault();
308	ConnectionConfigHandlerRoster::DeleteDefault();
309
310	debugger_global_uninit();
311}
312
313
314status_t
315Debugger::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
337void
338Debugger::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 = NULL;
413			if (message->FindPointer("interface", reinterpret_cast<void**>(
414					&interface)) != B_OK) {
415				// No interface specified: presume local.
416				TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default();
417				for (int32 i = 0; i < roster->CountActiveInterfaces(); i++) {
418					TargetHostInterface* iface = roster->ActiveInterfaceAt(i);
419					if (iface->IsLocal()) {
420						interface = iface;
421						break;
422					}
423				}
424				if (interface == NULL)
425					break;
426			}
427
428			TeamDebuggerOptions options;
429			options.requestType = TEAM_DEBUGGER_REQUEST_ATTACH;
430			options.settingsManager = &fSettingsManager;
431			options.team = teamID;
432			options.userInterface = new(std::nothrow) GraphicalUserInterface;
433			if (options.userInterface == NULL) {
434				// TODO: notify user.
435				fprintf(stderr, "Error: Failed to create GUI\n");
436				break;
437			}
438			BReference<UserInterface> uiReference(options.userInterface, true);
439			status_t error = interface->StartTeamDebugger(options);
440			if (error != B_OK) {
441				// TODO: notify user.
442				fprintf(stderr, "Error: Failed to start team debugger\n");
443			}
444			break;
445		}
446		case MSG_START_NEW_TEAM:
447		{
448			TargetHostInterface* interface;
449			if (message->FindPointer("interface", reinterpret_cast<void**>(
450					&interface)) != B_OK) {
451				break;
452			}
453
454			const char* teamPath = NULL;
455			const char* args = NULL;
456
457			message->FindString("path", &teamPath);
458			message->FindString("arguments", &args);
459
460			status_t result = _StartNewTeam(interface, teamPath, args);
461			BMessage reply;
462			reply.AddInt32("status", result);
463			message->SendReply(&reply);
464			break;
465		}
466		case MSG_LOAD_CORE_TEAM:
467		{
468			TargetHostInterface* interface;
469			if (message->FindPointer("interface", reinterpret_cast<void**>(
470					&interface)) != B_OK) {
471				break;
472			}
473
474			entry_ref ref;
475			if (message->FindRef("core", &ref) != B_OK)
476				break;
477
478			BPath path(&ref);
479			if (path.InitCheck() != B_OK)
480				break;
481
482			Options options;
483			options.coreFilePath = path.Path();
484			_HandleOptions(options);
485			break;
486		}
487		default:
488			BApplication::MessageReceived(message);
489			break;
490	}
491}
492
493
494void
495Debugger::ReadyToRun()
496{
497	TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default();
498	AutoLocker<TargetHostInterfaceRoster> lock(roster);
499	if (roster->CountRunningTeamDebuggers() == 0 && fStartTeamWindow == NULL)
500		PostMessage(MSG_SHOW_TEAMS_WINDOW);
501}
502
503
504void
505Debugger::ArgvReceived(int32 argc, char** argv)
506{
507	Options options;
508	if (!parse_arguments(argc, argv, true, options)) {
509		printf("Debugger::ArgvReceived(): parsing args failed!\n");
510		return;
511	}
512
513	_HandleOptions(options);
514}
515
516
517void
518Debugger::RefsReceived(BMessage* message)
519{
520	// iterate through the refs and handle the files we can handle
521	entry_ref ref;
522	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
523		BPath path;
524		if (path.SetTo(&ref) != B_OK)
525			continue;
526
527		ElfFile elfFile;
528		if (elfFile.Init(path.Path()) != B_OK)
529			continue;
530
531		switch (elfFile.Type()) {
532			case ET_CORE:
533			{
534				// open the core file
535				Options options;
536				options.coreFilePath = path.Path();
537				_HandleOptions(options);
538				break;
539			}
540			case ET_EXEC:
541			case ET_DYN:
542			{
543				// ask the user for arguments to pass to the executable
544				TargetHostInterface* hostInterface = TargetHostInterfaceRoster::Default()
545						->ActiveInterfaceAt(0);
546				status_t error = _ShowStartTeamWindow(hostInterface);
547				if (error != B_OK)
548					continue;
549
550				BMessage message(MSG_SET_TEAM_PATH);
551				message.AddRef("refs", &ref);
552				fStartTeamWindow->PostMessage(&message);
553				break;
554			}
555		}
556	}
557}
558
559
560bool
561Debugger::QuitRequested()
562{
563	// NOTE: The default implementation will just ask all windows'
564	// QuitRequested() hooks. This in turn will ask the TeamWindows.
565	// For now, this is what we want. If we have more windows later,
566	// like the global TeamsWindow, then we want to just ask the
567	// TeamDebuggers, the TeamsWindow should of course not go away already
568	// if one or more TeamDebuggers want to stay later. There are multiple
569	// ways how to do this. For example, TeamDebugger could get a
570	// QuitRequested() hook or the TeamsWindow and other global windows
571	// could always return false in their QuitRequested().
572	return BApplication::QuitRequested();
573		// TODO: This is ugly. The team debuggers own the windows, not the
574		// other way around.
575}
576
577void
578Debugger::Quit()
579{
580	TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default();
581	AutoLocker<TargetHostInterfaceRoster> lock(roster);
582	// don't quit before all team debuggers have been quit
583	if (roster->CountRunningTeamDebuggers() == 0 && fTeamsWindow == NULL)
584		BApplication::Quit();
585}
586
587
588void
589Debugger::TeamDebuggerCountChanged(int32 count)
590{
591	if (count == 0) {
592		AutoLocker<Debugger> lock(this);
593		Quit();
594	}
595}
596
597
598status_t
599Debugger::_ShowStartTeamWindow(TargetHostInterface* interface)
600{
601	if (fStartTeamWindow == NULL) {
602		TargetHostInterface* hostInterface = TargetHostInterfaceRoster::Default()
603			->ActiveInterfaceAt(0);
604		fStartTeamWindow = StartTeamWindow::Create(hostInterface);
605		if (fStartTeamWindow == NULL)
606			return B_NO_MEMORY;
607		fStartTeamWindow->Show();
608	} else
609		fStartTeamWindow->Activate();
610
611	return B_OK;
612}
613
614
615status_t
616Debugger::_StartNewTeam(TargetHostInterface* interface, const char* path,
617	const char* args)
618{
619	if (path == NULL)
620		return B_BAD_VALUE;
621
622	BString data;
623	data.SetToFormat("\"%s\" %s", path, args);
624	if (data.Length() == 0)
625		return B_NO_MEMORY;
626
627	ArgumentVector argVector;
628	argVector.Parse(data.String());
629
630	TeamDebuggerOptions options;
631	options.requestType = TEAM_DEBUGGER_REQUEST_CREATE;
632	options.settingsManager = &fSettingsManager;
633	options.userInterface = new(std::nothrow) GraphicalUserInterface;
634	if (options.userInterface == NULL)
635		return B_NO_MEMORY;
636	BReference<UserInterface> uiReference(options.userInterface, true);
637	options.commandLineArgc = argVector.ArgumentCount();
638	if (options.commandLineArgc <= 0)
639		return B_BAD_VALUE;
640
641	char** argv = argVector.DetachArguments();
642
643	options.commandLineArgv = argv;
644	MemoryDeleter deleter(argv);
645
646	status_t error = interface->StartTeamDebugger(options);
647	if (error == B_OK) {
648		deleter.Detach();
649	}
650
651	return error;
652}
653
654
655status_t
656Debugger::_HandleOptions(const Options& options)
657{
658	TeamDebuggerOptions debuggerOptions;
659	set_debugger_options_from_options(debuggerOptions, options);
660	debuggerOptions.settingsManager = &fSettingsManager;
661	debuggerOptions.userInterface = new(std::nothrow) GraphicalUserInterface;
662	if (debuggerOptions.userInterface == NULL)
663		return B_NO_MEMORY;
664	BReference<UserInterface> uiReference(debuggerOptions.userInterface, true);
665	TargetHostInterface* hostInterface
666		= TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0);
667	return hostInterface->StartTeamDebugger(debuggerOptions);
668}
669
670
671// #pragma mark - CliDebugger
672
673
674CliDebugger::CliDebugger()
675{
676}
677
678
679CliDebugger::~CliDebugger()
680{
681	DebuggerUiSettingsFactory::DeleteDefault();
682	debugger_global_uninit();
683}
684
685
686bool
687CliDebugger::Run(const Options& options)
688{
689	if (options.commandLineArgc == 0
690		&& options.team < 0
691		&& options.thread < 0
692		&& options.coreFilePath == NULL) {
693		fprintf(stderr, "No target specified to debug\n");
694		return false;
695	}
696
697	// Block SIGINT, in this thread so all threads created by it inherit the
698	// a block mask with the signal blocked. In the input loop the signal will
699	// be unblocked again.
700	SignalSet(SIGINT).BlockInCurrentThread();
701
702	// initialize global objects and settings manager
703	status_t error = debugger_global_init(this);
704	if (error != B_OK) {
705		fprintf(stderr, "Error: Global initialization failed: %s\n",
706			strerror(error));
707		return false;
708	}
709
710	error = DebuggerUiSettingsFactory::CreateDefault();
711	if (error != B_OK) {
712		fprintf(stderr, "Error: Failed to create default settings factory: "
713			"%s\n",	strerror(error));
714		return false;
715	}
716
717
718	DebuggerSettingsManager settingsManager;
719	error = settingsManager.Init(DebuggerUiSettingsFactory::Default());
720	if (error != B_OK) {
721		fprintf(stderr, "Error: Settings manager initialization failed: "
722			"%s\n", strerror(error));
723		return false;
724	}
725
726	// create the command line UI
727	CommandLineUserInterface* userInterface
728		= new(std::nothrow) CommandLineUserInterface();
729	if (userInterface == NULL) {
730		fprintf(stderr, "Error: Out of memory!\n");
731		return false;
732	}
733	BReference<UserInterface> userInterfaceReference(userInterface, true);
734
735	// TODO: once we support specifying a remote interface via command line
736	// args, this needs to be adjusted. For now, always assume actions
737	// are being taken via the local interface.
738	TargetHostInterface* hostInterface
739		= TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0);
740
741	TeamDebuggerOptions debuggerOptions;
742	set_debugger_options_from_options(debuggerOptions, options);
743	debuggerOptions.userInterface = userInterface;
744	debuggerOptions.settingsManager = &settingsManager;
745	error = hostInterface->StartTeamDebugger(debuggerOptions);
746	if (error != B_OK)
747		return false;
748
749	TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0);
750	thread_id teamDebuggerThread = teamDebugger->Thread();
751
752	// run the input loop
753	userInterface->Run();
754
755	// wait for the team debugger thread to terminate
756	wait_for_thread(teamDebuggerThread, NULL);
757
758	return true;
759}
760
761
762// #pragma mark - ReportDebugger
763
764
765ReportDebugger::ReportDebugger()
766{
767}
768
769
770ReportDebugger::~ReportDebugger()
771{
772	debugger_global_uninit();
773}
774
775
776bool
777ReportDebugger::Run(const Options& options)
778{
779	// initialize global objects and settings manager
780	status_t error = debugger_global_init(this);
781	if (error != B_OK) {
782		fprintf(stderr, "Error: Global initialization failed: %s\n",
783			strerror(error));
784		return false;
785	}
786
787	// create the report UI
788	ReportUserInterface* userInterface
789		= new(std::nothrow) ReportUserInterface(options.thread, options.reportPath);
790	if (userInterface == NULL) {
791		fprintf(stderr, "Error: Out of memory!\n");
792		return false;
793	}
794	BReference<UserInterface> userInterfaceReference(userInterface, true);
795
796	TargetHostInterface* hostInterface
797		= TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0);
798
799	TeamDebuggerOptions debuggerOptions;
800	set_debugger_options_from_options(debuggerOptions, options);
801	debuggerOptions.userInterface = userInterface;
802	error = hostInterface->StartTeamDebugger(debuggerOptions);
803	if (error != B_OK)
804		return false;
805
806	TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0);
807	thread_id teamDebuggerThread = teamDebugger->Thread();
808
809	// run the input loop
810	userInterface->Run();
811
812	// wait for the team debugger thread to terminate
813	wait_for_thread(teamDebuggerThread, NULL);
814
815	return true;
816}
817
818
819// #pragma mark -
820
821
822int
823main(int argc, const char* const* argv)
824{
825	// We test-parse the arguments here, so that, when we're started from the
826	// terminal and there's an instance already running, we can print an error
827	// message to the terminal, if something's wrong with the arguments.
828	// Otherwise, the arguments are reparsed in the actual application,
829	// unless the option to use the command line interface was chosen.
830
831	Options options;
832	parse_arguments(argc, argv, false, options);
833
834	if (options.useCLI) {
835		CliDebugger debugger;
836		return debugger.Run(options) ? 0 : 1;
837	} else if (options.saveReport) {
838		ReportDebugger debugger;
839		return debugger.Run(options) ? 0 : 1;
840	}
841
842	Debugger app;
843	status_t error = app.Init();
844	if (error != B_OK) {
845		fprintf(stderr, "Error: Failed to init application: %s\n",
846			strerror(error));
847		return 1;
848	}
849
850	app.Run();
851
852	return 0;
853}
854