xref: /haiku/src/apps/debugger/Debugger.cpp (revision 2cad94c1c30b6223ad8c08710b26e071d32e9979)
1 /*
2  * Copyright 2009-2016, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Copyright 2011-2016, 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 <Entry.h>
17 #include <Message.h>
18 #include <ObjectList.h>
19 #include <Path.h>
20 
21 #include <ArgumentVector.h>
22 #include <AutoDeleter.h>
23 #include <AutoLocker.h>
24 
25 #include "CommandLineUserInterface.h"
26 #include "DebuggerGlobals.h"
27 #include "DebuggerSettingsManager.h"
28 #include "DebuggerUiSettingsFactory.h"
29 #include "ElfFile.h"
30 #include "GraphicalUserInterface.h"
31 #include "MessageCodes.h"
32 #include "ReportUserInterface.h"
33 #include "SignalSet.h"
34 #include "StartTeamWindow.h"
35 #include "TargetHostInterface.h"
36 #include "TargetHostInterfaceRoster.h"
37 #include "TeamDebugger.h"
38 #include "TeamsWindow.h"
39 #include "ValueHandlerRoster.h"
40 
41 
42 extern const char* __progname;
43 const char* kProgramName = __progname;
44 
45 static const char* const kDebuggerSignature
46 	= "application/x-vnd.Haiku-Debugger";
47 
48 
49 static const char* const kUsage =
50 	"Usage: %s [ <options> ]\n"
51 	"       %s [ <options> ] <command line>\n"
52 	"       %s [ <options> ] --team <team>\n"
53 	"       %s [ <options> ] --thread <thread>\n"
54 	"       %s [ <options> ] --core <file>\n"
55 	"\n"
56 	"The first form starts the debugger displaying a requester to choose a\n"
57 	"running team to debug respectively to specify the program to run and\n"
58 	"debug.\n"
59 	"\n"
60 	"The second form runs the given command line and attaches the debugger to\n"
61 	"the new team. Unless specified otherwise the program will be stopped at\n"
62 	"the beginning of its main() function.\n"
63 	"\n"
64 	"The third and fourth forms attach the debugger to a running team. The\n"
65 	"fourth form additionally stops the specified thread.\n"
66 	"\n"
67 	"The fifth form loads a core file.\n"
68 	"\n"
69 	"Options:\n"
70 	"  -h, --help        - Print this usage info and exit.\n"
71 	"  -c, --cli         - Use command line user interface\n"
72 	"  -s, --save-report - Save crash report for the targetted team and exit.\n"
73 	"                      Implies --cli.\n"
74 ;
75 
76 
77 static void
78 print_usage_and_exit(bool error)
79 {
80     fprintf(error ? stderr : stdout, kUsage, kProgramName, kProgramName,
81     	kProgramName, kProgramName, kProgramName);
82     exit(error ? 1 : 0);
83 }
84 
85 
86 struct Options {
87 	int					commandLineArgc;
88 	const char* const*	commandLineArgv;
89 	team_id				team;
90 	thread_id			thread;
91 	bool				useCLI;
92 	bool				saveReport;
93 	const char*			reportPath;
94 	const char*			coreFilePath;
95 
96 	Options()
97 		:
98 		commandLineArgc(0),
99 		commandLineArgv(NULL),
100 		team(-1),
101 		thread(-1),
102 		useCLI(false),
103 		saveReport(false),
104 		reportPath(NULL),
105 		coreFilePath(NULL)
106 	{
107 	}
108 };
109 
110 
111 static void
112 set_debugger_options_from_options(TeamDebuggerOptions& _debuggerOptions,
113 	const Options& options)
114 {
115 	_debuggerOptions.commandLineArgc = options.commandLineArgc;
116 	_debuggerOptions.commandLineArgv = options.commandLineArgv;
117 	_debuggerOptions.team = options.team;
118 	_debuggerOptions.thread = options.thread;
119 	_debuggerOptions.coreFilePath = options.coreFilePath;
120 
121 	if (options.coreFilePath != NULL)
122 		_debuggerOptions.requestType = TEAM_DEBUGGER_REQUEST_LOAD_CORE;
123 	else if (options.commandLineArgc != 0)
124 		_debuggerOptions.requestType = TEAM_DEBUGGER_REQUEST_CREATE;
125 	else
126 		_debuggerOptions.requestType = TEAM_DEBUGGER_REQUEST_ATTACH;
127 }
128 
129 
130 
131 static bool
132 parse_arguments(int argc, const char* const* argv, bool noOutput,
133 	Options& options)
134 {
135 	optind = 1;
136 
137 	while (true) {
138 		static struct option sLongOptions[] = {
139 			{ "help", no_argument, 0, 'h' },
140 			{ "cli", no_argument, 0, 'c' },
141 			{ "save-report", optional_argument, 0, 's' },
142 			{ "team", required_argument, 0, 't' },
143 			{ "thread", required_argument, 0, 'T' },
144 			{ "core", required_argument, 0, 'C' },
145 			{ 0, 0, 0, 0 }
146 		};
147 
148 		opterr = 0; // don't print errors
149 
150 		int c = getopt_long(argc, (char**)argv, "+chs", sLongOptions, NULL);
151 		if (c == -1)
152 			break;
153 
154 		switch (c) {
155 			case 'c':
156 				options.useCLI = true;
157 				break;
158 
159 			case 'C':
160 				options.coreFilePath = optarg;
161 				break;
162 
163 			case 'h':
164 				if (noOutput)
165 					return false;
166 				print_usage_and_exit(false);
167 				break;
168 
169 			case 's':
170 			{
171 				options.saveReport = true;
172 				options.reportPath = optarg;
173 				break;
174 			}
175 
176 			case 't':
177 			{
178 				options.team = strtol(optarg, NULL, 0);
179 				if (options.team <= 0) {
180 					if (noOutput)
181 						return false;
182 					print_usage_and_exit(true);
183 				}
184 				break;
185 			}
186 
187 			case 'T':
188 			{
189 				options.thread = strtol(optarg, NULL, 0);
190 				if (options.thread <= 0) {
191 					if (noOutput)
192 						return false;
193 					print_usage_and_exit(true);
194 				}
195 				break;
196 			}
197 
198 			default:
199 				if (noOutput)
200 					return false;
201 				print_usage_and_exit(true);
202 				break;
203 		}
204 	}
205 
206 	if (optind < argc) {
207 		options.commandLineArgc = argc - optind;
208 		options.commandLineArgv = argv + optind;
209 	}
210 
211 	int exclusiveParams = 0;
212 	if (options.team > 0)
213 		exclusiveParams++;
214 	if (options.thread > 0)
215 		exclusiveParams++;
216 	if (options.commandLineArgc > 0)
217 		exclusiveParams++;
218 
219 	if (exclusiveParams == 0) {
220 		return true;
221 	} else if (exclusiveParams != 1) {
222 		if (noOutput)
223 			return false;
224 		print_usage_and_exit(true);
225 	}
226 
227 	return true;
228 }
229 
230 
231 // #pragma mark - Debugger application class
232 
233 
234 class Debugger : public BApplication,
235 	private TargetHostInterfaceRoster::Listener {
236 public:
237 								Debugger();
238 								~Debugger();
239 
240 			status_t			Init();
241 	virtual void 				MessageReceived(BMessage* message);
242 	virtual void 				ReadyToRun();
243 	virtual void 				ArgvReceived(int32 argc, char** argv);
244 	virtual	void				RefsReceived(BMessage* message);
245 
246 private:
247 	virtual bool 				QuitRequested();
248 	virtual void 				Quit();
249 
250 	// TargetHostInterfaceRoster::Listener
251 	virtual	void				TeamDebuggerCountChanged(int32 count);
252 
253 private:
254 			status_t			_StartNewTeam(TargetHostInterface* interface,
255 									const char* teamPath, const char* args);
256 			status_t			_HandleOptions(const Options& options);
257 
258 private:
259 			DebuggerSettingsManager fSettingsManager;
260 			TeamsWindow*		fTeamsWindow;
261 			StartTeamWindow*	fStartTeamWindow;
262 };
263 
264 
265 // #pragma mark - CliDebugger
266 
267 
268 class CliDebugger : private TargetHostInterfaceRoster::Listener {
269 public:
270 								CliDebugger();
271 								~CliDebugger();
272 
273 			bool				Run(const Options& options);
274 };
275 
276 
277 class ReportDebugger : private TargetHostInterfaceRoster::Listener  {
278 public:
279 								ReportDebugger();
280 								~ReportDebugger();
281 			bool				Run(const Options& options);
282 };
283 
284 
285 // #pragma mark - Debugger application class
286 
287 
288 Debugger::Debugger()
289 	:
290 	BApplication(kDebuggerSignature),
291 	TargetHostInterfaceRoster::Listener(),
292 	fTeamsWindow(NULL),
293 	fStartTeamWindow(NULL)
294 {
295 }
296 
297 
298 Debugger::~Debugger()
299 {
300 	DebuggerUiSettingsFactory::DeleteDefault();
301 	ValueHandlerRoster::DeleteDefault();
302 
303 	debugger_global_uninit();
304 }
305 
306 
307 status_t
308 Debugger::Init()
309 {
310 	status_t error = debugger_global_init(this);
311 	if (error != B_OK)
312 		return error;
313 
314 	error = DebuggerUiSettingsFactory::CreateDefault();
315 	if (error != B_OK)
316 		return error;
317 
318 	error = ValueHandlerRoster::CreateDefault();
319 	if (error != B_OK)
320 		return error;
321 
322 	return fSettingsManager.Init(DebuggerUiSettingsFactory::Default());
323 }
324 
325 
326 void
327 Debugger::MessageReceived(BMessage* message)
328 {
329 	switch (message->what) {
330 		case MSG_SHOW_TEAMS_WINDOW:
331 		{
332             if (fTeamsWindow) {
333                	fTeamsWindow->Activate(true);
334                	break;
335             }
336 
337            	try {
338 				fTeamsWindow = TeamsWindow::Create(&fSettingsManager);
339 				if (fTeamsWindow != NULL)
340 					fTeamsWindow->Show();
341            	} catch (...) {
342 				// TODO: Notify the user!
343 				fprintf(stderr, "Error: Failed to create Teams window\n");
344            	}
345 			break;
346 		}
347 		case MSG_TEAMS_WINDOW_CLOSED:
348 		{
349 			fTeamsWindow = NULL;
350 			Quit();
351 			break;
352 		}
353 		case MSG_SHOW_START_TEAM_WINDOW:
354 		{
355 			TargetHostInterface* hostInterface;
356 			if (message->FindPointer("interface",
357 					reinterpret_cast<void**>(&hostInterface)) != B_OK) {
358 				// if an interface isn't explicitly supplied, fall back to
359 				// the default local interface.
360 				hostInterface = TargetHostInterfaceRoster::Default()
361 					->ActiveInterfaceAt(0);
362 			}
363 
364 			BMessenger messenger(fStartTeamWindow);
365 			if (!messenger.IsValid()) {
366 				fStartTeamWindow = StartTeamWindow::Create(hostInterface);
367 				if (fStartTeamWindow == NULL)
368 					break;
369 				fStartTeamWindow->Show();
370 			} else
371 				fStartTeamWindow->Activate();
372 			break;
373 		}
374 		case MSG_START_TEAM_WINDOW_CLOSED:
375 		{
376 			fStartTeamWindow = NULL;
377 			break;
378 		}
379 		case MSG_DEBUG_THIS_TEAM:
380 		{
381 			team_id teamID;
382 			if (message->FindInt32("team", &teamID) != B_OK)
383 				break;
384 
385 			TargetHostInterface* interface;
386 			if (message->FindPointer("interface", reinterpret_cast<void**>(
387 					&interface)) != B_OK) {
388 				break;
389 			}
390 
391 			TeamDebuggerOptions options;
392 			options.requestType = TEAM_DEBUGGER_REQUEST_ATTACH;
393 			options.settingsManager = &fSettingsManager;
394 			options.team = teamID;
395 			options.userInterface = new(std::nothrow) GraphicalUserInterface;
396 			if (options.userInterface == NULL) {
397 				// TODO: notify user.
398 				break;
399 			}
400 			BReference<UserInterface> uiReference(options.userInterface, true);
401 			status_t error = interface->StartTeamDebugger(options);
402 			if (error != B_OK) {
403 				// TODO: notify user.
404 			} else
405 				uiReference.Detach();
406 			break;
407 		}
408 		case MSG_START_NEW_TEAM:
409 		{
410 			TargetHostInterface* interface;
411 			if (message->FindPointer("interface", reinterpret_cast<void**>(
412 					&interface)) != B_OK) {
413 				break;
414 			}
415 
416 			const char* teamPath = NULL;
417 			const char* args = NULL;
418 
419 			message->FindString("path", &teamPath);
420 			message->FindString("arguments", &args);
421 
422 			status_t result = _StartNewTeam(interface, teamPath, args);
423 			BMessage reply;
424 			reply.AddInt32("status", result);
425 			message->SendReply(&reply);
426 			break;
427 		}
428 		case MSG_LOAD_CORE_TEAM:
429 		{
430 			TargetHostInterface* interface;
431 			if (message->FindPointer("interface", reinterpret_cast<void**>(
432 					&interface)) != B_OK) {
433 				break;
434 			}
435 
436 			entry_ref ref;
437 			if (message->FindRef("core", &ref) != B_OK)
438 				break;
439 
440 			BPath path(&ref);
441 			if (path.InitCheck() != B_OK)
442 				break;
443 
444 			Options options;
445 			options.coreFilePath = path.Path();
446 			_HandleOptions(options);
447 			break;
448 		}
449 		default:
450 			BApplication::MessageReceived(message);
451 			break;
452 	}
453 }
454 
455 
456 void
457 Debugger::ReadyToRun()
458 {
459 	TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default();
460 	AutoLocker<TargetHostInterfaceRoster> lock(roster);
461 	if (roster->CountRunningTeamDebuggers() == 0)
462 		PostMessage(MSG_SHOW_TEAMS_WINDOW);
463 }
464 
465 
466 void
467 Debugger::ArgvReceived(int32 argc, char** argv)
468 {
469 	Options options;
470 	if (!parse_arguments(argc, argv, true, options)) {
471 		printf("Debugger::ArgvReceived(): parsing args failed!\n");
472 		return;
473 	}
474 
475 	_HandleOptions(options);
476 }
477 
478 
479 void
480 Debugger::RefsReceived(BMessage* message)
481 {
482 	// iterate through the refs and handle the files we can handle
483 	entry_ref ref;
484 	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
485 		BPath path;
486 		if (path.SetTo(&ref) != B_OK)
487 			continue;
488 
489 		// check, whether this is a core file
490 		{
491 			ElfFile elfFile;
492 			if (elfFile.Init(path.Path()) != B_OK || elfFile.Type() != ET_CORE)
493 				continue;
494 		}
495 
496 		// handle the core file
497 		Options options;
498 		options.coreFilePath = path.Path();
499 		_HandleOptions(options);
500 	}
501 }
502 
503 
504 bool
505 Debugger::QuitRequested()
506 {
507 	// NOTE: The default implementation will just ask all windows'
508 	// QuitRequested() hooks. This in turn will ask the TeamWindows.
509 	// For now, this is what we want. If we have more windows later,
510 	// like the global TeamsWindow, then we want to just ask the
511 	// TeamDebuggers, the TeamsWindow should of course not go away already
512 	// if one or more TeamDebuggers want to stay later. There are multiple
513 	// ways how to do this. For example, TeamDebugger could get a
514 	// QuitRequested() hook or the TeamsWindow and other global windows
515 	// could always return false in their QuitRequested().
516 	return BApplication::QuitRequested();
517 		// TODO: This is ugly. The team debuggers own the windows, not the
518 		// other way around.
519 }
520 
521 void
522 Debugger::Quit()
523 {
524 	TargetHostInterfaceRoster* roster = TargetHostInterfaceRoster::Default();
525 	AutoLocker<TargetHostInterfaceRoster> lock(roster);
526 	// don't quit before all team debuggers have been quit
527 	if (roster->CountRunningTeamDebuggers() == 0 && fTeamsWindow == NULL)
528 		BApplication::Quit();
529 }
530 
531 
532 void
533 Debugger::TeamDebuggerCountChanged(int32 count)
534 {
535 	if (count == 0) {
536 		AutoLocker<Debugger> lock(this);
537 		Quit();
538 	}
539 }
540 
541 
542 status_t
543 Debugger::_StartNewTeam(TargetHostInterface* interface, const char* path,
544 	const char* args)
545 {
546 	if (path == NULL)
547 		return B_BAD_VALUE;
548 
549 	BString data;
550 	data.SetToFormat("\"%s\" %s", path, args);
551 	if (data.Length() == 0)
552 		return B_NO_MEMORY;
553 
554 	ArgumentVector argVector;
555 	argVector.Parse(data.String());
556 
557 	TeamDebuggerOptions options;
558 	options.requestType = TEAM_DEBUGGER_REQUEST_CREATE;
559 	options.settingsManager = &fSettingsManager;
560 	options.userInterface = new(std::nothrow) GraphicalUserInterface;
561 	if (options.userInterface == NULL)
562 		return B_NO_MEMORY;
563 	BReference<UserInterface> uiReference(options.userInterface, true);
564 	options.commandLineArgc = argVector.ArgumentCount();
565 	if (options.commandLineArgc <= 0)
566 		return B_BAD_VALUE;
567 
568 	char** argv = argVector.DetachArguments();
569 
570 	options.commandLineArgv = argv;
571 	MemoryDeleter deleter(argv);
572 
573 	status_t error = interface->StartTeamDebugger(options);
574 	if (error == B_OK) {
575 		deleter.Detach();
576 		uiReference.Detach();
577 	}
578 
579 	return error;
580 }
581 
582 
583 status_t
584 Debugger::_HandleOptions(const Options& options)
585 {
586 	TeamDebuggerOptions debuggerOptions;
587 	set_debugger_options_from_options(debuggerOptions, options);
588 	debuggerOptions.settingsManager = &fSettingsManager;
589 	debuggerOptions.userInterface = new(std::nothrow) GraphicalUserInterface;
590 	if (debuggerOptions.userInterface == NULL)
591 		return B_NO_MEMORY;
592 	BReference<UserInterface> uiReference(debuggerOptions.userInterface, true);
593 	TargetHostInterface* hostInterface
594 		= TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0);
595 	status_t error = hostInterface->StartTeamDebugger(debuggerOptions);
596 	if (error == B_OK)
597 		uiReference.Detach();
598 
599 	return error;
600 }
601 
602 
603 // #pragma mark - CliDebugger
604 
605 
606 CliDebugger::CliDebugger()
607 {
608 }
609 
610 
611 CliDebugger::~CliDebugger()
612 {
613 	DebuggerUiSettingsFactory::DeleteDefault();
614 	debugger_global_uninit();
615 }
616 
617 
618 bool
619 CliDebugger::Run(const Options& options)
620 {
621 	// Block SIGINT, in this thread so all threads created by it inherit the
622 	// a block mask with the signal blocked. In the input loop the signal will
623 	// be unblocked again.
624 	SignalSet(SIGINT).BlockInCurrentThread();
625 
626 	// initialize global objects and settings manager
627 	status_t error = debugger_global_init(this);
628 	if (error != B_OK) {
629 		fprintf(stderr, "Error: Global initialization failed: %s\n",
630 			strerror(error));
631 		return false;
632 	}
633 
634 	error = DebuggerUiSettingsFactory::CreateDefault();
635 	if (error != B_OK) {
636 		fprintf(stderr, "Error: Failed to create default settings factory: "
637 			"%s\n",	strerror(error));
638 		return false;
639 	}
640 
641 
642 	DebuggerSettingsManager settingsManager;
643 	error = settingsManager.Init(DebuggerUiSettingsFactory::Default());
644 	if (error != B_OK) {
645 		fprintf(stderr, "Error: Settings manager initialization failed: "
646 			"%s\n", strerror(error));
647 		return false;
648 	}
649 
650 	// create the command line UI
651 	CommandLineUserInterface* userInterface
652 		= new(std::nothrow) CommandLineUserInterface();
653 	if (userInterface == NULL) {
654 		fprintf(stderr, "Error: Out of memory!\n");
655 		return false;
656 	}
657 	BReference<UserInterface> userInterfaceReference(userInterface, true);
658 
659 	// TODO: once we support specifying a remote interface via command line
660 	// args, this needs to be adjusted. For now, always assume actions
661 	// are being taken via the local interface.
662 	TargetHostInterface* hostInterface
663 		= TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0);
664 
665 	TeamDebuggerOptions debuggerOptions;
666 	set_debugger_options_from_options(debuggerOptions, options);
667 	debuggerOptions.userInterface = userInterface;
668 	debuggerOptions.settingsManager = &settingsManager;
669 	error = hostInterface->StartTeamDebugger(debuggerOptions);
670 	if (error != B_OK)
671 		return false;
672 
673 	TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0);
674 	thread_id teamDebuggerThread = teamDebugger->Thread();
675 
676 	// run the input loop
677 	userInterface->Run();
678 
679 	// wait for the team debugger thread to terminate
680 	wait_for_thread(teamDebuggerThread, NULL);
681 
682 	return true;
683 }
684 
685 
686 // #pragma mark - ReportDebugger
687 
688 
689 ReportDebugger::ReportDebugger()
690 {
691 }
692 
693 
694 ReportDebugger::~ReportDebugger()
695 {
696 	debugger_global_uninit();
697 }
698 
699 
700 bool
701 ReportDebugger::Run(const Options& options)
702 {
703 	// initialize global objects and settings manager
704 	status_t error = debugger_global_init(this);
705 	if (error != B_OK) {
706 		fprintf(stderr, "Error: Global initialization failed: %s\n",
707 			strerror(error));
708 		return false;
709 	}
710 
711 	// create the report UI
712 	ReportUserInterface* userInterface
713 		= new(std::nothrow) ReportUserInterface(options.thread, options.reportPath);
714 	if (userInterface == NULL) {
715 		fprintf(stderr, "Error: Out of memory!\n");
716 		return false;
717 	}
718 	BReference<UserInterface> userInterfaceReference(userInterface, true);
719 
720 	TargetHostInterface* hostInterface
721 		= TargetHostInterfaceRoster::Default()->ActiveInterfaceAt(0);
722 
723 	TeamDebuggerOptions debuggerOptions;
724 	set_debugger_options_from_options(debuggerOptions, options);
725 	debuggerOptions.userInterface = userInterface;
726 	error = hostInterface->StartTeamDebugger(debuggerOptions);
727 	if (error != B_OK)
728 		return false;
729 
730 	TeamDebugger* teamDebugger = hostInterface->TeamDebuggerAt(0);
731 	thread_id teamDebuggerThread = teamDebugger->Thread();
732 
733 	// run the input loop
734 	userInterface->Run();
735 
736 	// wait for the team debugger thread to terminate
737 	wait_for_thread(teamDebuggerThread, NULL);
738 
739 	return true;
740 }
741 
742 
743 // #pragma mark -
744 
745 
746 int
747 main(int argc, const char* const* argv)
748 {
749 	// We test-parse the arguments here, so that, when we're started from the
750 	// terminal and there's an instance already running, we can print an error
751 	// message to the terminal, if something's wrong with the arguments.
752 	// Otherwise, the arguments are reparsed in the actual application,
753 	// unless the option to use the command line interface was chosen.
754 
755 	Options options;
756 	parse_arguments(argc, argv, false, options);
757 
758 	if (options.useCLI) {
759 		CliDebugger debugger;
760 		return debugger.Run(options) ? 0 : 1;
761 	} else if (options.saveReport) {
762 		ReportDebugger debugger;
763 		return debugger.Run(options) ? 0 : 1;
764 	}
765 
766 	Debugger app;
767 	status_t error = app.Init();
768 	if (error != B_OK) {
769 		fprintf(stderr, "Error: Failed to init application: %s\n",
770 			strerror(error));
771 		return 1;
772 	}
773 
774 	app.Run();
775 
776 	return 0;
777 }
778