DOC HOME SITE MAP MAN PAGES GNU INFO SEARCH PRINT BOOK
 
Using the command line interface of debug

Session 5: why didn't that signal handler work?

Now that you have found all the (known) bugs in macros, you can move on to debugging sget. sget.c compiles cleanly and is lint-free, so you start using it to extract your files:

   $ cc -g -o sget sget.c
   $ cat files
   src/file1
   src/s.main.c
   $ sget <files
   file1: 0 characters, 0 words, 0 lines
   Caught signal,  removed
Not only did the program work incorrectly, it also left the copied file lying around, as you can see by listing file1:
   $ ls file1
   file1
It looks like there may be two problems (and two clues) here:

  1. It's getting a signal when it shouldn't. This may be because file1 has nothing in it (0 characters, 0 lines).

  2. The signal handler isn't cleaning up after getting a signal. The clue here is that the last line of output says removed instead of file1 removed. You may suspect that the problem is related to the missing file name.
Check the problems one at a time.

  1. Type debug to start the debugger.

  2. Run the program. debug will let you know when the program gets a signal:
       debug> create sget <files
    
    The debugger displays the following:
       New program sget (process p1) created
       HALTED p1 [main in sget.c]
       131:       (void) signal(SIGHUP, handler);
    
    Run the program.
       debug> run
    
    The debugger displays the following:
       file1: 0 characters, 0 words, 0 lines
       SIGNALED 8 (fpe) in p1 [getstats in sget.c]
       108:    (void) printf("\taverage characters per line: %d\n",
    
    From the event notification, you can see that the signal is fpe (or SIGFPE, for arithmetic exception). On many processors, trying to divide by zero will generate SIGFPE, and it looks like that's what's happening here:
       debug> list -c 4
       108:     (void) printf("\taverage characters per line: %d\n",
       109:            char_cnt/line_cnt);
       110:     (void) printf("\taverage words per line: %d\n",
       111:            word_cnt/line_cnt);
    

  3. Look at line_cnt to be sure:
       debug> print line_cnt
       0
    

  4. It is not necessary to print averages for an empty file, so this bug is easy to fix. Exit the debugger.

  5. Edit sget.c as follows:
       if (line_cnt)
            {
             (void) printf("\taverage characters per line: %d\n",
       		      char_cnt/line_cnt);
             (void) printf("\taverage words per line: %d\n\n",
       		      word_cnt/line_cnt);
            }
    

  6. Recompile:
       cc -g -o sget sget.c
    
    Now you can look at the second problem -- the one with the signal handler. However, since you just fixed the bug that caused the signal handler to be invoked, you have to fake it by generating a signal within debug.

  7. Type debug to restart the debugger.

  8. Create sget with the newly compiled program:
       debug> create sget <files
    
    The debugger displays the following:
       New program sget (process p1) created
       HALTED p1 [main in sget.c]
       134:       (void) signal(SIGHUP, handler);
    

  9. Run the program up to getstats:
       debug> stop sget.c@getstats
    
    The debugger displays the following:
       EVENT [1] assigned
    
    Run the program:
       debug> run
    
    The debugger displays the following:
    STOP EVENT TRIGGERED: sget.c@getstats in p2 [getstats in sget.c]
    106:     (void) printf("%s: %d characters, %d words, %d lines\n",
    

  10. Tell debug to send a signal to the process; this is similar to sending a signal at the shell level:
       debug> kill sigfpe
    


    NOTE: See ``Signals'' for more information. See signal(5) for a list of all signals and their meanings.

    The next time you let the process run or step, it will receive the signal:

       debug> run
       SIGNALED 8 (fpe) in p2 [getstats in sget.c]
       106: (void) printf("%s: %d characters, %d words, %d lines\n",
    

  11. It stopped in the same place because it received the signal as soon as it started to run. Stop at the signal handler:
       debug> stop handler
    
    The debugger displays the following:
       EVENT [2] assigned
    
    If you let it run again it will get to the signal handler:
       debug> run
    
    The debugger displays the following:
       STOP EVENT TRIGGERED: handler  in p2 [handler in sget.c]
       120:    (void) fprintf(stderr, "Caught signal, %s removed\n",
       debug> l -c3
       120:    (void) fprintf(stderr, "Caught signal, %s removed\n",
       121:           base_name);
       122:    (void) remove(base_name);
    

    handler is using base_name, which should be the name of the file; instead, it is the null string:

       debug> print base_name
       ""
    
    base_name should have been set in main before the call to getstats, either it hasn't been set or it got overwritten somewhere.

  12. Back up and see what's going on.
       debug> create
    
    The debugger displays the following:
       p2 killed
       sget <files
       New program sget (process p3) created
       HALTED p3 [main in sget.c]
       134:       (void) signal(SIGHUP, handler);
    

  13. Run the program:
       debug> run
    
    The debugger displays the following:
    STOP EVENT TRIGGERED: sget.c@getstats  in p3 [getstats in sget.c]
    106:  (void) printf("%s: %d characters, %d words, %d lines\n",
    debug> print base_name
    ""
    

  14. Since base_name is wrong in getstats, the problem must be further back in main. Look at the current state of main's variables.

  15. Set the debugger's idea of the current frame (%frame) to the frame number for main (the frame number is the left-most column in the stack trace):
       debug> stack
    
    The debugger displays the following:
       Stack Trace for p3, Program sget
       *[0] getstats(file="file1")      [sget.c@106]
        [1] main(0x1, 0x804794c, 0x8047954)       [sget.c@156]
        [2] _start()    [0x8048662]
       

    debug> set %frame = 1

  16. Now when you type symbols or print, debug will use main's local variables instead of getstats':
       debug> print base_name
       "file1"
    


    NOTE: See ``Changing the current context'' for more information for more information on the effects of changing %frame.

  17. How could base_name have changed without the program running? The whatis command will show you where base_name is defined, along with the type of the symbol:
       debug> whatis base_name
       char *  [local variable in main]
       const char *    [file static variable in sget.c]
    
    Aha, there is more than one base_name. Type symbols -fl (for file and local scopes) to show the values of both base_names:
       debug> symbols -flv base*
    
    The debugger displays the following:
       Symbols for p3, Program sget
       Name            Location     Line        Value
       base_name        main          127        "file1"
       base_name        sget.c                   ""
    

  18. Exit the debugger.
main is setting a local variable named base_name, which hides base_name in the outer scope.

Signals

Three related commands -- signal, kill, and cancel -- give you control over signals. All three commands recognize signals by number or by name, which can be upper or lower case, with or without the SIG prefix, so these commands are equivalent:

   debug> signal 2
   debug> signal sigint
   debug> signal INT
signal (or sig) lets you specify the actions the debugger is to take when a process receives a signal. By default, debug catches all signals; it will stop the process and tell you what happened. You can get more information by creating an event with a command list for a particular signal:
   debug> signal sigint {stack; run}
You can also tell it to ignore a signal instead:
   debug> signal -i sighup
If the process received sighup, debug will not stop the process. If you tell debug to ignore sigfpe, it will let sget run and exit without stopping it in getstats.
Typing signal by itself will tell you which signals are caught and which are ignored:
   debug> signal
   Signal Disposition for p1, program sget
   1    sighup          ignored SIG_DFL
   2    sigint          caught  SIG_DFL
   ...
signal sig will reset catching an ignored signal:
   debug> signal sighup
   debug> signal
   Signal Disposition for p1, program sget
   1    sighup          caught  SIG_DFL
   ...

The fourth column displays the current signal handler associated with each signal in your program.

The -d option to the signal command will let you change the debugger's default behavior with respect to a specific signal. signal -d -i sig changes the default behavior to ignore sig; signal -d sig resets the default to catch sig. Any process created or grabbed by the debugger inherits this default signal behavior.


NOTE: The default settings changed by signal -d apply only to processes created or grabbed after the command is given. It does not change affect the signal state of any process already under the debugger's control.

kill and cancel are inverse operations; kill sig sends sig to the process and cancel sig ensures that the process will never receive it. There are two things to note about these commands:

Changing the current context

The ``current frame'' is the basis for the ``current context''; the current view you have into the state of the process. The value of the debugger variable %frame is the number of the current stack frame; in a stack trace the current frame is also indicated by an asterisk (*). debug resets %frame whenever the process stops. You may also change the current frame by changing %frame with the set or print commands. The other context variables, %file, %func, %line, %list_file, %list_line, %loc, and %db_lang, are also set whenever an event triggers or when you change %frame.


NOTE: This description of the current context is incomplete. It is more accurately determined by the current frame within the current thread (%thread) and the current process (%proc). See ``ps and the current object'' for more information on %proc and %thread.

The current context determines the following three aspects of the debugger's behavior:

1. Initial location for source listings & disassembly

Setting %frame sets %list_line to the statement currently being executed in the new stack frame. You can see the current statement for each frame in the stack trace. If you type list (with no arguments) after changing %frame, it will start the display from the new value of %list_line. %loc is the value of the location counter for the current frame, and works the same way with dis.

   debug> print %frame, %func, %list_line
   2 "getstats" 106
   debug> list -c1
   106:  (void) printf("%s: %d characters, %d words, %d lines\n",
   debug> set %frame = 1
   debug> print %func, %line
   "main" 156
   debug> list -c1
   156:                getstats(base_name);

You may also change the context by setting %func, which indirectly sets %frame and the other context variables:

   debug> set %func = process_def
   debug> print %frame, %line
   2 227
The drawback to using %func instead of %frame is that you can not differentiate between multiple occurrences of a recursive function; debug will always pick the topmost frame on the stack for that function.

Changing the context does not affect the stack trace; you will still see the entire stack trace even if the current frame is somewhere in the middle, so you can always get back to the topmost frame.

2. Symbol visibility

symbols prints the local symbols for the current frame. By changing %frame you can walk up and down the stack and look at the variables in any active function.
The output of symbols -f (showing file static symbols) may also change; functions defined in different files will have different statics visible to them. The following example uses macros from sessions 1 through 4:

   debug> stack
   Stack Trace for p1, Program macros
   *[0] insert(name=0x804abec, list=0x804ac0c)       [macro.c@56]
    [1] add_def(name=0x804abec, read_more=1) [main.c@211]
    [2] process_def()       [main.c@227]
    [3] main(argc=1, argv=0x80479cc, 0x80479d4)       [main.c@339]
    [4] _start()    [0x80485a2]
   debug> print %frame, %file
   0  "macro.c"
   debug> symbols -f
   Symbols for p1, Program macros
   Name            Location     Line
   root            macro.c         
   debug> set %frame = 3
   debug> print %frame, %file
   3  "main.c"
   debug> symbols -f
   Symbols for p1, Program macros
   Name            Location     Line
   Usage           main.c          
   add_def         main.c          
   gettok          main.c          
   lines           main.c
   next_tok        main.c          
   process_def     main.c          
   tokenize        main.c       
   debug> stack
   Stack Trace for p1, Program macros
    [0] insert(name=0x804abec, list=0x804ac0c)     [macro.c@56]
    [1] add_def(name=0x804abec, read_more=1)       [main.c@211]
    [2] process_def()      [main.c@227]
   *[3] main(argc=1, argv=0x80479cc,0x80479d4)     [main.c@339]
    [4] _start()    [0x80485a2]

3. Language for expression evaluation

The value of an expression (used in print, set, and stop commands and if and while statements) depends on both symbol visibility and the language used to evaluate the expression. The current language (%db_lang) may change with the context if your program combines files written in different programming languages (say, C and FORTRAN).


Next topic: Multi-process debugging
Previous topic: Watchpoints, breakpoints, and general stop expressions

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