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
34extern const char* __progname;
35const char* kProgramName = __progname;
36
37static const char* const kDebuggerSignature
38	= "application/x-vnd.Haiku-Debugger";
39
40
41static 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
66static void
67print_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
75struct 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
98struct DebuggedProgramInfo {
99	team_id		team;
100	thread_id	thread;
101	bool		stopInMain;
102};
103
104
105static bool
106parse_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
200static status_t
201global_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 */
222static bool
223get_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 */
279static TeamDebugger*
280start_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
322class Debugger : public BApplication, private TeamDebugger::Listener {
323public:
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
332private:
333			typedef BObjectList<TeamDebugger>	TeamDebuggerList;
334
335private:
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
345private:
346			SettingsManager		fSettingsManager;
347			TeamDebuggerList	fTeamDebuggers;
348			int32				fRunningTeamDebuggers;
349			TeamsWindow*		fTeamsWindow;
350};
351
352
353// #pragma mark - CliDebugger
354
355
356class CliDebugger : private TeamDebugger::Listener {
357public:
358								CliDebugger();
359								~CliDebugger();
360
361			bool				Run(const Options& options);
362
363private:
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
373Debugger::Debugger()
374	:
375	BApplication(kDebuggerSignature),
376	fRunningTeamDebuggers(0),
377	fTeamsWindow(NULL)
378{
379}
380
381
382Debugger::~Debugger()
383{
384	ValueHandlerRoster::DeleteDefault();
385	TypeHandlerRoster::DeleteDefault();
386}
387
388
389status_t
390Debugger::Init()
391{
392	status_t error = global_init();
393	if (error != B_OK)
394		return error;
395
396	return fSettingsManager.Init();
397}
398
399
400void
401Debugger::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
453void
454Debugger::ReadyToRun()
455{
456	if (fRunningTeamDebuggers == 0)
457	   PostMessage(MSG_SHOW_TEAMS_WINDOW);
458}
459
460
461void
462Debugger::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
487void
488Debugger::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
500void
501Debugger::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
521bool
522Debugger::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
538void
539Debugger::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
547TeamDebugger*
548Debugger::_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
563CliDebugger::CliDebugger()
564{
565}
566
567
568CliDebugger::~CliDebugger()
569{
570}
571
572
573bool
574CliDebugger::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
630void
631CliDebugger::TeamDebuggerStarted(TeamDebugger* debugger)
632{
633}
634
635
636void
637CliDebugger::TeamDebuggerQuit(TeamDebugger* debugger)
638{
639}
640
641
642// #pragma mark -
643
644
645int
646main(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