xref: /haiku/src/apps/debugger/Debugger.cpp (revision b289aaf66bbf6e173aa90fa194fc256965f1b34d)
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 "TypeHandlerRoster.h"
27 #include "ValueHandlerRoster.h"
28 
29 
30 extern const char* __progname;
31 const char* kProgramName = __progname;
32 
33 static const char* const kDebuggerSignature
34 	= "application/x-vnd.Haiku-Debugger";
35 
36 
37 static const char* kUsage =
38 	"Usage: %s [ <options> ]\n"
39 	"       %s [ <options> ] <command line>\n"
40 	"       %s [ <options> ] --team <team>\n"
41 	"       %s [ <options> ] --thread <thread>\n"
42 	"\n"
43 	"The first form starts the debugger displaying a requester to choose a\n"
44 	"running team to debug respectively to specify the program to run and\n"
45 	"debug.\n"
46 	"\n"
47 	"The second form runs the given command line and attaches the debugger to\n"
48 	"the new team. Unless specified otherwise the program will be stopped at\n"
49 	"the beginning of its main() function.\n"
50 	"\n"
51 	"The third and fourth forms attach the debugger to a running team. The\n"
52 	"fourth form additionally stops the specified thread.\n"
53 	"\n"
54 	"Options:\n"
55 	"  -h, --help    - Print this usage info and exit.\n"
56 ;
57 
58 
59 static void
60 print_usage_and_exit(bool error)
61 {
62     fprintf(error ? stderr : stdout, kUsage, kProgramName, kProgramName,
63     	kProgramName, kProgramName);
64     exit(error ? 1 : 0);
65 }
66 
67 
68 struct Options {
69 	int					commandLineArgc;
70 	const char* const*	commandLineArgv;
71 	team_id				team;
72 	thread_id			thread;
73 
74 	Options()
75 		:
76 		commandLineArgc(0),
77 		commandLineArgv(NULL),
78 		team(-1),
79 		thread(-1)
80 	{
81 	}
82 };
83 
84 
85 static bool
86 parse_arguments(int argc, const char* const* argv, bool noOutput,
87 	Options& options)
88 {
89 	optind = 1;
90 
91 	while (true) {
92 		static struct option sLongOptions[] = {
93 			{ "help", no_argument, 0, 'h' },
94 			{ "team", required_argument, 0, 't' },
95 			{ "thread", required_argument, 0, 'T' },
96 			{ 0, 0, 0, 0 }
97 		};
98 
99 		opterr = 0; // don't print errors
100 
101 		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
102 		if (c == -1)
103 			break;
104 
105 		switch (c) {
106 			case 'h':
107 				if (noOutput)
108 					return false;
109 				print_usage_and_exit(false);
110 				break;
111 
112 			case 't':
113 			{
114 				options.team = strtol(optarg, NULL, 0);
115 				if (options.team <= 0) {
116 					if (noOutput)
117 						return false;
118 					print_usage_and_exit(true);
119 				}
120 				break;
121 			}
122 
123 			case 'T':
124 			{
125 				options.thread = strtol(optarg, NULL, 0);
126 				if (options.thread <= 0) {
127 					if (noOutput)
128 						return false;
129 					print_usage_and_exit(true);
130 				}
131 				break;
132 			}
133 
134 			default:
135 				if (noOutput)
136 					return false;
137 				print_usage_and_exit(true);
138 				break;
139 		}
140 	}
141 
142 	if (optind < argc) {
143 		options.commandLineArgc = argc - optind;
144 		options.commandLineArgv = argv + optind;
145 	}
146 
147 	int exclusiveParams = 0;
148 	if (options.team > 0)
149 		exclusiveParams++;
150 	if (options.thread > 0)
151 		exclusiveParams++;
152 	if (options.commandLineArgc > 0)
153 		exclusiveParams++;
154 
155 	if (exclusiveParams == 0) {
156 		// TODO: Support!
157 		if (noOutput)
158 			return false;
159 		fprintf(stderr, "Sorry, running without team/thread to debug not "
160 			"supported yet.\n");
161 		exit(1);
162 	} else if (exclusiveParams != 1) {
163 		if (noOutput)
164 			return false;
165 		print_usage_and_exit(true);
166 	}
167 
168 	return true;
169 }
170 
171 
172 class Debugger : public BApplication, private TeamDebugger::Listener {
173 public:
174 	Debugger()
175 		:
176 		BApplication(kDebuggerSignature),
177 		fRunningTeamDebuggers(0)
178 	{
179 	}
180 
181 	~Debugger()
182 	{
183 		ValueHandlerRoster::DeleteDefault();
184 		TypeHandlerRoster::DeleteDefault();
185 	}
186 
187 	status_t Init()
188 	{
189 		status_t error = TypeHandlerRoster::CreateDefault();
190 		if (error != B_OK)
191 			return error;
192 
193 		error = ValueHandlerRoster::CreateDefault();
194 		if (error != B_OK)
195 			return error;
196 
197 		return fSettingsManager.Init();
198 	}
199 
200 	virtual void MessageReceived(BMessage* message)
201 	{
202 		switch (message->what) {
203 			case MSG_TEAM_DEBUGGER_QUIT:
204 			{
205 				int32 threadID;
206 				if (message->FindInt32("thread", &threadID) == B_OK)
207 					wait_for_thread(threadID, NULL);
208 
209 				if (--fRunningTeamDebuggers == 0)
210 					Quit();
211 				break;
212 			}
213 			default:
214 				BApplication::MessageReceived(message);
215 				break;
216 		}
217 	}
218 
219 	virtual void ReadyToRun()
220 	{
221 	}
222 
223 	virtual void ArgvReceived(int32 argc, char** argv)
224 	{
225 		Options options;
226 		if (!parse_arguments(argc, argv, true, options))
227 {
228 printf("Debugger::ArgvReceived(): parsing args failed!\n");
229 			return;
230 }
231 
232 		team_id team = options.team;
233 		thread_id thread = options.thread;
234 		bool stopInMain = false;
235 
236 		// If command line arguments were given, start the program.
237 		if (options.commandLineArgc > 0) {
238 printf("loading program: \"%s\" ...\n", options.commandLineArgv[0]);
239 			// TODO: What about the CWD?
240 			thread = load_program(options.commandLineArgv,
241 				options.commandLineArgc, false);
242 			if (thread < 0) {
243 				// TODO: Notify the user!
244 				fprintf(stderr, "Error: Failed to load program \"%s\": %s\n",
245 					options.commandLineArgv[0], strerror(thread));
246 				return;
247 			}
248 
249 			team = thread;
250 				// main thread ID == team ID
251 			stopInMain = true;
252 		}
253 
254 		// If we've got
255 		if (team < 0) {
256 printf("no team yet, getting thread info...\n");
257 			thread_info threadInfo;
258 			status_t error = get_thread_info(thread, &threadInfo);
259 			if (error != B_OK) {
260 				// TODO: Notify the user!
261 				fprintf(stderr, "Error: Failed to get info for thread \"%ld\": "
262 					"%s\n", thread, strerror(error));
263 				return;
264 			}
265 
266 			team = threadInfo.team;
267 		}
268 printf("team: %ld, thread: %ld\n", team, thread);
269 
270 		TeamDebugger* debugger = _TeamDebuggerForTeam(team);
271 		if (debugger != NULL) {
272 			// TODO: Activate the respective window!
273 printf("There's already a debugger for team: %ld\n", team);
274 			return;
275 		}
276 
277 		UserInterface* userInterface = new(std::nothrow) GraphicalUserInterface;
278 		if (userInterface == NULL) {
279 			// TODO: Notify the user!
280 			fprintf(stderr, "Error: Out of memory!\n");
281 		}
282 		Reference<UserInterface> userInterfaceReference(userInterface, true);
283 
284 		debugger = new(std::nothrow) TeamDebugger(this, userInterface,
285 			&fSettingsManager);
286 		if (debugger == NULL) {
287 			// TODO: Notify the user!
288 			fprintf(stderr, "Error: Out of memory!\n");
289 		}
290 
291 		status_t error = debugger->Init(team, thread, stopInMain);
292 		if (debugger->Thread())
293 			fRunningTeamDebuggers++;
294 
295 		if (error == B_OK && fTeamDebuggers.AddItem(debugger)) {
296 printf("debugger for team %ld created and initialized successfully!\n", team);
297 		} else
298 			delete debugger;
299 	}
300 
301 private:
302 	typedef BObjectList<TeamDebugger>	TeamDebuggerList;
303 
304 private:
305 	// TeamDebugger::Listener
306 	virtual void TeamDebuggerQuit(TeamDebugger* debugger)
307 	{
308 		// Note: Locking here only works, since we're never locking the other
309 		// way around. If we even need to do that, we'll have to introduce a
310 		// separate lock to protect the list.
311 		AutoLocker<Debugger> locker(this);
312 		fTeamDebuggers.RemoveItem(debugger);
313 		locker.Unlock();
314 
315 		if (debugger->Thread() >= 0) {
316 			BMessage message(MSG_TEAM_DEBUGGER_QUIT);
317 			message.AddInt32("thread", debugger->Thread());
318 			PostMessage(&message);
319 		}
320 	}
321 
322 	virtual bool QuitRequested()
323 	{
324 		// NOTE: The default implementation will just ask all windows'
325 		// QuitRequested() hooks. This in turn will ask the TeamWindows.
326 		// For now, this is what we want. If we have more windows later,
327 		// like the global TeamsWindow, then we want to just ask the
328 		// TeamDebuggers, the TeamsWindow should of course not go away already
329 		// if one or more TeamDebuggers want to stay later. There are multiple
330 		// ways how to do this. For examaple, TeamDebugger could get a
331 		// QuitReqested() hook or the TeamsWindow and other global windows
332 		// could always return false in their QuitRequested().
333 		return BApplication::QuitRequested();
334 			// TODO: This is ugly. The team debuggers own the windows, not the
335 			// other way around.
336 	}
337 
338 	virtual void Quit()
339 	{
340 		// don't quit before all team debuggers have been quit
341 		if (fRunningTeamDebuggers <= 0)
342 			BApplication::Quit();
343 	}
344 
345 	TeamDebugger* _TeamDebuggerForTeam(team_id teamID) const
346 	{
347 		for (int32 i = 0; TeamDebugger* debugger = fTeamDebuggers.ItemAt(i);
348 				i++) {
349 			if (debugger->TeamID() == teamID)
350 				return debugger;
351 		}
352 
353 		return NULL;
354 	}
355 
356 private:
357 	SettingsManager		fSettingsManager;
358 	TeamDebuggerList	fTeamDebuggers;
359 	int32				fRunningTeamDebuggers;
360 };
361 
362 
363 int
364 main(int argc, const char* const* argv)
365 {
366 	// We test-parse the arguments here, so, when we're started from the
367 	// terminal and there's an instance already running, we can print an error
368 	// message to the terminal, if something's wrong with the arguments.
369 	{
370 		Options options;
371 		parse_arguments(argc, argv, false, options);
372 	}
373 
374 	Debugger app;
375 	status_t error = app.Init();
376 	if (error != B_OK) {
377 		fprintf(stderr, "Error: Failed to init application: %s\n",
378 			strerror(error));
379 		return 1;
380 	}
381 
382 	app.Run();
383 	return 0;
384 }
385