OpenStep Development Tools
  Search only this book
Download this book in PDF

Debugging an OpenStep Application

A

The SPARCworks Debugger is an interactive, window-based, source code and machine-instruction level debugging tool. It provides dynamic analysis for observing run-tme program behavior. The Debugger gives you complete control of the dynamic execution of a program, including the collection of performance data.
The Debugger provides the same functionality as dbx, the command-line debugging tool, and you can enter dbx commands in the Command Pane of the Debugger base window.
To debug an OpenStep application, click on the Debug button in the project window for the application in Project Builder. If the project has not been built yet, it is built first. If the project builds successfully, then the application is run in debug mode and the SPARCworks Debugger starts up. See the SPARCworks manual Debugging a Program for details on using the Debugger windows.

Note - If the project has already been built, you can Alt-click on the Debug button to run the application under the Debugger.

Debugger Objective C Support

Release 3.1 of the SPARCworks Debugger includes support for Objective C applications, such as those developed using OpenStep.

Dynamic Types

In Objective C an object pointer has two types:
  • its static type, which is defined in the source code
  • its dynamic type, which is known at run-time
The Debugger can provide information about the dynamic type of an object when you use the print, display, inspect, and whatis commands with the -d option, or when you have set the dbx customization variable output_dynamic_type to on. If you use the +d option, the commands will use the static type.
It is recommended that you put dbxenv output_dynamic_type on in your ~/.dbxrc file when debugging Objective C programs.

Finding Methods and Using Method Names in Non-expression Commands

The following are non-expression commands:
stop in
funcs
whatis
list
edit

Use the funcs command (with a regular expression) to find methods and functions that the Debugger knows about and to print them in a format the Debugger accepts. Use the dbx command help funcs for more information on the funcs command.
If the process is active, the Debugger uses the run-time system to look up a method, otherwise it uses static information (stabs).

Setting Breakpoints

The Debugger accepts the following variations of the stop command for setting breakpoints in Objective C methods:
stop in -[Test ival:second:]

stop in +[Test alloc]
stop in [Test ival:second:]
stop in [obj ival:second:]// through an object (only if active
                                   process)
stop in ``ival:second:         // searches all classes for ival:second:
stop in ival:second:           // only if dbxenv scope_look_aside is `on'
stop inmethod ival:second:// stops in all methods with that name

If the process is not active, use the following syntax for category methods:
stop in -[Test(Cat1) catmethod:second:]

Calling Objective C Methods

All Objective C instance methods must be called through an object. The following are some valid variations of calling Objective C methods:
call [obj ival: 30]            // calling instance method with parameter
call [self ival: 30]           // use self if stopped inside a class
call [Test alloc]              // calling class method

Recovering from a Run-time System Crash

The Debugger calls the Objective C run-time system to look up methods and, if output_dynamic_type is on, to find the dynamic type of an object. In some cases this can cause a crash of the run-time system. The Debugger can usually recover if you use the pop command. Use the where command and then the pop command to unwind frames from the stack. You can also use the kill command to return to a previous Debugger level.

Sample .dbxrc File

Your ~/.dbxrc file is read automatically if it exists when the Debugger starts up. The following is a sample .dbxrc file for debugging Objective C applications. This file is located in /usr/openstep/etc. To have the Debugger read this file when it starts up, add source /usr/openstep/etc/.dbxrc to your .dbxrc file.
##################################################################
#               Objective C settings                              #
##################################################################

language objc
dbxenv scope_look_aside on // sets the dbx customization variable
                                   scope_look_aside to on (find static
                                   symbols even when not in scope)
dbxenv output_dynamic_type on // sets the dbx customization variable
                                   output_dynamic_type to on (display,
                                   inspect, print, whatis commands use
                                   dynamic type of object)

# do
#    call objc_enableMessageSendDebug(1)
# to enable the tracing of messages in objc_msgSend.  This tracing is
# very fast and flexible.  The above command will echo back all the
# info you need to use this feature.

