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