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