function objchelp
{
    echo "Add 'source /usr/openstep/etc/.dbxrc' to your ~/.dbxrc
file"
    echo "  to define helpful Objective C functions, aliases and
buttons."
    echo "  Look at this file in an editor to see what it contains."
    echo "For more help, enter:"
    echo "      help        general dbx help"
    echo "      help ObjC   more Objective C help"
    echo "      help FAQ    dbx - gdb correspondences, and other
information"
}

function memon # stop if an object is freed too many times. VERY
SLOW!!
{

    call [NSAutoreleasePool enableDoubleReleaseCheck: 1]
    stop in _NSAutoreleaseInconsistency
    status
}

function memoff
{
    call [NSAutoreleasePool enableDoubleReleaseCheck: 0]
}

function defbrks  # breakpoints that catch errors
{
    language objc
    stop in abort
    stop in -[NSObject doesNotRecognizeSelector:]
    stop in +[NSAssertionHandler currentHandler]
    stop in -[NSAssertionHandler
handleFailureInMethod:object:file:lineNumber:description:]
    stop in -[NSAssertionHandler
handleFailureInFunction:file:lineNumber:description:]
    stop in -[NSException raise]
    stop in DPSDefaultErrorProc
    stop in DPSCantHappen
    stop in _exit
}

function morebrks  # other helpful places to breakpoint
{
    stop in NSLog
    stop in _XErrorHandler
    stop in -[Zombie forward::]
}

function allbrks  # set breakpoints to catch errors

{
    defbrks
    morebrks
}

function pselfvar
{
    print self->${1}
}

function pdesc
{
    print [[$* description] cString]
}

function pnsstring
{
    print [$* cString]
}

function prstar
{
    print -r *($*)
}

function pcounts # print retain count and number of autoreleases of
$1
{
    print [$* retainCount]
    print [NSAutoreleasePool _numberOfObjectsIdenticalTo: $*]
}

# print string of an NSText object

dalias ptext print [[!1 text] cString] // sets dbx alias ptext to
                                   print string of an NSText object

# print string of an NSCStringText object
dalias pcs   print [[!1 cStringTextInternalState]->_string cString]
                               // sets dbx alias pcs to print string of
                                   an NSCStringText object
dalias pns pnsstring           // sets dbx alias pns for psstring command
dalias pd  pdesc               // sets dbx alias pd for pdesc command
dalias prs prstar              // sets dbx alias prs for prstar command

alias typeof="print -l ((NSObject *)!:*)->isa->name"  // sets dbx
                                   alias typeof for printing type of
                                   current object

dalias currwin "print -l [(NSView *)[NSView focusView] window]"
                               // sets dbx alias currwin for printing
                                   current window

dalias flushcurrwin "print -l [((NSWindow *)[(NSView *)[NSView
focusView] window]) _forceFlushWindowToScreen]" // sets dbx alias
                                   fluchcurrwin for synchronous flushing
                                   of current window's off-screen buffer
                                   to screen

button expand whatis       // adds whatis button command;if selected
                                   characters begin with alphanumeric
                                   character, $, or _, then expands
                                   selection and uses as target
button expand prstar           // adds prstar button command;if selected
                                   characters begin with alphanumeric
                                   character, $, or _, then expands
                                   selection and uses as target
button expand pselfvar         // adds pselfvar button command;if
                                   selected characters begin with
                                   alphanumeric character, $, or _, then
                                   expands selection and uses as target
button expand pnsstring        // adds pnsstring button command;if
                                   selected characters begin with
                                   alphanumeric character, $, or _, then

                                   expands selection and uses as target
button expand pcounts          // add pcounts button command;if selected
                                   characters begin with alphanumeric
                                   character, $, or _, then expands
                                   selection and uses as target
button expand pdesc            // add pcounts button command;if selected
                                   characters begin with alphanumeric
                                   character, $, or _, then expands
                                   selection and uses as target
button ignore defbrks          // adds defbrks button command; ignores
                                   current mouse selection for command

##################################################################
#              General purpose settings                          #
##################################################################

toolenv cmdlines 20            // sets the number of lines in the
                                   command subwindow to 20
