Using the command line interface of debug

Session 1: why is my program in an infinite loop?

  1. Compile macros:
       $ cc -g -o macros main.c macro.c

  2. Since you are confident that it will work as you expected, run the program:
       $ cat test1
       define(TEST,this is a test)
       define(X,TEST test TEST)
       define(A1,1 2 3 4)  A1
       $ macros <test1

  3. After a significant amount of time has passed it becomes apparent that the program must be looping somewhere. You can interrupt it and do the following: The debugger command grab pid suspends process pid and brings it under the debugger's control. From then on, process pid will not do anything until you give debug another command allowing it to run. debug tells you what function was being executed when the process was suspended. In this example it was suspended in malloc.

    NOTE: See ``Grabbing processes'' for more information.

  4. Check to see what was calling malloc with the stack (or t) command:
       debug> stack
       Stack Trace for p1, Program macros
       *[0] malloc(0xc) [0x800187c1]
        [1] tokenize(buf="(TEST,this is a test)\n") [main.c@62]
        [2] gettok(read_more=1) [main.c@167]
        [3] main(argc=1, argv=0x80479cc, 0x80479d4) [main.c@333]
        [4] _start()    [0x80485a2]
    The most recently invoked function is always displayed at the top of the stack trace. Here, the process is in malloc(3), which was called by tokenize, called in turn by gettok, and so on. Since the point at which the process was suspended is random, when you try this, the process may be suspended in a different function, and your stack trace would not look exactly like this, but since the program is in a tight loop it should be similar. The program could conceivably be stuck in malloc, but it isn't likely because library functions, particularly frequently used ones like malloc, tend to be well tested. Since macros hasn't been tested at all, you can assume the bug is in the code for macros. Finding the bug is an iterative process and may take several tries.

    NOTE: See ``Stack traces'' for more information.

    You would like to let the program run until it is finished in malloc but before it does anything else in tokenize. But you are unsure of what option to use with the run command.

  5. Ask debug for help:
       debug> help run

  6. The run help message is displayed:
    run - set thread or process running.

    SYNOPSIS run [-p proclist] [-bfr] [-u location]

    DESCRIPTION Set the specified list of threads and processes (%thread or %proc by default) running. The -f option specifies that the debugger will run the thread or process in the foreground, i.e., it will wait until the threads or processes stop before returning control to the user. The -b option specifies background execution (no waiting). Control returns immediately to the user, and the thread or process is started.

    The -r option causes the thread or process to run until the current function returns. The -u option specifies a "location" to run to.

    Note that threads that are not currently running on a kernel light-weight process cannot be set running (such threads show up in the "Off LWP" state in the ps command).

    EXAMPLES run -p all -b run -r run -u 108

    SEE ALSO %wait, location, proclist, ps, step, thread

    NOTE: See ``Getting help'' for more information.

    NOTE: Successive runs continue execution from the point where the program stopped, not from the beginning. create re-starts the program.

  7. Type
       run -r

    to get out of malloc (-r stands for run to return). (If you grabbed the process while it was still in tokenize you shouldn't do the next step. If the program is suspended in something malloc calls, you will have to do it more than once.)

       debug> run -r
       HALTED p1 [tokenize in main.c]
       62:  token = (struct token *)malloc(sizeof(struct token));

  8. Look at what's going on in tokenize:
       debug> list
       62:  token = (struct token *)malloc(sizeof(struct token));
       63:  if (!token)
       64:  {
       65:          (void) fprintf(stderr, "malloc failed!\n");
       66:          exit(1);
       67:  }
       68:  token->string = 0;
       69:  token->next = 0;
       71:  if (!head)
    If the program is really looping forever the odds are good that if you let the program run it will come back to this point.

  9. Stop the program when it gets back to this point by defining a ``stop event'' (also commonly known as setting a breakpoint) on this line:
       debug> stop 62
       EVENT [1] assigned
    To be on the safe side you can also define a stop event at the beginning of the function. This will let you know if it leaves tokenize and comes back, which will suggest that the problem is in not in tokenize, but in gettok instead.
       debug> stop tokenize
       EVENT [2] assigned

  10. Before you let the program run, look at the local variables:
       debug> symbols -tv
       Symbols for p1, Program macros
       Name        Location  Line  Type           Value
       buf         tokenize   52   const char *   "(TEST,this is a test)\n"
       c           tokenize   52   char           40
       head        tokenize   52   struct token * 0x804a880
       len         tokenize   52   unsigned int   7
       next        tokenize   52   const char *   "(TEST,this is a test)\n"
       save_string tokenize   52   label          0x804881c
       tail        tokenize   52   struct token * 0x81c3784
       token       tokenize   52   struct token * 0x81c3784

    NOTE: See ``Printing symbols'' for more information.

  11. Run the program:
       debug> run
       STOP EVENT TRIGGERED: 62  in p1 [tokenize in main.c]
       62:         token = (struct token *)malloc(sizeof(struct token));

  12. The program has stopped at the same place. See what has changed:
       debug> symbols -tv
       Name        Location  Line  Type           Value
       buf         tokenize   52   const char *   "(TEST,this is a test)\n"
       c           tokenize   52   char           40
       head        tokenize   52   struct token * 0x804a880
       len         tokenize   52   unsigned int   7
       next        tokenize   52   const char *   "(TEST,this is a test)\n"
       save_string tokenize   52   label          0x804881c
       tail        tokenize   52   struct token * 0x81c3798
       token       tokenize   52   struct token * 0x81c3798

  13. next and c haven't changed at all. That seems illogical. Look at tokenize to see where they are used:
       debug> list tokenize
       52:   {
       53:           struct token        *head = 0;
       54:           struct token        *tail = 0;
       55:           struct token        *token;
       56:           const char          *next = buf;
       57:           char                c;
       58:           size_t              len;
       60:           while ((c = *next) != '\0')
       61:           {
The program is in a while loop that depends on *next. If next doesn't change, the program will never get out of the loop. It is reasonable to assume you have found the bug, so you can quit debug. However, before you leave debug kill the macros process. Since it was a running program that you grabbed, the normal behavior for debug when it exits is to release the process and let it keep on running. You'll have to kill it either at the shell level or in debug, so you might as well do it here to make sure it doesn't do something strange when it resumes running uncontrolled.
   debug> kill
   p1 killed
   No more processes.
   debug> quit

NOTE: You can use either quit, q or exit to get out of debug.

Grabbing processes

In addition to the grab command, you can grab a process directly from the debug command line, or by using its pathname in the /proc file system, so these examples are all equivalent:

   $ debug /proc/27221
   . . .
   $ debug 27221
   . . .
   $ debug
   debug> grab /proc/27221
   . . .
   $ debug
   debug> grab 27221
Of course, the normal security rules apply; you cannot get control over a process with a different effective user id from your own.

The debugger prints a message showing you the name of the program (usually the name of the object file) and a debugger-generated name for the process. The debugger maintains a hierarchy of program, process and thread; programs are at the top and each program is made up of one or more processes, which in turn, are made up of one or more threads (if the program is multi-threaded). For now, you may assume that there is only one of each, and that the program name and process name are synonymous, but the debugger displays its information using the process name where the program name might be ambiguous. The significance of the hierarchy becomes more apparent when debugging pipelines and the effects of fork(S), exec(S) and thr_create(3thread). (See ``Multi-process debugging''.)

By using grab and create (described later) you can debug different programs without having to exit and restart the debugger.

Stack traces

There are several things to note about stack traces:

Getting help

Help is always at your fingertips. Typing help command_name will produce a synopsis of the command's syntax, a brief description of what the command does, and a list of other related topics. Examples of two of those other topics are sysnames and format:

   debug> help sysnames
will list all the system call names that the syscall command accepts, and
   debug> help format
will produce a description of the print command's formatting capability. Typing help (or h) by itself will produce a list of all the topics you can get help on, including all the commands.

Printing symbols

The symbols (or syms) command with no options will print out the names of the local variables that are visible from the current scope:

   debug> symbols
   Symbols for p1, Program macros
   Name         Location    Line
   buf          tokenize    52
   c            tokenize    52
   head         tokenize    52
   len          tokenize    52
   next         tokenize    52
   save_string  tokenize    52
   tail         tokenize    52
   token        tokenize    52
You can restrict the symbols displayed by giving symbols a pattern [using sh(C) syntax for pattern matching]:
   debug> symbols [b-h]?*
   Symbols for p1, Program macros
   Name         Location    Line
   buf          tokenize    52
   head         tokenize    52
Adding the -t or -v (or both) options will make debug print the types or values (or both):
   debug> symbols -tv t*
   Symbols for p1, Program macros
   Name         Location    Line   Type              Value
   tail         tokenize    52     struct token *    0x81c3798
   token        tokenize    52     struct token *    0x81c3798
Other options to symbols make it display other groups of symbols. For example, the -f option (for file static) displays static variables visible from the current compilation unit:
   debug> symbols -ft
   Symbols for p1, Program macros
   Name         Location    Line   Type
   Usage        main.c             char *
   add_def      main.c             void(struct token *, int)
   gettok       main.c             struct token *(int)
   lines        main.c             int
   next_tok     main.c             struct token *
   process_def  main.c             void()
   tokenize     main.c             void(const char *)
There are two things to note about the output from symbols:

Shorthand notations

t, r, l and b are shorthand notations for stack, run, list and stop, respectively. There are several other built-in shorthands (or aliases) available. You can create your own or redefine the built-ins with the alias command:

   debug> alias lm list main
   debug> lm
   300:        {
   301:                struct token        *definition;
   302:                struct token        *name;
   303:                struct token        *token;
   304:                struct arglist      *arguments;
   305:                char                *argptr;
   306:                int                 i;
   308:                for (i = 1; i < argc; ++i)
   309:                {
alias with no arguments will list all the aliases, both built-in and user-defined:
   debug> alias
   b => stop
   exit => quit
   h => help
   history => fc -1
   l => list
   lm => list main
   n => step -o
   next => step -o
   ni => step -io
   p => print
   q => quit
   r => run
   rr => fc -e -
   s => step
   si => step -i
   sig => signal
   syms => symbols
   sys => syscall
   t => stack
The commands and their built-in aliases are interchangeable. This tutorial normally uses the complete command names for clarity.

debug also supports ksh-style command history and command-line editing. Both vi and emacs editing modes are supported, and, like ksh, debug determines the default editing modes from the VISUAL and EDITOR environment variables. For information on using this capability see ksh(C).

Next topic: Session 2: why did it dump core?
Previous topic: Single-process debugging

© 2005 The SCO Group, Inc. All rights reserved.
SCO OpenServer Release 6.0.0 -- 02 June 2005