/[svn]/linuxsampler/trunk/src/common/stacktrace.c
ViewVC logotype

Annotation of /linuxsampler/trunk/src/common/stacktrace.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 270 - (hide annotations) (download)
Fri Oct 8 20:30:25 2004 UTC (19 years, 6 months ago) by schoenebeck
File MIME type: text/plain
File size: 18152 byte(s)
stacktrace routines by Bjorn Reese, only slightly modified:
- header file adjusted to work with C++ applications
- deactivated separated piping of debugger output, as it didn't work out
  correctly in multithreaded application

1 schoenebeck 270 /*************************************************************************
2     *
3     * $Id: stacktrace.c,v 1.1 2004-10-08 20:30:25 schoenebeck Exp $
4     *
5     * Copyright (c) 1998 by Bjorn Reese <breese@mail1.stofanet.dk>
6     *
7     * Permission to use, copy, modify, and distribute this software for any
8     * purpose with or without fee is hereby granted, provided that the above
9     * copyright notice and this permission notice appear in all copies.
10     *
11     * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
12     * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
13     * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND
14     * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER.
15     *
16     ************************************************************************
17     *
18     * 1999/08/19 - breese
19     * - Cleaned up the interface
20     *
21     * 1999/08/06 - breese
22     * - Added U_STACK_TRACE for HP/UX
23     *
24     * 1999/01/24 - breese
25     * - Added GCC_DumpStack
26     *
27     * 1998/12/21 - breese
28     * - Fixed include files and arguments for waitpid()
29     * - Made an AIX workaround for waitpid() exiting with EINTR
30     * - Added a missing 'quit' command for OSF dbx
31     *
32     ************************************************************************/
33    
34     #if defined(unix) || defined(__unix) || defined(__xlC__)
35     # define PLATFORM_UNIX
36     #elif defined(WIN32) || defined(_WIN32)
37     # define PLATFORM_WIN32
38     #endif
39    
40     #if defined(_AIX) || defined(__xlC__)
41     # define PLATFORM_AIX
42     #elif defined(__FreeBSD__)
43     # define PLATFORM_FREEBSD
44     #elif defined(hpux) || defined(__hpux) || defined(_HPUX_SOURCE)
45     # define PLATFORM_HPUX
46     #elif defined(sgi) || defined(mips) || defined(_SGI_SOURCE)
47     # define PLATFORM_IRIX
48     #elif defined(__osf__)
49     # define PLATFORM_OSF
50     #elif defined(M_I386) || defined(_SCO_DS) || defined(_SCO_C_DIALECT)
51     # define PLATFORM_SCO
52     #elif defined(sun) || defined(__sun__) || defined(__SUNPRO_C)
53     # if defined(__SVR4) || defined(__svr4__)
54     # define PLATFORM_SOLARIS
55     # endif
56     #endif
57    
58     /* ANSI C includes */
59     #include <stdio.h>
60     #include <stdlib.h>
61     #include <stdarg.h>
62     #include <string.h>
63     #include <limits.h>
64     #include <errno.h>
65     #include <signal.h>
66     #if defined(PLATFORM_UNIX)
67     # include <unistd.h>
68     # include <sys/types.h>
69     # include <sys/wait.h>
70     # if defined(PLATFORM_IRIX) && defined(USE_BUILTIN)
71     /* Compile with -DUSE_BUILTIN and -lexc */
72     # include <libexc.h>
73     # elif defined(PLATFORM_HPUX) && defined(USE_BUILTIN)
74     /* Compile with -DUSE_BUILTIN and -lcl */
75     extern void U_STACK_TRACE(void);
76     # endif
77     #endif
78    
79     #include "stacktrace.h"
80    
81     #ifndef FALSE
82     # define FALSE (0 == 1)
83     # define TRUE (! FALSE)
84     #endif
85    
86     #define SYS_ERROR -1
87    
88     #ifndef EXIT_SUCCESS
89     # define EXIT_SUCCESS 0
90     #endif
91     #ifndef EXIT_FAILURE
92     # define EXIT_FAILURE 1
93     #endif
94    
95     #define MAX_BUFFER_SIZE 512
96     #if defined(__GNUC__)
97     /* Change the code if ADDRESSLIST_SIZE is increased */
98     # define ADDRESSLIST_SIZE 20
99     #endif
100    
101     /*************************************************************************
102     * Globals
103     *
104     * We cannot pass custom arguments to signal handlers so we store
105     * them as global variables (but limit their scope to this file.)
106     */
107     static const char *global_progname;
108     static int global_output = STDOUT_FILENO;
109    
110    
111     #if defined(PLATFORM_UNIX)
112     /*************************************************************************
113     * my_pclose [private]
114     */
115     static void my_pclose(int fd, int pid)
116     {
117     close(fd);
118     /* Make sure the the child process has terminated */
119     (void)kill(pid, SIGTERM);
120     }
121    
122     /*************************************************************************
123     * my_popen [private]
124     */
125     static int my_popen(const char *command, pid_t *pid)
126     {
127     int rc;
128     //int pipefd[2];
129    
130     //FIXME: deactivated separate piping of debugger output, as it caused problems in conjunction with threads
131     //rc = pipe(pipefd);
132     rc = SYS_ERROR;
133     //if (SYS_ERROR != rc)
134     {
135     *pid = fork();
136     switch (*pid)
137     {
138     case SYS_ERROR:
139     //rc = SYS_ERROR;
140     //close(pipefd[0]);
141     //close(pipefd[1]);
142     break;
143    
144     case 0: /* Child */
145     //close(pipefd[0]);
146     //close(STDOUT_FILENO);
147     //close(STDERR_FILENO);
148     //dup2(pipefd[1], STDOUT_FILENO);
149     //dup2(pipefd[1], STDERR_FILENO);
150     /*
151     * The System() call assumes that /bin/sh is
152     * always available, and so will we.
153     */
154     execl("/bin/sh", "/bin/sh", "-c", command, NULL);
155     _exit(EXIT_FAILURE);
156     break;
157    
158     default: /* Parent */
159     //close(pipefd[1]);
160     //rc = pipefd[0];
161     break;
162     } /* switch */
163     }
164     return rc;
165     }
166    
167     /*************************************************************************
168     * my_getline [private]
169     */
170     static int my_getline(int fd, char *buffer, int max)
171     {
172     char c;
173     int i = 0;
174    
175     do {
176     if (read(fd, &c, 1) < 1)
177     return 0;
178     if (i < max)
179     buffer[i++] = c;
180     } while (c != '\n');
181     buffer[i] = (char)0;
182     return i;
183     }
184    
185     /*************************************************************************
186     * GCC_DumpStack [private]
187     *
188     *
189     * This code is still experimental.
190     *
191     * Stackbased arrays are used to prevent allocating from the heap.
192     * Warning: sprintf/sscanf are not ASync safe. They were used for
193     * convenience.
194     *
195     * 'nm' is used because it is most widespread
196     * GNU: nm [-B]
197     * Solaris: nm -x -p
198     * IRIX: nm -x -B (but __builtin_return_address() always returns NULL)
199     * AIX: nm -x -B
200     * OSF/1: nm -B
201     * SCO/OpenServer: nm -x -p
202     * HP/UX nm -x -p
203     */
204    
205     #if defined(__GNUC__) && defined(USE_BUILTIN)
206    
207     typedef struct {
208     unsigned long realAddress;
209     unsigned long closestAddress;
210     char name[MAX_BUFFER_SIZE + 1];
211     char type;
212     } address_T;
213    
214    
215     static void GCC_DumpStack(void)
216     {
217     int i;
218     void *p = &p; /* dummy start value */
219     address_T syms[ADDRESSLIST_SIZE + 1];
220     char buffer[MAX_BUFFER_SIZE];
221     int fd;
222     pid_t pid;
223     unsigned long addr;
224     unsigned long highestAddress;
225     unsigned long lowestAddress;
226     char type;
227     char *pname;
228     char name[MAX_BUFFER_SIZE];
229     int number;
230    
231     for (i = 0; p; i++)
232     {
233     /*
234     * This is based on code by Steve Coleman <steve.colemanjhuapl.edu>
235     *
236     * __builtin_return_address() only accepts a constant as argument.
237     */
238     switch (i)
239     {
240     case 0:
241     p = __builtin_return_address(0);
242     break;
243     case 1:
244     p = __builtin_return_address(1);
245     break;
246     case 2:
247     p = __builtin_return_address(2);
248     break;
249     case 3:
250     p = __builtin_return_address(3);
251     break;
252     case 4:
253     p = __builtin_return_address(4);
254     break;
255     case 5:
256     p = __builtin_return_address(5);
257     break;
258     case 6:
259     p = __builtin_return_address(6);
260     break;
261     case 7:
262     p = __builtin_return_address(7);
263     break;
264     case 8:
265     p = __builtin_return_address(8);
266     break;
267     case 9:
268     p = __builtin_return_address(9);
269     break;
270     case 10:
271     p = __builtin_return_address(10);
272     break;
273     case 11:
274     p = __builtin_return_address(11);
275     break;
276     case 12:
277     p = __builtin_return_address(12);
278     break;
279     case 13:
280     p = __builtin_return_address(13);
281     break;
282     case 14:
283     p = __builtin_return_address(14);
284     break;
285     case 15:
286     p = __builtin_return_address(15);
287     break;
288     case 16:
289     p = __builtin_return_address(16);
290     break;
291     case 17:
292     p = __builtin_return_address(17);
293     break;
294     case 18:
295     p = __builtin_return_address(18);
296     break;
297     case 19:
298     p = __builtin_return_address(19);
299     break;
300     default:
301     /* Change ADDRESSLIST_SIZE if more are added */
302     p = NULL;
303     break;
304     }
305     if ((p) && (i < ADDRESSLIST_SIZE))
306     {
307     syms[i].realAddress = (unsigned long)p;
308     syms[i].closestAddress = 0;
309     syms[i].name[0] = (char)0;
310     syms[i].type = ' ';
311     }
312     else
313     {
314     syms[i].realAddress = 0;
315     break; /* for */
316     }
317     } /* for */
318    
319    
320     /* First find out if we are using GNU or vendor nm */
321     number = 0;
322     strcpy(buffer, "nm -V 2>/dev/null | grep GNU | wc -l");
323     fd = my_popen(buffer, &pid);
324     if (SYS_ERROR != fd)
325     {
326     if (my_getline(fd, buffer, sizeof(buffer)))
327     {
328     sscanf(buffer, "%d", &number);
329     }
330     my_pclose(fd, pid);
331     }
332     if (number == 0) /* vendor nm */
333     {
334     # if defined(PLATFORM_SOLARIS) || defined(PLATFORM_SCO) || defined(PLATFORM_HPUX)
335     strcpy(buffer, "nm -x -p ");
336     # elif defined(PLATFORM_AIX) || defined(PLATFORM_IRIX) || defined(PLATFORM_OSF)
337     strcpy(buffer, "nm -x -B ");
338     # else
339     strcpy(buffer, "nm -B ");
340     # endif
341     }
342     else /* GNU nm */
343     strcpy(buffer, "nm -B ");
344     strcat(buffer, global_progname);
345    
346     lowestAddress = ULONG_MAX;
347     highestAddress = 0;
348     fd = my_popen(buffer, &pid);
349     if (SYS_ERROR != fd)
350     {
351     while (my_getline(fd, buffer, sizeof(buffer)))
352     {
353     if (buffer[0] == '\n')
354     continue;
355     if (3 == sscanf(buffer, "%lx %c %s", &addr, &type, name))
356     {
357     if ((type == 't') || type == 'T')
358     {
359     if (addr == 0)
360     continue; /* while */
361     if (addr < lowestAddress)
362     lowestAddress = addr;
363     if (addr > highestAddress)
364     highestAddress = addr;
365     for (i = 0; syms[i].realAddress != 0; i++)
366     {
367     if ((addr <= syms[i].realAddress) &&
368     (addr > syms[i].closestAddress))
369     {
370     syms[i].closestAddress = addr;
371     strncpy(syms[i].name, name, MAX_BUFFER_SIZE);
372     syms[i].name[MAX_BUFFER_SIZE] = (char)0;
373     syms[i].type = type;
374     }
375     }
376     }
377     }
378     }
379     my_pclose(fd, pid);
380    
381     for (i = 0; syms[i].realAddress != 0; i++)
382     {
383     if ((syms[i].name[0] == (char)0) ||
384     (syms[i].realAddress <= lowestAddress) ||
385     (syms[i].realAddress >= highestAddress))
386     {
387     sprintf(buffer, "[%d] 0x%08lx ???\n", i, syms[i].realAddress);
388     }
389     else
390     {
391     sprintf(buffer, "[%d] 0x%08lx <%s + 0x%lx> %c\n",
392     i,
393     syms[i].realAddress,
394     syms[i].name,
395     syms[i].realAddress - syms[i].closestAddress,
396     syms[i].type);
397     }
398     write(global_output, buffer, strlen(buffer));
399     }
400     }
401     }
402     #endif
403    
404     /*************************************************************************
405     * DumpStack [private]
406     */
407     static int DumpStack(char *format, ...)
408     {
409     int gotSomething = FALSE;
410     int fd;
411     pid_t pid;
412     int status = EXIT_FAILURE;
413     int rc;
414     va_list args;
415     char *buffer;
416     char cmd[MAX_BUFFER_SIZE];
417     char buf[MAX_BUFFER_SIZE];
418    
419     /*
420     * Please note that vsprintf() is not ASync safe (ie. cannot safely
421     * be used from a signal handler.) If this proves to be a problem
422     * then the cmd string can be built by more basic functions such as
423     * strcpy, strcat, and a homemade integer-to-ascii function.
424     */
425     va_start(args, format);
426     vsprintf(cmd, format, args);
427     va_end(args);
428    
429     fd = my_popen(cmd, &pid);
430     if (SYS_ERROR != fd)
431     {
432     /*
433     * Wait for the child to exit. This must be done
434     * to make the debugger attach successfully.
435     * The output from the debugger is buffered on
436     * the pipe.
437     *
438     * AIX needs the looping hack
439     */
440     do
441     {
442     rc = waitpid(pid, &status, 0);
443     }
444     while ((SYS_ERROR == rc) && (EINTR == errno));
445    
446     if ((WIFEXITED(status)) && (WEXITSTATUS(status) == EXIT_SUCCESS))
447     {
448     while (my_getline(fd, buf, sizeof(buf)))
449     {
450     buffer = buf;
451     if (! gotSomething)
452     {
453     write(global_output, "Output from ",
454     strlen("Output from "));
455     strtok(cmd, " ");
456     write(global_output, cmd, strlen(cmd));
457     write(global_output, "\n", strlen("\n"));
458     gotSomething = TRUE;
459     }
460     if ('\n' == buf[strlen(buf)-1])
461     {
462     buf[strlen(buf)-1] = (char)0;
463     }
464     write(global_output, buffer, strlen(buffer));
465     write(global_output, "\n", strlen("\n"));
466     }
467     }
468     my_pclose(fd, pid);
469     }
470     return gotSomething;
471     }
472     #endif /* PLATFORM_UNIX */
473    
474     /*************************************************************************
475     * StackTrace
476     */
477     void StackTrace(void)
478     {
479     #if defined(PLATFORM_UNIX)
480     /*
481     * In general dbx seems to do a better job than gdb.
482     *
483     * Different dbx implementations require different flags/commands.
484     */
485    
486     # if defined(PLATFORM_AIX)
487    
488     if (DumpStack("dbx -a %d 2>/dev/null <<EOF\n"
489     "where\n"
490     "detach\n"
491     "EOF\n",
492     (int)getpid()))
493     return;
494    
495     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
496     "set prompt\n"
497     "where\n"
498     "detach\n"
499     "quit\n"
500     "EOF\n",
501     global_progname, (int)getpid()))
502     return;
503    
504     # elif defined(PLATFORM_FREEBSD)
505    
506     /*
507     * FreeBSD insists on sending a SIGSTOP to the process we
508     * attach to, so we let the debugger send a SIGCONT to that
509     * process after we have detached.
510     */
511     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
512     "set prompt\n"
513     "where\n"
514     "detach\n"
515     "shell kill -CONT %d\n"
516     "quit\n"
517     "EOF\n",
518     global_progname, (int)getpid(), (int)getpid()))
519     return;
520    
521     # elif defined(PLATFORM_HPUX)
522    
523     /*
524     * HP decided to call their debugger xdb.
525     *
526     * This does not seem to work properly yet. The debugger says
527     * "Note: Stack traces may not be possible until you are
528     * stopped in user code." on HP-UX 09.01
529     *
530     * -L = line-oriented interface.
531     * "T [depth]" gives a stacktrace with local variables.
532     * The final "y" is confirmation to the quit command.
533     */
534    
535     if (DumpStack("xdb -P %d -L %s 2>&1 <<EOF\n"
536     "T 50\n"
537     "q\ny\n"
538     "EOF\n",
539     (int)getpid(), global_progname))
540     return;
541    
542     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
543     "set prompt\n"
544     "where\n"
545     "detach\n"
546     "quit\n"
547     "EOF\n",
548     global_progname, (int)getpid()))
549     return;
550    
551     # if defined(PLATFORM_HPUX) && defined(USE_BUILTIN)
552     U_STACK_TRACE();
553     return;
554     # endif
555    
556     # elif defined(PLATFORM_IRIX)
557    
558     /*
559     * "set $page=0" drops hold mode
560     * "dump ." displays the contents of the variables
561     */
562     if (DumpStack("dbx -p %d 2>/dev/null <<EOF\n"
563     "set \\$page=0\n"
564     "where\n"
565     # if !defined(__GNUC__)
566     /* gcc does not generate this information */
567     "dump .\n"
568     # endif
569     "detach\n"
570     "EOF\n",
571     (int)getpid()))
572     return;
573    
574     # if defined(USE_BUILTIN)
575     if (trace_back_stack_and_print())
576     return;
577     # endif
578    
579     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
580     "set prompt\n"
581     "echo --- Stacktrace\\n\n"
582     "where\n"
583     "echo --- Symbols\\n\n"
584     "frame 5\n" /* Skip signal handler frames */
585     "set \\$x = 50\n"
586     "while (\\$x)\n" /* Print local variables for each frame */
587     "info locals\n"
588     "up\n"
589     "set \\$x--\n"
590     "end\n"
591     "echo ---\\n\n"
592     "detach\n"
593     "quit\n"
594     "EOF\n",
595     global_progname, (int)getpid()))
596     return;
597    
598     # elif defined(PLATFORM_OSF)
599    
600     if (DumpStack("dbx -pid %d %s 2>/dev/null <<EOF\n"
601     "where\n"
602     "detach\n"
603     "quit\n"
604     "EOF\n",
605     (int)getpid(), global_progname))
606     return;
607    
608     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
609     "set prompt\n"
610     "where\n"
611     "detach\n"
612     "quit\n"
613     "EOF\n",
614     global_progname, (int)getpid()))
615     return;
616    
617     # elif defined(PLATFORM_SCO)
618    
619     /*
620     * SCO OpenServer dbx is like a catch-22. The 'detach' command
621     * depends on whether ptrace(S) support detaching or not. If it
622     * is supported then 'detach' must be used, otherwise the process
623     * will be killed upon dbx exit. If it isn't supported then 'detach'
624     * will cause the process to be killed. We do not want it to be
625     * killed.
626     *
627     * Out of two evils, the omission of 'detach' was chosen because
628     * it worked on our system.
629     */
630     if (DumpStack("dbx %s %d 2>/dev/null <<EOF\n"
631     "where\n"
632     "quit\nEOF\n",
633     global_progname, (int)getpid()))
634     return;
635    
636     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
637     "set prompt\n"
638     "where\n"
639     "detach\n"
640     "quit\n"
641     "EOF\n",
642     global_progname, (int)getpid()))
643     return;
644    
645     # elif defined(PLATFORM_SOLARIS)
646    
647     if (DumpStack("dbx %s %d 2>/dev/null <<EOF\n"
648     "where\n"
649     "detach\n"
650     "EOF\n",
651     global_progname, (int)getpid()))
652     return;
653    
654     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
655     "set prompt\n"
656     "echo --- Stacktrace\\n\n"
657     "where\n"
658     "echo --- Symbols\\n\n"
659     "frame 5\n" /* Skip signal handler frames */
660     "set \\$x = 50\n"
661     "while (\\$x)\n" /* Print local variables for each frame */
662     "info locals\n"
663     "up\n"
664     "set \\$x--\n"
665     "end\n"
666     "echo ---\\n\n"
667     "detach\n"
668     "quit\n"
669     "EOF\n",
670     global_progname, (int)getpid()))
671     return;
672    
673     if (DumpStack("/usr/proc/bin/pstack %d",
674     (int)getpid()))
675     return;
676    
677     /*
678     * Other Unices (AIX, HPUX, SCO) also have adb, but
679     * they seem unable to attach to a running process.
680     */
681     if (DumpStack("adb %s 2>&1 <<EOF\n"
682     "0t%d:A\n" /* Attach to pid */
683     "\\$c\n" /* print stacktrace */
684     ":R\n" /* Detach */
685     "\\$q\n" /* Quit */
686     "EOF\n",
687     global_progname, (int)getpid()))
688     return;
689    
690     # else /* All other Unix platforms */
691    
692     /*
693     * TODO: SCO/UnixWare 7 must be something like (not tested)
694     * debug -i c <pid> <<EOF\nstack -f 4\nquit\nEOF\n
695     */
696    
697     # if !defined(__GNUC__)
698     if (DumpStack("dbx %s %d 2>/dev/null <<EOF\n"
699     "where\n"
700     "detach\n"
701     "EOF\n",
702     global_progname, (int)getpid()))
703     return;
704     # endif
705    
706     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
707     "set prompt\n"
708     "echo --- Stacktrace\\n\n"
709     "where\n"
710     "echo --- Symbols\\n\n"
711     "frame 4\n"
712     "set \\$x = 50\n"
713     "while (\\$x)\n" /* Print local variables for each frame */
714     "info locals\n"
715     "up\n"
716     "set \\$x--\n"
717     "end\n"
718     "echo ---\\n\n"
719     "detach\n"
720     "quit\n"
721     "EOF\n",
722     global_progname, (int)getpid()))
723     return;
724    
725     # endif
726    
727     # if defined(__GNUC__) && defined(USE_BUILTIN)
728    
729     GCC_DumpStack();
730    
731     # endif
732    
733     write(global_output,
734     "No debugger found\n", strlen("No debugger found\n"));
735    
736     #elif defined(PLATFORM_WIN32)
737     /* Use StackWalk() */
738     #endif
739     }
740    
741     /*************************************************************************
742     * StackTraceInit
743     */
744     void StackTraceInit(const char *in_name, int in_handle)
745     {
746     global_progname = in_name;
747     global_output = (in_handle == -1) ? STDOUT_FILENO : in_handle;
748     }
749    
750    
751     /*************************************************************************
752     * test
753     */
754     #if defined(STANDALONE)
755    
756     void CrashHandler(int sig)
757     {
758     /* Reinstall default handler to prevent race conditions */
759     signal(sig, SIG_DFL);
760     /* Print the stack trace */
761     StackTrace();
762     /* And exit because we may have corrupted the internal
763     * memory allocation lists. Use abort() if we want to
764     * generate a core dump. */
765     _exit(EXIT_FAILURE);
766     }
767    
768    
769     void Crash(void)
770     {
771     /* Force a crash */
772     strcpy(NULL, "");
773     }
774    
775     int main(int argc, char *argv[])
776     {
777     struct sigaction sact;
778    
779     StackTraceInit(argv[0], -1);
780    
781     sigemptyset(&sact.sa_mask);
782     sact.sa_flags = 0;
783     sact.sa_handler = CrashHandler;
784     sigaction(SIGSEGV, &sact, NULL);
785     sigaction(SIGBUS, &sact, NULL);
786    
787     Crash();
788     return EXIT_SUCCESS;
789     }
790     #endif

  ViewVC Help
Powered by ViewVC