dbxenv step_events on          // sets the dbx customization variable
                                   step_events to on to allow breakpoints
                                   while stepping or "nexting"
dbxenv suppress_startup_message 4.0  // sets the dbx customization
                                   variable suppress_startup_message to
set -o ignoresuspend     # uncomment to cause dbx to ignore ^Z
set -o emacs             # uncomment to enable emacs-style command
                               editing
#set -o vi               # or uncomment this line for vi-style editing

function attach  # attach to a running process
{
    typeset PIG="$(/bin/ps -ef | /bin/egrep ${1} | /bin/egrep -v
egrep | /bin/head -1 | /bin/awk '{ print $8 " " $2 }')"
    debug $PIG
}

function collOn   # enable collector modes
{
    collector work_set mode on

    collector profile mode stack
}

function ff
{
   where -f $(frame) 1
   list
}

function penviron  # dump the environment variables of the target
process
{
    [ -z "$1" ] || { echo "$0: unexpected argument" >&2 && return; }
    typeset -i i=0
    typeset env="((char **)$[(char**)environ])"
    while :
    do
        x=$[($env)[$i]]
        echo "$i: " "${x#0x*\ }"
        case "$x" in
        *\(nil\)*)    break;;
        esac
        ((i += 1))
    done
}

PS1="$SMSO(dbx !)$RMSO " # reverse-video prompt with history number

function _cb_prompt
{
   if $mtfeatures
   then    # set prompt for MT debugging
           PS1='${SMSO}${thread} ${lwp}:!${RMSO} '

   else    # set prompt for non-thread debugging
           PS1='${SMSO}dbx:!${RMSO} '
   fi
}

function hex    # print arg in hex
{
    : ${1?"usage: $0 <expr>  # print <expr> in hex"}
    typeset -i16 x
    ((x = $[(int)$*]))
    echo - $* = $x
}
typeset -q hex

function hexdump         # dump $2 (default: sizeof $1) bytes in hex
{
    : ${1?"usage: $0 <exp> [<size>]  # dump <size> bytes in hex"}
    typeset -i16 p="$[(void *)&$1]"                     # address of $1
    typeset -i s="${2:-$[sizeof ($1)]}" >/dev/null 2>&1 # number of
bytes
    builtin examine $p/$[(${s:-4}+3)/4]X
}
typeset -q hexdump

function pg # print process status by name
{
    /bin/ps -ef | /bin/egrep ${1} | /bin/egrep -v egrep
}

regs() # print register contents
{
    case $# in
    0)  x &$g0/32X; x &$y/X; x &$psr/X; x &$pc/X; x &$npc/X ;;
    *)  for i

            do reg=\$$i; x &$reg/X
        done ;;
    esac
}

dalias p print                 // sets dbx alias p for print command
dalias w where                 // sets dbx alias w for where command
dalias br where                // sets dbx alias br for where command
dalias ww where -q             // sets dbx alias ww for where -q (quick
                                   traceback) command
dalias fr frame                // sets dbx alias fr for frame command
dalias b stop in               // sets dbx alias b for stop in command
dalias ba stop at              // sets dbx alias ba for stop at command
dalias si stop in              // sets dbx alias si for stop in command
dalias sa stop at              // sets dbx alias sa for stop at command
dalias sic stop inclass        // sets dbx alias sic for stop inclass
                                   command
dalias sif stop infunction// sets dbx alias sif for stop
                                   infunction command
dalias sim stop inmember       // sets dbx alias sim for stop inmember
                                   command
dalias sm stop modify          // sets dbx alias sm for stop modify
                                   command
dalias sr stop returns         // sets dbx alias sr for stop returns
                                   command
kalias cc="clear;cont"         // sets Korn alias cc for clear command
                                   followed by cont command
dalias cl clear                // sets dbx alias cl for clear command
dalias ib status               // sets dbx alias ib for status command
dalias st status               // sets dbx alias st for status command
dalias d delete                // sets dbx alias d for delete command
dalias r run                   // sets dbx alias r for run command
dalias c cont                  // sets dbx alias c for cont command
dalias s step                  // sets dbx alias s for step command
dalias su step up              // sets dbx alias su for step up command

