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