xref: /haiku/src/apps/debugger/Debugger.cpp (revision 4466b89c65970de4c7236ac87faa2bee4589f413)
1 /*
2  * Copyright 2009, 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 "TeamDebugger.h"
28 #include "TeamsWindow.h"
29 #include "TypeHandlerRoster.h"
30 #include "ValueHandlerRoster.h"
31 
32 
33 extern const char* __progname;
34 const char* kProgramName = __progname;
35 
36 static const char* const kDebuggerSignature
37 	= "application/x-vnd.Haiku-Debugger";
38 
39 
40 static const char* kUsage =
41 	"Usage: %s [ <options> ]\n"
42 	"       %s [ <options> ] <command line>\n"
43 	"       %s [ <options> ] --team <team>\n"
44 	"       %s [ <options> ] --thread <thread>\n"
45 	"\n"
46 	"The first form starts the debugger displaying a requester to choose a\n"
47 	"running team to debug respectively to specify the program to run and\n"
48 	"debug.\n"
49 	"\n"
50 	"The second form runs the given command line and attaches the debugger to\n"
51 	"the new team. Unless specified otherwise the program will be stopped at\n"
52 	"the beginning of its main() function.\n"
53 	"\n"
54 	"The third and fourth forms attach the debugger to a running team. The\n"
55 	"fourth form additionally stops the specified thread.\n"
56 	"\n"
57 	"Options:\n"
58 	"  -h, --help    - Print this usage info and exit.\n"
59 	"  -c, --cli     - Use command line user interface (not yet implemented)\n"
60 ;
61 
62 
63 static void
64 print_usage_and_exit(bool error)
65 {
66     fprintf(error ? stderr : stdout, kUsage, kProgramName, kProgramName,
67     	kProgramName, kProgramName);
68     exit(error ? 1 : 0);
69 }
70 
71 
72 struct Options {
73 	int					commandLineArgc;
74 	const char* const*	commandLineArgv;
75 	team_id				team;
76 	thread_id			thread;
77 	bool				useCLI;
78 
79 	Options()
80 		:
81 		commandLineArgc(0),
82 		commandLineArgv(NULL),
83 		team(-1),
84 		thread(-1),
85 		useCLI(false)
86 	{
87 	}
88 };
89 
90 
91 static bool
92 parse_arguments(int argc, const char* const* argv, bool noOutput,
93 	Options& options)
94 {
95 	optind = 1;
96 
97 	while (true) {
98 		static struct option sLongOptions[] = {
99 			{ "help", no_argument, 0, 'h' },
100 			{ "team", required_argument, 0, 't' },
101 			{ "thread", required_argument, 0, 'T' },
102 			{ "cli", no_argument, 0, 'c' },
103 			{ 0, 0, 0, 0 }
104 		};
105 
106 		opterr = 0; // don't print errors
107 
108 		int c = getopt_long(argc, (char**)argv, "+ch", sLongOptions, NULL);
109 		if (c == -1)
110 			break;
111 
112 		switch (c) {
113 			case 'c':
114 				options.useCLI = true;
115 				break;
116 
117 			case 'h':
118 				if (noOutput)
119 					return false;
120 				print_usage_and_exit(false);
121 				break;
122 
123 			case 't':
124 			{
125 				options.team = strtol(optarg, NULL, 0);
126 				if (options.team <= 0) {
127 					if (noOutput)
128 						return false;
129 					print_usage_and_exit(true);
130 				}
131 				break;
132 			}
133 
134 			case 'T':
135 			{
136 				options.thread = strtol(optarg, NULL, 0);
137 				if (options.thread <= 0) {
138 					if (noOutput)
139 						return false;
140 					print_usage_and_exit(true);
141 				}
142 				break;
143 			}
144 
145 			default:
146 				if (noOutput)
147 					return false;
148 				print_usage_and_exit(true);
149 				break;
150 		}
151 	}
152 
153 	if (optind < argc) {
154 		options.commandLineArgc = argc - optind;
155 		options.commandLineArgv = argv + optind;
156 	}
157 
158 	int exclusiveParams = 0;
159 	if (options.team > 0)
160 		exclusiveParams++;
161 	if (options.thread > 0)
162 		exclusiveParams++;
163 	if (options.commandLineArgc > 0)
164 		exclusiveParams++;
165 
166 	if (exclusiveParams == 0) {
167 		return true;
168 	} else if (exclusiveParams != 1) {
169 		if (noOutput)
170 			return false;
171 		print_usage_and_exit(true);
172 	}
173 
174 	return true;
175 }
176 
177 
178 static TeamDebugger*
179 start_team_debugger(team_id teamID, SettingsManager* settingsManager,
180 	TeamDebugger::Listener* listener, thread_id threadID = -1,
181 	bool stopInMain = false, bool useCLI = false)
182 {
183 	if (teamID < 0)
184 		return NULL;
185 
186 	UserInterface* userInterface = useCLI
187 		? (UserInterface*)new(std::nothrow)	CommandLineUserInterface
188 		: (UserInterface*)new(std::nothrow)	GraphicalUserInterface;
189 
190 	if (userInterface == NULL) {
191 		// TODO: Notify the user!
192 		fprintf(stderr, "Error: Out of memory!\n");
193 		return NULL;
194 	}
195 	BReference<UserInterface> userInterfaceReference(userInterface, true);
196 
197 	status_t error = B_NO_MEMORY;
198 
199 	TeamDebugger* debugger = new(std::nothrow) TeamDebugger(listener,
200 		userInterface, settingsManager);
201 	if (debugger)
202 		error = debugger->Init(teamID, threadID, stopInMain);
203 
204 	if (error != B_OK) {
205 		printf("Error: debugger for team %ld failed to init: %s!\n",
206 			teamID, strerror(error));
207 		delete debugger;
208 		return NULL;
209 	} else
210 		printf("debugger for team %ld created and initialized successfully!\n",
211 			teamID);
212 
213 	return debugger;
214 }
215 
216 // #pragma mark - Debugger application class
217 
218 
219 class Debugger : public BApplication, private TeamDebugger::Listener {
220 public:
221 								Debugger();
222 								~Debugger();
223 
224 			status_t			Init();
225 	virtual void 				MessageReceived(BMessage* message);
226 	virtual void 				ReadyToRun();
227 	virtual void 				ArgvReceived(int32 argc, char** argv);
228 
229 private:
230 			typedef BObjectList<TeamDebugger>	TeamDebuggerList;
231 
232 private:
233 	// TeamDebugger::Listener
234 	virtual void 				TeamDebuggerStarted(TeamDebugger* debugger);
235 	virtual void 				TeamDebuggerQuit(TeamDebugger* debugger);
236 
237 	virtual bool 				QuitRequested();
238 	virtual void 				Quit();
239 
240 			TeamDebugger* 		_FindTeamDebugger(team_id teamID) const;
241 
242 private:
243 			SettingsManager		fSettingsManager;
244 			TeamDebuggerList	fTeamDebuggers;
245 			int32				fRunningTeamDebuggers;
246 			TeamsWindow*		fTeamsWindow;
247 };
248 
249 
250 Debugger::Debugger()
251 	:
252 	BApplication(kDebuggerSignature),
253 	fRunningTeamDebuggers(0),
254 	fTeamsWindow(NULL)
255 {
256 }
257 
258 
259 Debugger::~Debugger()
260 {
261 	ValueHandlerRoster::DeleteDefault();
262 	TypeHandlerRoster::DeleteDefault();
263 }
264 
265 
266 status_t
267 Debugger::Init()
268 {
269 	status_t error = TypeHandlerRoster::CreateDefault();
270 	if (error != B_OK)
271 		return error;
272 
273 	error = ValueHandlerRoster::CreateDefault();
274 	if (error != B_OK)
275 		return error;
276 
277 	return fSettingsManager.Init();
278 }
279 
280 
281 void
282 Debugger::MessageReceived(BMessage* message)
283 {
284 	switch (message->what) {
285 		case MSG_SHOW_TEAMS_WINDOW:
286 		{
287             if (fTeamsWindow) {
288                	fTeamsWindow->Activate(true);
289                	break;
290             }
291 
292            	try {
293 				fTeamsWindow = TeamsWindow::Create(&fSettingsManager);
294 				if (fTeamsWindow != NULL)
295 					fTeamsWindow->Show();
296            	} catch (...) {
297 				// TODO: Notify the user!
298 				fprintf(stderr, "Error: Failed to create Teams window\n");
299            	}
300 			break;
301 		}
302 		case MSG_TEAMS_WINDOW_CLOSED:
303 		{
304 			fTeamsWindow = NULL;
305 			Quit();
306 			break;
307 		}
308 		case MSG_DEBUG_THIS_TEAM:
309 		{
310 			int32 teamID;
311 			if (message->FindInt32("team", &teamID) != B_OK)
312 				break;
313 
314 			start_team_debugger(teamID, &fSettingsManager, this);
315 			break;
316 		}
317 		case MSG_TEAM_DEBUGGER_QUIT:
318 		{
319 			int32 threadID;
320 			if (message->FindInt32("thread", &threadID) == B_OK)
321 				wait_for_thread(threadID, NULL);
322 
323 			--fRunningTeamDebuggers;
324 			Quit();
325 			break;
326 		}
327 		default:
328 			BApplication::MessageReceived(message);
329 			break;
330 	}
331 }
332 
333 
334 void
335 Debugger::ReadyToRun()
336 {
337 	if (fRunningTeamDebuggers == 0)
338 	   PostMessage(MSG_SHOW_TEAMS_WINDOW);
339 }
340 
341 
342 void
343 Debugger::ArgvReceived(int32 argc, char** argv)
344 {
345 	Options options;
346 	if (!parse_arguments(argc, argv, true, options)) {
347 		printf("Debugger::ArgvReceived(): parsing args failed!\n");
348 		return;
349 	}
350 
351 	team_id team = options.team;
352 	thread_id thread = options.thread;
353 	bool stopInMain = false;
354 
355 	// If command line arguments were given, start the program.
356 	if (options.commandLineArgc > 0) {
357 		printf("loading program: \"%s\" ...\n", options.commandLineArgv[0]);
358 		// TODO: What about the CWD?
359 		thread = load_program(options.commandLineArgv,
360 			options.commandLineArgc, false);
361 		if (thread < 0) {
362 			// TODO: Notify the user!
363 			fprintf(stderr, "Error: Failed to load program \"%s\": %s\n",
364 				options.commandLineArgv[0], strerror(thread));
365 			return;
366 		}
367 
368 		team = thread;
369 			// main thread ID == team ID
370 		stopInMain = true;
371 	}
372 
373 	// no parameters given, prompt the user to attach to a team
374 	if (team < 0 && thread < 0)
375 		return;
376 
377 	// If we've got
378 	if (team < 0) {
379 		printf("no team yet, getting thread info...\n");
380 		thread_info threadInfo;
381 		status_t error = get_thread_info(thread, &threadInfo);
382 		if (error != B_OK) {
383 			// TODO: Notify the user!
384 			fprintf(stderr, "Error: Failed to get info for thread \"%ld\": "
385 				"%s\n", thread, strerror(error));
386 			return;
387 		}
388 
389 		team = threadInfo.team;
390 	}
391 	printf("team: %ld, thread: %ld\n", team, thread);
392 
393 	TeamDebugger* debugger = _FindTeamDebugger(team);
394 	if (debugger != NULL) {
395 		printf("There's already a debugger for team: %ld\n", team);
396 		debugger->Activate();
397 		return;
398 	}
399 
400 	start_team_debugger(team, &fSettingsManager, this, thread, stopInMain);
401 }
402 
403 
404 // TeamDebugger::Listener
405 
406 
407 void
408 Debugger::TeamDebuggerStarted(TeamDebugger* debugger)
409 {
410 	printf("debugger for team %ld started...\n",
411 		debugger->TeamID());
412 
413  	// Note: see TeamDebuggerQuit() note about locking
414 	AutoLocker<Debugger> locker(this);
415 	fTeamDebuggers.AddItem(debugger);
416 	fRunningTeamDebuggers++;
417 	locker.Unlock();
418 }
419 
420 
421 void
422 Debugger::TeamDebuggerQuit(TeamDebugger* debugger)
423 {
424 	// Note: Locking here only works, since we're never locking the other
425 	// way around. If we even need to do that, we'll have to introduce a
426 	// separate lock to protect the list.
427 
428 	printf("debugger for team %ld quit.\n",
429 		debugger->TeamID());
430 
431 	AutoLocker<Debugger> locker(this);
432 	fTeamDebuggers.RemoveItem(debugger);
433 	locker.Unlock();
434 
435 	if (debugger->Thread() >= 0) {
436 		BMessage message(MSG_TEAM_DEBUGGER_QUIT);
437 		message.AddInt32("thread", debugger->Thread());
438 		PostMessage(&message);
439 	}
440 }
441 
442 
443 bool
444 Debugger::QuitRequested()
445 {
446 	// NOTE: The default implementation will just ask all windows'
447 	// QuitRequested() hooks. This in turn will ask the TeamWindows.
448 	// For now, this is what we want. If we have more windows later,
449 	// like the global TeamsWindow, then we want to just ask the
450 	// TeamDebuggers, the TeamsWindow should of course not go away already
451 	// if one or more TeamDebuggers want to stay later. There are multiple
452 	// ways how to do this. For example, TeamDebugger could get a
453 	// QuitRequested() hook or the TeamsWindow and other global windows
454 	// could always return false in their QuitRequested().
455 	return BApplication::QuitRequested();
456 		// TODO: This is ugly. The team debuggers own the windows, not the
457 		// other way around.
458 }
459 
460 void
461 Debugger::Quit()
462 {
463 	// don't quit before all team debuggers have been quit
464 	if (fRunningTeamDebuggers <= 0 && fTeamsWindow == NULL)
465 		BApplication::Quit();
466 }
467 
468 
469 TeamDebugger*
470 Debugger::_FindTeamDebugger(team_id teamID) const
471 {
472 	for (int32 i = 0; TeamDebugger* debugger = fTeamDebuggers.ItemAt(i);
473 			i++) {
474 		if (debugger->TeamID() == teamID)
475 			return debugger;
476 	}
477 
478 	return NULL;
479 }
480 
481 
482 // #pragma mark -
483 
484 int
485 main(int argc, const char* const* argv)
486 {
487 	// We test-parse the arguments here, so that, when we're started from the
488 	// terminal and there's an instance already running, we can print an error
489 	// message to the terminal, if something's wrong with the arguments.
490 	// Otherwise, the arguments are reparsed in the actual application,
491 	// unless the option to use the command line interface was chosen.
492 
493 	Options options;
494 	parse_arguments(argc, argv, false, options);
495 
496 	if (options.useCLI) {
497 		// TODO: implement
498 		fprintf(stderr, "Error: Command line interface unimplemented\n");
499 		return 1;
500 	} else {
501 		Debugger app;
502 		status_t error = app.Init();
503 		if (error != B_OK) {
504 			fprintf(stderr, "Error: Failed to init application: %s\n",
505 				strerror(error));
506 			return 1;
507 		}
508 
509 		app.Run();
510 	}
511 	return 0;
512 }
513