dalias n next                  // sets dbx alias n for next command
dalias di handler -disable// sets dbx alias di for handler
                                   -disable command
dalias en handler -enable // sets dbx alias en for handler -enable
                                   command
dalias N nexti                 // sets dbx alias N for nexti command
dalias S stepi                 // sets dbx alias S for stepi command
dalias q quit                  // sets dbx alias q for quit command
dalias tiny toolenv srclines 16; toolenv cmdlines 8// sets dbx alias
                                   tiny to 16 lines in the source
                                   subwindow and 8 lines in the command
                                   subwindow
dalias mid toolenv srclines 25; toolenv cmdlines 25// sets dbx alias
                                   mid to 25 lines in the source
                                   subwindow and 25 lines in the command
                                   subwindow
dalias big toolenv srclines 33; toolenv cmdlines 14// sets dbx alias
                                   big to 33 lines in the source
                                   subwindow and 14 lines in the command
                                   subwindow
dalias und_all undisplay
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20  // sets dbx
                                   alias und_all to undo display commands
                                   1 through 20
dalias insense dbxenv case insensitive  // sets dbx alias insense to
                                   make case insignificant in variable
                                   and function names
dalias sense dbxenv case sensitive  // sets dbx alias sense to make
                                   case significant in variable and
                                   function names

button lineno cont at          // adds button command cont at; uses
                                   line number associated with current
                                   selection as target of command
button ignore step up          // adds button command step up; ignores
                                   current mouse selection for command
button ignore tiny             // adds button command tiny; ignores
                                   current mouse selection for command
button ignore mid              // adds button command mid; ignores

button ignore big              // adds button command big; ignores
                                   current mouse selection for command
button ignore quit             // adds button command quit; ignores

Helpful User Default Variables to Set with dwrite

The following user default variables, which you can set with the dwrite command, may be useful in debugging your application:
NSEnableAutoreleasePool
NSEnableDoubleReleaseCheck
NSHideOnDeactivateEnabled
NSPauseAtStartup
NSSetPoolThreshold
NSShowAllViews
NSShowAllWindows
NSShowDrawTimes
NSShowEvents
NSShowPS
NSShowWindowInfo
NSShowXEvents
NSTrapIllegalFloatingPointOps

Tracing Objective C Objects

You can monitor Objective C messages being sent by objects in your application by calling the function objc_messageSendDebug. This function allows you to filter on different message attributes, and also stop at breakpoints when a certain filter matches the current message.
This facility is particularly useful for finding memory allocation errors and performance problems.
You can enable messageSendDebug in three ways:

Invoking messageSendDebug Using dwrite Commands

You can automatically invoke messageSendDebug at execution time by using one of the following dwrite commands.
This command turns on messageSendDebug, but no messages are sent until a filter is set:
dwrite AppName NSEnableMessageSendDebug YES

This command suspends the display of messages, even if filters are set:
dwrite AppName NSEnableMessageSendDebug NO

This command turns on messageSendDebug, and adds a generic filter to show all messages:
dwrite AppName NSEnableMessageSendDebug ALL

This command displays an explanation of how to use this facility:
dwrite AppName NSEnableMessageSendDebug HELP

Adding Individual Message Filters

You can add individual message filters with the following commands:
dwrite AppName NSMessageSendDebugFilter "ClassName | *,
[+ | -]selectorName | [+ | -]*, receiverID[hex or dec] | *, YES | NO
dwrite AppName NSMessageSendDebugFilter "GENERIC_FILTER"
dwrite AppName NSMessageSendDebugFilter1 "(AnotherSelectorName,
...)"
dwrite AppName NSMessageSendDebugFilterN "(SelectorNameN, ...)"

