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