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