YES or NO applies to whether or not to call objc_messageMatchedFilter() when a filter matches. Enter YES if you want your program to hit a breakpoint when any filter matches (see "Setting a Breakpoint on a Filter Match" on page A-17).
GENERIC_FILTER shows all messages.
If ClassName in the filter is *, any class counts as a match.
If selectorName in the filter is *, any selector counts as a match.
If selectorName in the filter is preceeded by a "+" or "-", only class methods, or instance methods (respectively) are considered matches.
If receiverID in the filter is *, any receiver counts as a match.
If ClassName, selectorName, and receiverID are all *, all messages are considered matches.

Controlling Call Level Indentation

By default, the call level (nested level) of each method is shown in the matched filter output by indenting the line. At times this may be undesirable. To disable or enable this feature, use one of the following commands to control (typically turn off) call level indentation in all applications.
dwrite AppName NSEnableFilterCallLevelIndentation YES | NO
dwrite NSGlobalDomain NSEnableFilterCallLevelIndentation YES | NO

This setting effects console output only, and has no effect on external debug-monitoring applications like ObjectDebug.

Invoking messageSendDebug from a Program or the Debugger

For detailed information, see the file /usr/openstep/include/objc/objc-debug.h. Enabling messageSendDebug adds to, and does not preclude, filtering options you have set using dwrite.

Enabling messageSendDebug

To enable messageSendDebug, call one of the following methods:
objc_enableMessageSendDebug(EnableDebug)

objc_enableMessageSendDebug(EnableDebugShowAllMessages)
objc_enableMessageSendDebug(EnableDebugSilently)
objc_enableMessageSendDebug(DisableDebug)
objc_enableMessageSendDebug(DisableDebugSilently)

The following methods are equivalent to the above methods:
objc_enableMessageSendDebug(1)
objc_enableMessageSendDebug(2)
objc_enableMessageSendDebug(3)
objc_enableMessageSendDebug(0)
objc_enableMessageSendDebug(-1)


Note - You may want to disable this mechanism before making method calls in your debugger, as those method calls will get traced as well!

Adding Filters

To add filters, you can call one of the following methods:
objc_addFilterFromString(const char *filterString)
objc_addFilterForClass(const char *className)
objc_addFilterForSelector(const char *selectorName)
objc_addFilterForReceiver(id receiver)

