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