dvadura@watdragon.waterloo.edu (Dennis Vadura) (05/10/91)
Submitted-by: Dennis Vadura <dvadura@watdragon.waterloo.edu> Posting-number: Volume 19, Issue 24 Archive-name: dmake/part03 Supersedes: dmake-3.6: Volume 15, Issue 52-77 ---- Cut Here and feed the following to sh ---- #!/bin/sh # this is dmake.shar.03 (part 3 of a multipart archive) # do not concatenate these parts, unpack them in order with /bin/sh # file dmake/dbug/dbug/dbug.c continued # if test ! -r _shar_seq_.tmp; then echo 'Please unpack part 1 first!' exit 1 fi (read Scheck if test "$Scheck" != 3; then echo Please unpack part "$Scheck" next! exit 1 else exit 0 fi ) < _shar_seq_.tmp || exit 1 if test -f _shar_wnt_.tmp; then sed 's/^X//' << 'SHAR_EOF' >> 'dmake/dbug/dbug/dbug.c' && X * current process name, current source file name and line number, X * and current function nesting depth. X * X */ X X LOCAL VOID DoPrefix (_line_) int _line_; { X lineno++; X if (stack -> flags & NUMBER_ON) { X (VOID) fprintf (_db_fp_, "%5d: ", lineno); X } X if (stack -> flags & PROCESS_ON) { X (VOID) fprintf (_db_fp_, "%s: ", _db_process_); X } X if (stack -> flags & FILE_ON) { X (VOID) fprintf (_db_fp_, "%14s: ", file); X } X if (stack -> flags & LINE_ON) { X (VOID) fprintf (_db_fp_, "%5d: ", _line_); X } X if (stack -> flags & DEPTH_ON) { X (VOID) fprintf (_db_fp_, "%4d: ", stack -> level); X } X (VOID) fflush (_db_fp_); } X X /* X * FUNCTION X * X * OpenFile open new output stream for debugger output X * X * SYNOPSIS X * X * LOCAL VOID OpenFile (name) X * char *name; X * X * DESCRIPTION X * X * Given name of a new file (or "-" for stdout) opens the file X * and sets the output stream to the new file. X * X */ X LOCAL VOID OpenFile (name) char *name; { X REGISTER FILE *fp; X REGISTER BOOLEAN newfile; X X if (name != NULL) { X if (strcmp (name, "-") == 0) { X _db_fp_ = stdout; X stack -> out_file = _db_fp_; X } else { X if (!Writable (name)) { X (VOID) fprintf (_db_fp_, ERR_OPEN, _db_process_, name); X perror (""); X (VOID) fflush (_db_fp_); X (VOID) XDelay (stack -> delay); X } else { X if (EXISTS (name)) { X newfile = FALSE; X } else { X newfile = TRUE; X } X fp = fopen (name, "a"); X if (fp == NULL) { X (VOID) fprintf (_db_fp_, ERR_OPEN, _db_process_, name); X perror (""); X (VOID) fflush (_db_fp_); X (VOID) XDelay (stack -> delay); X } else { X _db_fp_ = fp; X stack -> out_file = fp; X if (newfile) { X ChangeOwner (name); X } X } X } X } X } } X X /* X * FUNCTION X * X * OpenProfile open new output stream for profiler output X * X * SYNOPSIS X * X * LOCAL VOID OpenProfile (name) X * char *name; X * X * DESCRIPTION X * X * Given name of a new file, opens the file X * and sets the profiler output stream to the new file. X * X * It is currently unclear whether the prefered behavior is X * to truncate any existing file, or simply append to it. X * The latter behavior would be desirable for collecting X * accumulated runtime history over a number of separate X * runs. It might take some changes to the analyzer program X * though, and the notes that Binayak sent with the profiling X * diffs indicated that append was the normal mode, but this X * does not appear to agree with the actual code. I haven't X * investigated at this time [fnf; 24-Jul-87]. X */ X LOCAL VOID OpenProfile (name) char *name; { X REGISTER FILE *fp; X REGISTER BOOLEAN newfile; X X if (name != NULL) { X if (!Writable (name)) { X (VOID) fprintf (_db_fp_, ERR_OPEN, _db_process_, name); X perror (""); X (VOID) fflush (_db_fp_); X (VOID) XDelay (stack -> delay); X } else { X if (EXISTS (name)) { X newfile = FALSE; X } else { X newfile = TRUE; X } X fp = fopen (name, "w"); X if (fp == NULL) { X (VOID) fprintf (_db_fp_, ERR_OPEN, _db_process_, name); X perror (""); X (VOID) fflush (_db_fp_); X (VOID) XDelay (stack -> delay); X } else { X _db_pfp_ = fp; X stack -> prof_file = fp; X if (newfile) { X ChangeOwner (name); X } X } X } X } } X X /* X * FUNCTION X * X * CloseFile close the debug output stream X * X * SYNOPSIS X * X * LOCAL VOID CloseFile (fp) X * FILE *fp; X * X * DESCRIPTION X * X * Closes the debug output stream unless it is standard output X * or standard error. X * X */ X LOCAL VOID CloseFile (fp) FILE *fp; { X if (fp != stderr && fp != stdout) { X if (fclose (fp) == EOF) { X (VOID) fprintf (stderr, ERR_CLOSE, _db_process_); X perror (""); X (VOID) fflush (stderr); X (VOID) XDelay (stack -> delay); X } X } } X X /* X * FUNCTION X * X * DbugExit print error message and exit X * X * SYNOPSIS X * X * LOCAL VOID DbugExit (why) X * char *why; X * X * DESCRIPTION X * X * Prints error message using current process name, the reason for X * aborting (typically out of memory), and exits with status 1. X * This should probably be changed to use a status code X * defined in the user's debugger include file. X * X */ X LOCAL VOID DbugExit (why) char *why; { X (VOID) fprintf (stderr, ERR_ABORT, _db_process_, why); X (VOID) fflush (stderr); X (VOID) XDelay (stack -> delay); X exit (1); } X X /* X * FUNCTION X * X * DbugMalloc allocate memory for debugger runtime support X * X * SYNOPSIS X * X * LOCAL char *DbugMalloc (size) X * int size; X * X * DESCRIPTION X * X * Allocate more memory for debugger runtime support functions. X * Failure to to allocate the requested number of bytes is X * immediately fatal to the current process. This may be X * rather unfriendly behavior. It might be better to simply X * print a warning message, freeze the current debugger state, X * and continue execution. X * X */ X LOCAL char *DbugMalloc (size) int size; { X register char *new; X X new = malloc ( size ); X if (new == NULL) { X DbugExit ("out of memory"); X } X return (new); } X X /* X * This function may be eliminated when strtok is available X * in the runtime environment (missing from BSD4.1). X */ X LOCAL char *strtok (s1, s2) char *s1, *s2; { X static char *end = NULL; X REGISTER char *rtnval; X X rtnval = NULL; X if (s2 != NULL) { X if (s1 != NULL) { X end = s1; X rtnval = strtok ((char *) NULL, s2); X } else if (end != NULL) { X if (*end != EOS) { X rtnval = end; X while (*end != *s2 && *end != EOS) {end++;} X if (*end != EOS) { X *end++ = EOS; X } X } X } X } X return (rtnval); } X X /* X * FUNCTION X * X * BaseName strip leading pathname components from name X * X * SYNOPSIS X * X * LOCAL char *BaseName (pathname) X * char *pathname; X * X * DESCRIPTION X * X * Given pointer to a complete pathname, locates the base file X * name at the end of the pathname and returns a pointer to X * it. X * X */ X LOCAL char *BaseName (pathname) char *pathname; { X register char *base; X X base = strrchr (pathname, '/'); X if (base++ == NULL) { X base = pathname; X } X return (base); } X X /* X * FUNCTION X * X * Writable test to see if a pathname is writable/creatable X * X * SYNOPSIS X * X * LOCAL BOOLEAN Writable (pathname) X * char *pathname; X * X * DESCRIPTION X * X * Because the debugger might be linked in with a program that X * runs with the set-uid-bit (suid) set, we have to be careful X * about opening a user named file for debug output. This consists X * of checking the file for write access with the real user id, X * or checking the directory where the file will be created. X * X * Returns TRUE if the user would normally be allowed write or X * create access to the named file. Returns FALSE otherwise. X * X */ X LOCAL BOOLEAN Writable (pathname) char *pathname; { X REGISTER BOOLEAN granted; #ifdef unix X REGISTER char *lastslash; #endif X #ifndef unix X granted = TRUE; #else X granted = FALSE; X if (EXISTS (pathname)) { X if (WRITABLE (pathname)) { X granted = TRUE; X } X } else { X lastslash = strrchr (pathname, '/'); X if (lastslash != NULL) { X *lastslash = EOS; X } else { X pathname = "."; X } X if (WRITABLE (pathname)) { X granted = TRUE; X } X if (lastslash != NULL) { X *lastslash = '/'; X } X } #endif X return (granted); } X X /* X * This function may be eliminated when strrchr is available X * in the runtime environment (missing from BSD4.1). X * Alternately, you can use rindex() on BSD systems. X */ X LOCAL char *strrchr (s, c) char *s; char c; { X REGISTER char *scan; X X for (scan = s; *scan != EOS; scan++) {;} X while (scan > s && *--scan != c) {;} X if (*scan != c) { X scan = NULL; X } X return (scan); } X X /* X * FUNCTION X * X * ChangeOwner change owner to real user for suid programs X * X * SYNOPSIS X * X * LOCAL VOID ChangeOwner (pathname) X * X * DESCRIPTION X * X * For unix systems, change the owner of the newly created debug X * file to the real owner. This is strictly for the benefit of X * programs that are running with the set-user-id bit set. X * X * Note that at this point, the fact that pathname represents X * a newly created file has already been established. If the X * program that the debugger is linked to is not running with X * the suid bit set, then this operation is redundant (but X * harmless). X * X */ X LOCAL VOID ChangeOwner (pathname) char *pathname; { #ifdef unix X if (chown (pathname, getuid (), getgid ()) == -1) { X (VOID) fprintf (stderr, ERR_CHOWN, _db_process_, pathname); X perror (""); X (VOID) fflush (stderr); X (VOID) XDelay (stack -> delay); X } #endif } X X /* X * FUNCTION X * X * _db_setjmp_ save debugger environment X * X * SYNOPSIS X * X * VOID _db_setjmp_ () X * X * DESCRIPTION X * X * Invoked as part of the user's DBUG_SETJMP macro to save X * the debugger environment in parallel with saving the user's X * environment. X * X */ X VOID _db_setjmp_ () { X jmplevel = stack -> level; X jmpfunc = func; X jmpfile = file; } X X /* X * FUNCTION X * X * _db_longjmp_ restore previously saved debugger environment X * X * SYNOPSIS X * X * VOID _db_longjmp_ () X * X * DESCRIPTION X * X * Invoked as part of the user's DBUG_LONGJMP macro to restore X * the debugger environment in parallel with restoring the user's X * previously saved environment. X * X */ X VOID _db_longjmp_ () { X stack -> level = jmplevel; X if (jmpfunc) { X func = jmpfunc; X } X if (jmpfile) { X file = jmpfile; X } } X X /* X * FUNCTION X * X * DelayArg convert D flag argument to appropriate value X * X * SYNOPSIS X * X * LOCAL int DelayArg (value) X * int value; X * X * DESCRIPTION X * X * Converts delay argument, given in tenths of a second, to the X * appropriate numerical argument used by the system to delay X * that that many tenths of a second. For example, on the X * AMIGA, there is a system call "Delay()" which takes an X * argument in ticks (50 per second). On unix, the sleep X * command takes seconds. Thus a value of "10", for one X * second of delay, gets converted to 50 on the amiga, and 1 X * on unix. Other systems will need to use a timing loop. X * X */ X LOCAL int DelayArg (value) int value; { X int delayarg = 0; X #ifdef unix X delayarg = value / 10; /* Delay is in seconds for sleep () */ #endif #ifdef AMIGA X delayarg = (HZ * value) / 10; /* Delay in ticks for XDelay () */ #endif X return (delayarg); } X X /* X * A dummy delay stub for systems that do not support delays. X * With a little work, this can be turned into a timing loop. X */ X #ifndef unix #ifndef AMIGA XXDelay () { } #endif #endif X X /* X * FUNCTION X * X * perror perror simulation for systems that don't have it X * X * SYNOPSIS X * X * LOCAL VOID perror (s) X * char *s; X * X * DESCRIPTION X * X * Perror produces a message on the standard error stream which X * provides more information about the library or system error X * just encountered. The argument string s is printed, followed X * by a ':', a blank, and then a message and a newline. X * X * An undocumented feature of the unix perror is that if the string X * 's' is a null string (NOT a NULL pointer!), then the ':' and X * blank are not printed. X * X * This version just complains about an "unknown system error". X * X */ X #if !unix && !(AMIGA || LATTICE || __TURBOC__ ) LOCAL VOID perror (s) #if __STDC__ const char *s; #else char *s; #endif { X if (s && *s != EOS) { X (VOID) fprintf (stderr, "%s: ", s); X } X (VOID) fprintf (stderr, "<unknown system error>\n"); } #endif /* !unix && !(AMIGA && LATTICE) */ X /* X * Here we need the definitions of the clock routine. Add your X * own for whatever system that you have. X */ X #if unix X # include <sys/param.h> # if BSD4_3 || sun X /* X * Definition of the Clock() routine for 4.3 BSD. X */ X #include <sys/time.h> #include <sys/resource.h> X /* X * Returns the user time in milliseconds used by this process so X * far. X */ X LOCAL unsigned long Clock () { X struct rusage ru; X X (VOID) getrusage (RUSAGE_SELF, &ru); X return ((ru.ru_utime.tv_sec * 1000) + (ru.ru_utime.tv_usec / 1000)); } X #else X LOCAL unsigned long Clock () { X return (0); } X # endif X #else X #if AMIGA X struct DateStamp { /* Yes, this is a hack, but doing it right */ X long ds_Days; /* is incredibly ugly without splitting this */ X long ds_Minute; /* off into a separate file */ X long ds_Tick; }; X static int first_clock = TRUE; static struct DateStamp begin; static struct DateStamp elapsed; X LOCAL unsigned long Clock () { X register struct DateStamp *now; X register unsigned long millisec = 0; X extern VOID *AllocMem (); X X now = (struct DateStamp *) AllocMem ((long) sizeof (struct DateStamp), 0L); X if (now != NULL) { X if (first_clock == TRUE) { X first_clock = FALSE; X (VOID) DateStamp (now); X begin = *now; X } X (VOID) DateStamp (now); X millisec = 24 * 3600 * (1000 / HZ) * (now -> ds_Days - begin.ds_Days); X millisec += 60 * (1000 / HZ) * (now -> ds_Minute - begin.ds_Minute); X millisec += (1000 / HZ) * (now -> ds_Tick - begin.ds_Tick); X (VOID) FreeMem (now, (long) sizeof (struct DateStamp)); X } X return (millisec); } X #else X LOCAL unsigned long Clock () { X return (0); } X #endif /* AMIGA */ X #endif /* unix */ X #ifdef AMIGA XXDelay(x) int x; { X if (x) Delay(x); /* fix Delay bug in AmigaDOS */ } #endif X SHAR_EOF chmod 0640 dmake/dbug/dbug/dbug.c || echo 'restore of dmake/dbug/dbug/dbug.c failed' Wc_c="`wc -c < 'dmake/dbug/dbug/dbug.c'`" test 44504 -eq "$Wc_c" || echo 'dmake/dbug/dbug/dbug.c: original size 44504, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= dmake/dbug/dbug/dbug.h ============== if test -f 'dmake/dbug/dbug/dbug.h' -a X"$1" != X"-c"; then echo 'x - skipping dmake/dbug/dbug/dbug.h (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp sed 's/^X//' << 'SHAR_EOF' > 'dmake/dbug/dbug/dbug.h' && /****************************************************************************** X * * X * N O T I C E * X * * X * Copyright Abandoned, 1987, Fred Fish * X * * X * * X * This previously copyrighted work has been placed into the public * X * domain by the author and may be freely used for any purpose, * X * private or commercial. * X * * X * Because of the number of inquiries I was receiving about the use * X * of this product in commercially developed works I have decided to * X * simply make it public domain to further its unrestricted use. I * X * specifically would be most happy to see this material become a * X * part of the standard Unix distributions by AT&T and the Berkeley * X * Computer Science Research Group, and a standard part of the GNU * X * system from the Free Software Foundation. * X * * X * I would appreciate it, as a courtesy, if this notice is left in * X * all copies and derivative works. Thank you. * X * * X * The author makes no warranty of any kind with respect to this * X * product and explicitly disclaims any implied warranties of mer- * X * chantability or fitness for any particular purpose. * X * * X ****************************************************************************** X */ X X /* X * FILE X * X * dbug.h user include file for programs using the dbug package X * X * SYNOPSIS X * X * #include <local/dbug.h> X * X * SCCS ID X * X * @(#)dbug.h 1.11 9/5/87 X * X * DESCRIPTION X * X * Programs which use the dbug package must include this file. X * It contains the appropriate macros to call support routines X * in the dbug runtime library. X * X * To disable compilation of the macro expansions define the X * preprocessor symbol "DBUG_OFF". This will result in null X * macros expansions so that the resulting code will be smaller X * and faster. (The difference may be smaller than you think X * so this step is recommended only when absolutely necessary). X * In general, tradeoffs between space and efficiency are X * decided in favor of efficiency since space is seldom a X * problem on the new machines). X * X * All externally visible symbol names follow the pattern X * "_db_xxx..xx_" to minimize the possibility of a dbug package X * symbol colliding with a user defined symbol. X * X * The DBUG_<N> style macros are obsolete and should not be used X * in new code. Macros to map them to instances of DBUG_PRINT X * are provided for compatibility with older code. They may go X * away completely in subsequent releases. X * X * AUTHOR X * X * Fred Fish X * (Currently employed by Motorola Computer Division, Tempe, Az.) X * hao!noao!mcdsun!fnf X * (602) 438-3614 X * X */ X X /* X * Internally used dbug variables which must be global. X */ X #ifndef DBUG_OFF X extern int _db_on_; /* TRUE if debug currently enabled */ X extern FILE *_db_fp_; /* Current debug output stream */ X extern char *_db_process_; /* Name of current process */ X extern int _db_keyword_ (); /* Accept/reject keyword */ X extern void _db_push_ (); /* Push state, set up new state */ X extern void _db_pop_ (); /* Pop previous debug state */ X extern void _db_enter_ (); /* New user function entered */ X extern void _db_return_ (); /* User function return */ X extern void _db_pargs_ (); /* Remember args for line */ X extern void _db_doprnt_ (); /* Print debug output */ X extern void _db_setjmp_ (); /* Save debugger environment */ X extern void _db_longjmp_ (); /* Restore debugger environment */ # endif X X /* X * These macros provide a user interface into functions in the X * dbug runtime support library. They isolate users from changes X * in the MACROS and/or runtime support. X * X * The symbols "__LINE__" and "__FILE__" are expanded by the X * preprocessor to the current source file line number and file X * name respectively. X * X * WARNING --- Because the DBUG_ENTER macro allocates space on X * the user function's stack, it must precede any executable X * statements in the user function. X * X */ X # ifdef DBUG_OFF # define DBUG_ENTER(a1) # define DBUG_MALLOC(a1) # define DBUG_RETURN(a1) return(a1) # define DBUG_VOID_RETURN return # define DBUG_EXECUTE(keyword,a1) # define DBUG_PRINT(keyword,arglist) # define DBUG_2(keyword,format) /* Obsolete */ # define DBUG_3(keyword,format,a1) /* Obsolete */ # define DBUG_4(keyword,format,a1,a2) /* Obsolete */ # define DBUG_5(keyword,format,a1,a2,a3) /* Obsolete */ # define DBUG_PUSH(a1) # define DBUG_POP() # define DBUG_PROCESS(a1) # define DBUG_FILE (stderr) # define DBUG_SETJMP setjmp # define DBUG_LONGJMP longjmp # else # define DBUG_ENTER(a) \ X auto char *_db_func_, *_db_file_; \ X int _db_level_; \ X _db_enter_ (a,__FILE__,__LINE__,&_db_func_,&_db_file_,&_db_level_) # define DBUG_MALLOC(a) \ X auto char *_db_func_, *_db_file_; \ X int _db_level_; \ X malloc_init();\ X _db_enter_ (a,__FILE__,__LINE__,&_db_func_,&_db_file_,&_db_level_) # define DBUG_LEAVE \ X (_db_return_ (__LINE__, &_db_func_, &_db_file_, &_db_level_)) # define DBUG_RETURN(a1) return (DBUG_LEAVE, (a1)) /* define DBUG_RETURN(a1) {DBUG_LEAVE; return(a1);} Alternate form */ # define DBUG_VOID_RETURN DBUG_LEAVE; return # define DBUG_EXECUTE(keyword,a1) \ X {if (_db_on_) {if (_db_keyword_ (keyword)) { a1 }}} # define DBUG_PRINT(keyword,arglist) \ X {if (_db_on_) {_db_pargs_(__LINE__,keyword); _db_doprnt_ arglist;}} # define DBUG_2(keyword,format) \ X DBUG_PRINT(keyword,(format)) /* Obsolete */ # define DBUG_3(keyword,format,a1) \ X DBUG_PRINT(keyword,(format,a1)) /* Obsolete */ # define DBUG_4(keyword,format,a1,a2) \ X DBUG_PRINT(keyword,(format,a1,a2)) /* Obsolete */ # define DBUG_5(keyword,format,a1,a2,a3) \ X DBUG_PRINT(keyword,(format,a1,a2,a3)) /* Obsolete */ # define DBUG_PUSH(a1) _db_push_ (a1) # define DBUG_POP() _db_pop_ () # define DBUG_PROCESS(a1) (_db_process_ = a1) # define DBUG_FILE (_db_fp_) # define DBUG_SETJMP(a1) (_db_setjmp_ (), setjmp (a1)) # define DBUG_LONGJMP(a1,a2) (_db_longjmp_ (), longjmp (a1, a2)) # endif X SHAR_EOF chmod 0640 dmake/dbug/dbug/dbug.h || echo 'restore of dmake/dbug/dbug/dbug.h failed' Wc_c="`wc -c < 'dmake/dbug/dbug/dbug.h'`" test 6259 -eq "$Wc_c" || echo 'dmake/dbug/dbug/dbug.h: original size 6259, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= dmake/dbug/dbug/dbug.p ============== if test -f 'dmake/dbug/dbug/dbug.p' -a X"$1" != X"-c"; then echo 'x - skipping dmake/dbug/dbug/dbug.p (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp sed 's/^X//' << 'SHAR_EOF' > 'dmake/dbug/dbug/dbug.p' && X X X X D B U G X X C Program Debugging Package X X by X X Fred Fish X X X X X IIIINNNNTTTTRRRROOOODDDDUUUUCCCCTTTTIIIIOOOONNNN X X X Almost every program development environment worthy of X the name provides some sort of debugging facility. Usually X this takes the form of a program which is capable of X controlling execution of other programs and examining the X internal state of other executing programs. These types of X programs will be referred to as external debuggers since the X debugger is not part of the executing program. Examples of X this type of debugger include the aaaaddddbbbb and ssssddddbbbb debuggers X provided with the UUUUNNNNIIIIXXXX811119 operating system. X X X One of the problems associated with developing programs X in an environment with good external debuggers is that X developed programs tend to have little or no internal X instrumentation. This is usually not a problem for the X developer since he is, or at least should be, intimately X familiar with the internal organization, data structures, X and control flow of the program being debugged. It is a X serious problem for maintenance programmers, who are X unlikely to have such familiarity with the program being X maintained, modified, or ported to another environment. It X is also a problem, even for the developer, when the program X is moved to an environment with a primitive or unfamiliar X debugger, or even no debugger. X X X On the other hand, _d_b_u_g is an example of an internal X debugger. Because it requires internal instrumentation of a X program, and its usage does not depend on any special X capabilities of the execution environment, it is always X available and will execute in any environment that the X program itself will execute in. In addition, since it is a X complete package with a specific user interface, all X programs which use it will be provided with similar X debugging capabilities. This is in sharp contrast to other X X X __________ X X 1. UNIX is a trademark of AT&T Bell Laboratories. X X X X X - 1 - X X X X X X X X DBUG User Manual October 29, 1986 X X X X forms of internal instrumentation where each developer has X their own, usually less capable, form of internal debugger. X In summary, because _d_b_u_g is an internal debugger it provides X consistency across operating environments, and because it is X available to all developers it provides consistency across X all programs in the same environment. X X X The _d_b_u_g package imposes only a slight speed penalty on X executing programs, typically much less than 10 percent, and X a modest size penalty, typically 10 to 20 percent. By X defining a specific C preprocessor symbol both of these can X be reduced to zero with no changes required to the source X code. X X X The following list is a quick summary of the X capabilities of the _d_b_u_g package. Each capability can be X individually enabled or disabled at the time a program is X invoked by specifying the appropriate command line X arguments. X X o Execution trace showing function level control X flow in a semi-graphically manner using X indentation to indicate nesting depth. X X o Output the values of all, or any subset of, key X internal variables. X X o Limit actions to a specific set of named X functions. X X o Limit function trace to a specified nesting depth. X X o Label each output line with source file name and X line number. X X o Label each output line with name of current X process. X X o Push or pop internal debugging state to allow X execution with built in debugging defaults. X X o Redirect the debug output stream to standard X output (stdout) or a named file. The default X output stream is standard error (stderr). The X redirection mechanism is completely independent of X normal command line redirection to avoid output X conflicts. X X X X X X - 2 - X X X X X X X X DBUG User Manual October 29, 1986 X X X X PPPPRRRRIIIIMMMMIIIITTTTIIIIVVVVEEEE DDDDEEEEBBBBUUUUGGGGGGGGIIIINNNNGGGG TTTTEEEECCCCHHHHNNNNIIIIQQQQUUUUEEEESSSS X X X Internal instrumentation is already a familiar concept X to most programmers, since it is usually the first debugging X technique learned. Typically, "print statements" are X inserted in the source code at interesting points, the code X is recompiled and executed, and the resulting output is X examined in an attempt to determine where the problem is. X X The procedure is iterative, with each iteration yielding X more and more output, and hopefully the source of the X problem is discovered before the output becomes too large to X deal with or previously inserted statements need to be X removed. Figure 1 is an example of this type of primitive X debugging technique. X X X X #include <stdio.h> X X main (argc, argv) X int argc; X char *argv[]; X { X printf ("argv[0] = %d\n", argv[0]); X /* X * Rest of program X */ X printf ("== done ==\n"); X } X X X Figure 1 X Primitive Debugging Technique X X X X X X Eventually, and usually after at least several X iterations, the problem will be found and corrected. At X this point, the newly inserted print statements must be X dealt with. One obvious solution is to simply delete them X all. Beginners usually do this a few times until they have X to repeat the entire process every time a new bug pops up. X The second most obvious solution is to somehow disable the X output, either through the source code comment facility, X creation of a debug variable to be switched on or off, or by X using the C preprocessor. Figure 2 is an example of all X three techniques. X X X X - 3 - X X X X X X X X DBUG User Manual October 29, 1986 X X X X X X #include <stdio.h> X X int debug = 0; X X main (argc, argv) X int argc; X char *argv[]; X { X /* printf ("argv = %x\n", argv) */ X if (debug) printf ("argv[0] = %d\n", argv[0]); X /* X * Rest of program X */ X #ifdef DEBUG X printf ("== done ==\n"); X #endif X } X X X Figure 2 X Debug Disable Techniques X X X X X X Each technique has its advantages and disadvantages X with respect to dynamic vs static activation, source code X overhead, recompilation requirements, ease of use, program X readability, etc. Overuse of the preprocessor solution X quickly leads to problems with source code readability and X maintainability when multiple ####iiiiffffddddeeeeffff symbols are to be X defined or undefined based on specific types of debug X desired. The source code can be made slightly more readable X by suitable indentation of the ####iiiiffffddddeeeeffff arguments to match the X indentation of the code, but not all C preprocessors allow X this. The only requirement for the standard UUUUNNNNIIIIXXXX C X preprocessor is for the '#' character to appear in the first X column, but even this seems like an arbitrary and X unreasonable restriction. Figure 3 is an example of this X usage. X X X X X X X X X X X X - 4 - X X X X X X X X DBUG User Manual October 29, 1986 X X X X X X #include <stdio.h> X X main (argc, argv) X int argc; X char *argv[]; X { X # ifdef DEBUG X printf ("argv[0] = %d\n", argv[0]); X # endif X /* X * Rest of program X */ X # ifdef DEBUG X printf ("== done ==\n"); X # endif X } X X X Figure 3 X More Readable Preprocessor Usage X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X - 5 - X X X X X X X X DBUG User Manual October 29, 1986 X X X X FFFFUUUUNNNNCCCCTTTTIIIIOOOONNNN TTTTRRRRAAAACCCCEEEE EEEEXXXXAAAAMMMMPPPPLLLLEEEE X X X We will start off learning about the capabilities of X the _d_b_u_g package by using a simple minded program which X computes the factorial of a number. In order to better X demonstrate the function trace mechanism, this program is X implemented recursively. Figure 4 is the main function for X this factorial program. X X X X #include <stdio.h> X /* User programs should use <local/dbug.h> */ X #include "dbug.h" X X main (argc, argv) X int argc; X char *argv[]; X { X register int result, ix; X extern int factorial (), atoi (); X X DBUG_ENTER ("main"); X DBUG_PROCESS (argv[0]); X for (ix = 1; ix < argc && argv[ix][0] == '-'; ix++) { X switch (argv[ix][1]) { X case '#': X DBUG_PUSH (&(argv[ix][2])); X break; X } X } X for (; ix < argc; ix++) { X DBUG_PRINT ("args", ("argv[%d] = %s", ix, argv[ix])); X result = factorial (atoi (argv[ix])); X printf ("%d\n", result); X } X DBUG_RETURN (0); X } X X X Figure 4 X Factorial Program Mainline X X X X X X The mmmmaaaaiiiinnnn function is responsible for processing any X command line option arguments and then computing and X printing the factorial of each non-option argument. X X X X - 6 - X X X X X X X X DBUG User Manual October 29, 1986 X X X X First of all, notice that all of the debugger functions X are implemented via preprocessor macros. This does not X detract from the readability of the code and makes disabling X all debug compilation trivial (a single preprocessor symbol, X DDDDBBBBUUUUGGGG____OOOOFFFFFFFF, forces the macro expansions to be null). X X Also notice the inclusion of the header file ddddbbbbuuuugggg....hhhh X from the local header file directory. (The version included X here is the test version in the dbug source distribution X directory). This file contains all the definitions for the X debugger macros, which all have the form DDDDBBBBUUUUGGGG____XXXXXXXX............XXXXXXXX. X X X The DDDDBBBBUUUUGGGG____EEEENNNNTTTTEEEERRRR macro informs that debugger that we have X entered the function named mmmmaaaaiiiinnnn. It must be the very first X "executable" line in a function, after all declarations and X before any other executable line. The DDDDBBBBUUUUGGGG____PPPPRRRROOOOCCCCEEEESSSSSSSS macro is X generally used only once per program to inform the debugger X what name the program was invoked with. The DDDDBBBBUUUUGGGG____PPPPUUUUSSSSHHHH macro X modifies the current debugger state by saving the previous X state and setting a new state based on the control string X passed as its argument. The DDDDBBBBUUUUGGGG____PPPPRRRRIIIINNNNTTTT macro is used to X print the values of each argument for which a factorial is X to be computed. The DDDDBBBBUUUUGGGG____RRRREEEETTTTUUUURRRRNNNN macro tells the debugger X that the end of the current function has been reached and X returns a value to the calling function. All of these X macros will be fully explained in subsequent sections. X X To use the debugger, the factorial program is invoked X with a command line of the form: X X factorial -#d:t 1 2 3 X X The mmmmaaaaiiiinnnn function recognizes the "-#d:t" string as a X debugger control string, and passes the debugger arguments X ("d:t") to the _d_b_u_g runtime support routines via the X DDDDBBBBUUUUGGGG____PPPPUUUUSSSSHHHH macro. This particular string enables output from X the DDDDBBBBUUUUGGGG____PPPPRRRRIIIINNNNTTTT macro with the 'd' flag and enables function X tracing with the 't' flag. The factorial function is then X called three times, with the arguments "1", "2", and "3". X Note that the DBUG_PRINT takes exactly ttttwwwwoooo arguments, with X the second argument (a format string and list of printable X values) enclosed in parenthesis. X X Debug control strings consist of a header, the "-#", X followed by a colon separated list of debugger arguments. X Each debugger argument is a single character flag followed X by an optional comma separated list of arguments specific to X the given flag. Some examples are: X X X X X X - 7 - X X X X X X X X DBUG User Manual October 29, 1986 X X X X -#d:t:o X -#d,in,out:f,main:F:L X X Note that previously enabled debugger actions can be X disabled by the control string "-#". X X X The definition of the factorial function, symbolized as X "N!", is given by: X X N! = N * N-1 * ... 2 * 1 X X Figure 5 is the factorial function which implements this X algorithm recursively. Note that this is not necessarily X the best way to do factorials and error conditions are X ignored completely. X X X X #include <stdio.h> X /* User programs should use <local/dbug.h> */ X #include "dbug.h" X X int factorial (value) X register int value; X { X DBUG_ENTER ("factorial"); X DBUG_PRINT ("find", ("find %d factorial", value)); X if (value > 1) { X value *= factorial (value - 1); X } X DBUG_PRINT ("result", ("result is %d", value)); X DBUG_RETURN (value); X } X X X Figure 5 X Factorial Function X X X X X X One advantage (some may not consider it so) to using X the _d_b_u_g package is that it strongly encourages fully X structured coding with only one entry and one exit point in X each function. Multiple exit points, such as early returns X to escape a loop, may be used, but each such point requires X the use of an appropriate DDDDBBBBUUUUGGGG____RRRREEEETTTTUUUURRRRNNNN or DDDDBBBBUUUUGGGG____VVVVOOOOIIIIDDDD____RRRREEEETTTTUUUURRRRNNNN X macro. X X X X X - 8 - X X X X X X X X DBUG User Manual October 29, 1986 X X X X To build the factorial program on a UUUUNNNNIIIIXXXX system, X compile and link with the command: X X cc -o factorial main.c factorial.c -ldbug X X The "-ldbug" argument tells the loader to link in the X runtime support modules for the _d_b_u_g package. Executing the X factorial program with a command of the form: X X factorial 1 2 3 4 5 X X generates the output shown in figure 6. X X X X 1 X 2 X 6 X 24 X 120 X X X Figure 6 X factorial 1 2 3 4 5 SHAR_EOF true || echo 'restore of dmake/dbug/dbug/dbug.p failed' fi echo 'End of part 3, continue with part 4' echo 4 > _shar_seq_.tmp exit 0 exit 0 # Just in case... -- Kent Landfield INTERNET: kent@sparky.IMD.Sterling.COM Sterling Software, IMD UUCP: uunet!sparky!kent Phone: (402) 291-8300 FAX: (402) 291-4362 Please send comp.sources.misc-related mail to kent@uunet.uu.net.