objc_addFilterFromString has the same syntax as the NSMessageSendDebugFilter dwrite command, with the addition of a FilterID field as the first value. This field lets you uniquely identify the filter.
The filter string format looks like this:
objc_addFilterFromString("FilterID, ClassName | *,
[+ | -]selectorName | [+ | -]*, receiverID[hex or dec] | *, YES | NO

or this:
objc_addFilterFromString("GENERIC_FILTER")

YES or NO applies to whether or not to call objc_messageMatchedFilter() when a filter matches. Enter YES if you want your program to hit a breakpoint when any filter matches (see "Setting a Breakpoint on a Filter Match" on page A-17).
GENERIC_FILTER shows all messages.
If ClassName in the filter is *, any class counts as a match.
If selectorName in the filter is *, any selector counts as a match.
If selectorName in the filter is preceeded by a "+" or "-", only class methods, or instance methods (respectively) are considered matches.
If receiverID in the filter is *, any receiver counts as a match.
If ClassName, selectorName, and receiverID are all *, all messages are considered matches.

Controlling Call Level Indentation

By default, the call level (nested level) of each method is shown in the matched filter output by indenting the line. At times this may be undesirable. To disable or enable this feature, call the following method:
objc_enableFilterCallLevelIndentation(0 | 1)

This setting effects console output only, and has no effect on external debug-monitoring applications. It is unnecessary if the feature has already been enabled or disabled with dwrite.

Removing Filters

To remove all filters, call the following method:
objc_removeAllFilters()

Disabling Filters

To temporarily disable all filters, call the following method:
objc_enableMessageSendDebug(DisableDebug[0])

Setting a Breakpoint on a Filter Match

If you want your program to hit a breakpoint when any filter matches, call the following method:
objc_callMessageMatchedFilter(0 | 1)


Note - objc_callMessageMatchedFilter sets this flag for all existing filters. To set the flag for individual filters, use the objc_addFilterFromString method to create your filter, or call objc_callMessageMatchedFilter after setting some filters, and then set the rest of your filters.

Once you continue program execution, a string is printed indicating that the current message or receiver matched one of the set filters.
After this string is printed, the function objc_messageMatchedFilter() is called by the Objective C runtime system.
You can put a breakpoint at objc_messageMatchedFilter() to get a backtrace.

Examples

Example 1:

To see all the messages sent to the NSAutoreleasePool class, either enter the following dbx commands in the Debugger Command Pane:
call objc_enableMessageSendDebug(1)
call objc_addFilterForClass("NSAutoreleasePool")

or use the following dwrite commands at execution time:
dwrite AppName NSEnableMessageSendDebug YES
dwrite AppName NSMessageSendDebugFilter "NSAutoreleasePool,*,*,NO"

Example 2:

To see all the addObject: messages sent to the NSAutoreleasePool class, and have Objective C call objc_messageMatchedFilter() when that message is sent (so you can hit a breakpoint there), either enter the following dbx commands in the Debugger Command Pane:
call objc_enableMessageSendDebug(1)
call
objc_addFilterFromString("1,NSAutoreleasePool,addObject:,*,YES")

or use the following dwrite commands at execution time
dwrite AppName NSEnableMessageSendDebug YES
dwrite AppName NSMessageSendDebugFilter1
"NSAutoreleasePool,addObject:,*,YES"

Notice the lack of the first parameter, the filterID, which is automatically generated in this case by the number that is appended to the dwrite key + 1000 (for example. "1001").

Example 3:

To see all the class method calls (as opposed to instance method calls) sent to any object, and not show call level indentation, either enter the following dbx commands in the Debugger Command Pane:
call objc_enableMessageSendDebug(1)
call objc_addFilterForSelector("+*")
call objc_enableFilterCallLevelIndentation(0)

or use the following dwrite commands at execution time:
dwrite AppName NSEnableMessageSendDebug YES
dwrite AppName NSMessageSendDebugFilter "*,+*,*,NO"
dwrite AppName NSEnableFilterCallLevelIndentation NO

Implementing Your Own Filtering Mechanism

If you want to implement your own filtering mechanism, you can call the following function:
objc_setMessageSendFilterFunction(void
(*customFilterFunction)(Class receiverClass,id receiver, SEL
selector,void *callLevel, void *threadID))

This function takes a pointer to a filterFunction. After calling this function, every message (objc_msgSend) that is sent will go through your own filter function. You can then implement your own filtering system.
You can also call the following function from your filter function, in case you want to do the normal filtering stuff but tweak a few things first.:
objc_defaultMessageSendFilterFunction()

You can get a linked list of all the currently set filters by calling the following:
objc_filterList(void)

Debugging Applications Using Optimized Libraries

If you compile an application with -g but use libraries not compiled with -g, the Debugger does not know the prototypes of methods defined in the libraries. This means it does not know the types of the returned values, nor of the parameters. It assumes the types are int.
For example, assume that set1 is an NSSet, You could use casts to tell the Debugger the return types of methods:
dbx:3 whatis set1
@interface NSSet *set1;
dbx:4 p [set1 description]
[set1 description] = -283014864
dbx:5 p (NSString *)[set1 description]
(@interface NSString *) [set1 description] = 0xef218bd0
dbx:6 p [(NSString *)[set1 description] cString]
[(@interface NSString *) [set1 description] cString] = -281204711
dbx:7 p (char *)[(NSString *)[set1 description] cString]
(char *) [(@interface NSString *) [set1 description] cString] =
0xef3d2841 "NSConcreteSet(a, b)"

In order to obviate the need for these casts, Project Builder includes a special module named dbxInfo.o. This module contains debugging information for all the methods defined in the Application Kit and Foundation Kit libraries. When you do a debug build using the Project Builder Makefiles, this module is automatically linked into your application. In order to make this information available to the Debugger after it has started, Project Builder issues the following command to the Debugger as it is starting up:
module dbxInfo.o

This command causes the Debugger to read in the debugging information contained in dbxInfo.o so it is available when the Debugger has to determine the return types and parameter types of methods invocations.