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