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