Using the command line interface of debug

Session 2: why did it dump core?

  1. Edit line 60 in main.c so it looks like this:
       while ((c = *next++) != '\0')
    This will fix the looping problem.

  2. Recompile with -g:
       $ cc -g -o macros main.c macro.c
    Run macros. This time macros dumps core:
       $ macros <test1
       Warning, TEST redefined
       Segmentation fault(coredump)

  3. Core files are designated by Look in the directory for the most recent core file:
       $ ls -lt core.*
       -rw-r--r--  1 user   14237     32860  Jul 22 10:56 core.2088

  4. Get into debug to look at the program again, using grab -c (for core) to examine the core file:
       $ debug
       debug> grab -c core.2088 macros
    The debugger displays the following:
       Core image of macros (process p1) created
       CORE FILE [insert in macro.c]
       Signal: sigsegv Fault address: 0xc
       65:                             node->definition = list;

    NOTE: See ``Grabbing Core Files'' for more information.

    You can see that it received the signal SIGSEGV in insert, while it was assigning defn to node->right. That's suspicious; assigning through a null pointer is a sure way to get a segmentation violation (and core dump) on most machines.

  5. Check to see what node is:
       debug> print node

  6. Check to see why node is zero. Look at the beginning of insert to see where node was set:
       debug> list insert
       22:  {
       23:       struct macro_def        *defn;
       24:       struct macro_def        *node;
       25:       int                     val=0;
       27:       if ((defn = (struct macro_def *)malloc(
       28:               sizeof(struct macro_def))) == 0)
       29:       {
       30:           (void) fprintf(stderr, "malloc failed!\n");
       31:           exit(1);

  7. Node isn't set there. Try looking for node specifically:
       debug> list /node
       38:        node = root;

    NOTE: See ``Examining the source file'' for more information.

    It is set to the value of root. Find the value of root.

       debug> print root

  8. root shouldn't be zero unless this is the first call to insert. Look at the parameters to insert to see if this is the first definition. The parameters name and list are both pointers to struct tokens. If you print them now, you will just see the values of the pointers:
       debug> print name, list
       0x804abec 0x804ac0c

  9. De-referencing the pointers will show you what they point to:
       debug> print *name, *list
           type=WORD (or 0)
           type=WORD (or 0)
    The name of the macro being defined is TEST.

    NOTE: See ``Printing expressions'' for more information about the print command.

  10. Escape to the shell so that you can look at the input file again to make sure that was the first time insert was called:

    NOTE: debug lets you escape to the shell with !, just like ed(1). A subsequent !! re-runs the most recent shell command.

       debug> !cat test1
    The debugger displays the following:
       define(TEST,this is a test)
       define(X,TEST test TEST)
       define(A1,1 2 3 4)  A1
    Since this is the first time insert is called, it is reasonable that root is still zero. On the other hand, insert shouldn't be walking the tree looking for a match when there is nothing there. It would be more reasonable to just set root and return.

  11. Look for root:
       debug> list /root
       80:        node = root;

  12. Here root is being used, but not set. Look again:
       debug> l /
    The debugger displays the definition.
       16:        static struct macro_def        *root;

  13. Look again:
       debug> l /
       38:        node = root;
       debug> l /
       80:        node = root;
    This last list puts you back to where you started, with root being referenced twice, but never set. Since it is declared static, it could not have been set in some other file, so you have found the root cause of this bug.

Grabbing core files

A core file is a file produced by the operating system to record the process state at the time the process dies abnormally. When you grab a core file, debug will tell you where it died and the signal that made the operating system produce the core file. You can do anything with the program that you can do with a running program except make it run any further; you can look at the stack trace, print variables, list the source, and so on.

In addition to the grab command, you can tell debug directly on the command line that you want to examine a core file:

   $ debug -c macros

Printing expressions

Both the symbols and print commands are useful in different circumstances. symbols is useful for quickly examining several symbols, but it may give more information than you want, for example, when one of the variables is a large array or structure. print provides more control over what is printed and how the information is formatted, but you have to do more typing to get the information. The syntax of the print command is

   print [-f format] expr, ...
where expr may be a variable, a structure or array member, or any valid C expression.

NOTE: expr may also be a subset of C++ expressions. See ``Debugging C++ programs''.

If you do not give it a format string, debug will evaluate the expression and judge how to print the result based on the type of the expression.

NOTE: Comments start with # and continue to the end of the line.

   debug> print val, name  # integer and pointer to structure
   0 0x804abec
   debug> print *name      # dereference a pointer to a structure
           type=WORD (or 0)
   debug> print name->string[0]  # single character
   84 (or 'T')
   debug> p list[val]            # an array element
           type=WORD (or 0)
   debug> print list->next->next->string  # character string

format is a simplified version of the printf [see fprintf(3S)] format string. The format specifier %z stands for the debugger's default format style, and lets you print out entire structures or arrays at once:

   debug> print -f "val = %d, *name = %z\n" val, *name
   val = 0, *name = {
       type=WORD (or 0)
However, if you supply a format string, you are also responsible for the new-line; otherwise you will get this:
   debug> print -f"val = %d" val
   val = 0debug>

Examining the source file

You can examine the source of the program you are debugging using the list command. There are three ways to tell list where to start printing:

  1. Give list a location, which can be an ordinary line number:
       debug> list 16
       16:        static struct macro_def *root;
       . . .
    a function name:
       debug> list insert
       22:        {
       . . .
    or a line number or function name qualified with a file name:
       debug> l main.c@main
       . . .
       debug> l macro.c@18
       . . .
    The unqualified forms work only if the line or function is in the current file. If it is in a different file you have to use the qualified form. You can see what the current file is by printing the debugger variable %file:
       debug> print %file

  2. Give list a regular expression to search for in the current file.

    NOTE: See ed(1) for a description of regular expressions.

    Use /expression/ to search forwards, ?expression? to search backwards. ? (or /) by itself will continue the search using the last regular expression given:

       debug> l /look.*(/
       75:     lookup(struct token *name)
       debug> l /
       107:             && (macro = lookup(tok_list)) != 0)

  3. Set the debugger variables %list_file and %list_line. If you do not give list any arguments, it will use %list_file and %list_line to determine where to start printing:
       debug> print %list_file, %list_line
       "macro.c" 107
       debug> set %list_file = "main.c"
       debug> set %list_line = 12
       debug> list
       12:     error(const char *msg)
       . . . {
Whenever list stops, it saves the last file and line displayed in %list_file and %list_line, and the next time you use list, it will start printing from where it left off. If you hadn't typed list before, it will start printing from the current line in the current file. If you are debugging a live process (instead of a core file), every time you run or step the process debug resets %list_file and %list_line; the next list will start off from the new current line instead of beginning where the last one left off.

Normally list displays 10 lines at a time, but you can control the number of lines displayed with the -c option:

   debug> l -c 3 insert
   22:  {
   23:         struct macro_def    *defn;
   24:         struct macro_def    *node;
or by setting the debugger variable %num_lines:
   debug> print %num_lines
   debug> set %num_lines = 3
   debug> l insert
   22:  {
   23:         struct macro_def    *defn;
   24:         struct macro_def    *node;
All the examples assume that the source files are in the current directory. If they aren't, you have to tell debug how to find them with the -s (for source path) option on the command line:
   $ debug -s macros/common:macros/mach -c core macros
or by setting the debugger variable %global_path:
   debug> set %global_path = "macros/common"
Setting %global_path overrides the source path set on the command line. In either case, the path is a colon-separated ordered list of directories that debug will look in to find the source files.
Next topic: Session 3: where did that data structure go?
Previous topic: Shorthand notations

© 2004 The SCO Group, Inc. All rights reserved.
UnixWare 7 Release 7.1.4 - 27 April 2004