Chapter 18. Debugging

WHAT'S IN THIS CHAPTER?

  • Running your application

  • Starting your application under the control of the debugger

  • Setting breakpoints and creating smart breakpoints

  • Examining, changing, and customizing the display of variables

  • Debugging a program running on a remote system

Getting your project to build is sometimes only half the battle. OK, let's be honest; it's often much less than half the battle. It's a cruel fact of programming that your application will have bugs, design flaws, and unexpected behavior. Object-oriented languages, modeling, good design, rigorous coding standards, and unit testing can reduce the number of bugs that creep into your code, but unless your application is trivial, it doesn't matter how careful you've been, how many code reviews you've done, or how many "best practices" you've employed. Someday your application is simply not going to work the way you want it to, and you'll have to find out why. The tool of choice to answer that question is the debugger.

The debugger is a magic window into your application. You can literally watch the internals of your program at work. You can stop your application, examine the values of variables, the state of other threads, and much more. Xcode even allows you to alter values and fix some code while your application is still running — the equivalent of performing a heart transplant on an athlete who's in the middle of running a marathon.

RUNNING YOUR APPLICATION

Before getting into debugging, this section covers the trivial case of simply running your application. You can launch your program, more or less as it would be launched from the Finder or shell, using these commands:

  • Build

    RUNNING YOUR APPLICATION
  • Build

    RUNNING YOUR APPLICATION
  • Run

    RUNNING YOUR APPLICATION
  • Run

    RUNNING YOUR APPLICATION

All of these commands launch the active executable produced by the most recent build. You choose the active executable much as you do the active target and build configuration, described in the "Choosing the Active Executable" section.

Note

If your "Run" command has turned into a "Debug" command, it's because you have breakpoints enabled. With breakpoints enabled, Run

RUNNING YOUR APPLICATION

The run commands that also say "Breakpoints Off" first deactivate all breakpoints before running the executable, equivalent to first choosing Run

RUNNING YOUR APPLICATION

The two Build and Run commands build the active target before starting your program. This is the most common way of running an application — notice that they have the simpler key combinations. It first ensures that the target is fully built before starting your program. Remember to save your source files first, or set the Always Save setting in the Building tab of Xcode's preferences.

The run commands are also accessible via your toolbar in the form of the Run/Debug button and the Build and Run/Debug buttons, shown in Figure 18-1. The Option key changes the action of the buttons from running with (breakpoints enabled) and without (breakpoints disabled) the debugger.

FIGURE 18-1

Figure 18-1. FIGURE 18-1

Monitoring Your Process

Running a program opens the Debugger Console window, shown in Figure 18-2. You can reopen this window at any time using the Run

Monitoring Your Process
FIGURE 18-2

Figure 18-2. FIGURE 18-2

You can modify these I/O connections, along with many other aspects of your program's execution environment. See the "Custom Executables" section later in this chapter for more details.

Stopping the Executable

Whenever the executable is running, the Run/Debug button changes into a Stop button. You can unceremoniously terminate the running program (equivalent to a kill -KILL command or a Force Quit) using the Stop button or by choosing the Run

Stopping the Executable

Choosing the Active Executable

The active executable determines what program is launched when you choose any of the run or debug commands. You can change the active executable using the Project

Choosing the Active Executable

In a number of circumstances this might not happen automatically. This is especially true when switching to or from an active target that does not produce an executable. Switching from an application target to an aggregate target (which produces nothing) or a framework target (that doesn't produce anything that can be executed on its own) does not change the active executable. In these circumstances, you'll need to choose the active executable yourself.

You may sometimes want to launch a different executable from the one produced by the target. Say you are working on the Server application, but need to test it using the Client application. Select the Server as the active target and then switch the active executable to the Client. When you Build and Run, the Server gets built, but it's the Client that gets launched.

For the vast majority of projects, the executable produced by your application target is the executable that you want to run or debug. If you have a special situation, you may need to modify the environment in which your executable runs or create a custom executable. Both are explained toward the end of this chapter in the "Custom Executables" section. For now, you'll concentrate on debugging simple executables produced by application and command-line targets. Everything here applies to custom executables as well.

DEBUG ANYTIME, ANYWHERE

The past few versions of Xcode have revealed a noticeable trend toward transparent and ubiquitous debugging. In earlier versions, debugging was performed almost exclusively in the single debugger window, with occasional side trips to the breakpoints and memory windows.

Now, Xcode tries to bring the debugger to you whenever you need it, and wherever you happen to be, rather than making you go to the debugger. Xcode doesn't even open the debugger window by default anymore.

You can debug your applications in the same editor window that you write your code. You can set and modify breakpoints, view variables, and control execution. You can still utilize the traditional debugger window — which is still exceptionally useful for some debugging tasks. Furthermore, it provides some additional, and rather interesting, debugging interfaces. All of these interfaces are described in detail in later sections of this chapter, but I'll summarize them here.

Xcode 3.2 provides three primary debugging interfaces:

  • In-Editor debugging

  • The Debugger window

  • The mini-debugger

In-Editor debugging controls appear in all of your editing panes whenever a debug session is in progress, as shown in Figure 18-3.

FIGURE 18-3

Figure 18-3. FIGURE 18-3

From your source file editing pane, you can:

  • Set, enable, disable, and delete breakpoints

  • Control program execution (pause, run, step over, step into, set out of, and so on)

  • Examine variables

  • Switch to a different task or stack frame

The second big interface is the debugger window, shown in Figure 18-4.

FIGURE 18-4

Figure 18-4. FIGURE 18-4

The debugger window includes an editing pane, so everything you can do in an editing pane can also be done in the debugger window. In addition, the debugger window provides:

  • A structured list of all in-scope variables

  • The CPU registers

  • Access to global variables

  • Advanced data inspectors

  • A stack frame list

  • Thread selection

The debugger window is where you turn if you want to explore other stack frames, switch to another thread, want to see a list of all variables in scope simultaneously, see global variables, want to examine variables in more detail, or want to modify variables.

The third interface is the mini-debugger. It's a minimal debugging interface designed for use with full-screen applications and other situations where getting to the debugger window is awkward or inconvenient. The mini-debugger is described in later sections.

You can see where you can debug from almost anywhere in the Xcode interface, but you can also debug your application anytime; you can launch your application normally and then later decide that you want to debug it; Xcode will interrupt the process, attach its debugger, and hand over control to you.

In fact, that's really the primary reason for the Run

FIGURE 18-4

Before any serious debugging can take place, you must first prepare your project for debugging.

BUILT TO BE DEBUGGED

The take-home message of this section is this:

  • Before debugging, profiling, or analyzing your code, you must first build it using the Debug build configuration.

It's an essential requirement for doing any kind of debugging or analysis. If you're in a hurry, switch your active build configuration to Debug and skip to the next section. If you're interested in knowing why, keep reading.

How you build your application affects its ability to be debugged. The quintessential quality of a modern programming language is that it allows a developer to express procedures symbolically, letting the compiler deal with the ugly details of how to accomplish those procedures in machine code. Listing 18-1 shows just how obtuse the machine code for a few "simple" lines of programming source can be. The source code is shown in the listing, followed by the resulting Intel machine code. The debugger has the unenviable job of reversing this process — it must examine the raw machine code and translate that back into something that corresponds to the functions, methods, code blocks, classes, structures, and variable names defined in your source code. (You see this process at work later in Figure 18-6.)

Example 18-1. Compiled source code

SOURCE CODE

- (void)dealloc
{
    free(byteArray);
    [super dealloc];
}

COMPILED ASSEMBLY CODE

    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    subl    $36, %esp
    movl    8(%ebp), %ebx
    movl    4(%ebx), %eax
    movl    %eax, (%esp)
    call    _free
    movl    %ebx, −16(%ebp)
    movl    L_OBJC_CLASS_SieveOfEratosthenes+4, %eax
    movl    %eax, −12(%ebp)
    leal    −16(%ebp), %edx
    movl    L_OBJC_SELECTOR_REFERENCES_2, %eax
    movl    %eax, 4(%esp)
    movl    %edx, (%esp)
    call    _objc_msgSendSuper
    addl    $36, %esp
    popl    %ebx
    leave
    ret

To accomplish this feat, the debugger needs a lot of help. That help comes in the form of debugger symbols produced by the compiler. Debugger symbols are a kind of massive cross-index. They contain information like "the machine instruction at byte offset 12,738 corresponds to line 83 of the source file breakme.c." If you set a breakpoint at line 83 of breakme.c, the debugger knows it needs to stop your program at the instruction found at offset 12,738. If your program crashes at (or near) the machine instruction at offset 12,738, the debugger can tell you that your program crashed at (or near) line 83 of breakme.c.

The debugger symbols contain similar information about data structures, classes, automatic variables, and so on. You must request that these debugger symbols be produced when your application is compiled. If you have created your project using one of the Xcode templates, you should have a Release and a Debug build configuration. The Debug build configuration, shown in Figure 18-5, for your program's target has the following build settings:

  • Generate Debug Symbols: On

  • Debug Information Format: DWARF with dSYM File

  • Optimization Level: None

  • Fix & Continue: Off

FIGURE 18-5

Figure 18-5. FIGURE 18-5

Generate Debug Symbols enables the full array of debugger symbols in the compiler, detailing every aspect of your code for the debugger. Without it, the debugger is next to useless. This information is produced when each source file is compiled, it takes a little extra time to compile, and produces a lot of data — quite often more data than your actual program — which the debugger has to load. All of this slows down both compilation and launch time. If you are debugging a massive amount of code, you may elect to generate debug symbols only for some modules and not others. For example, you may generate debugger symbols for your main application code but not some well-tested library routines. This speeds up building and debugging, but limits your debugging to the portion of your code that has debugger symbols.

The Debug Information Format defines which file format to use to write the debugging information. DWARF with dSYM File is the modern format and should be your first choice. DWARF and Stabs are older formats that embed the debugging information in the executable.

Warning

If you're using the modern DWARF with dSYM File debug symbols format, the legacy build settings Strip Linked Product and Strip Debug Symbols During Copy are largely superfluous. Legacy debugging symbols were stored in the executable files themselves, and later stripped off during the deployment phase of your Release build configuration. The modern dSYM file writes the debugging information to a separate symbol file, so the executable is essentially already "stripped" of its debug information.

If you're developing a legacy application created with an earlier version of Xcode, update your debug format to DWARF with dSYM File and make sure the Strip Debug Symbols During Copy build setting is set to NO. Leaving Strip Debug Symbols During Copy on will interfere with code signing, which is critical to iPhone and modern Mac OS X applications.

While you're debugging, the optimization of your code should be set to None. The reason why goes back to how debuggers work. Optimization, by its very nature, is logic in the compiler that reorganizes, reorders, rewrites, and often eliminates code that it finds to be inefficient or redundant. Take the following code fragment as an example:

Line 100: int i=0;
Line 101: for (i=1; i<argc; i++)

With optimization turned on, the compiler eliminates the statement i=0 because the result of that assignment is never used. In the debugger, it now becomes impossible to set a breakpoint at line 100 because the code that corresponds to that line of source code doesn't exist in your compiled application. More advanced optimization techniques, such as loop unrolling or instruction reordering, can produce even more bizarre aberrations, such as programs whose statements execute out of order (for example, line 101, then 103, then 102). It might be impossible to stop an application at a certain point in your source code, or step through your code one line at a time. Skeptical readers are invited to enable full optimization and then attempt to debug their application.

Fix & Continue is a feature, covered later in "The Magic Fix," that allows you to make limited code changes in your application while your application is running. That is, you don't have to stop your program, change your code, rebuild, and restart. You simply change your code and keep executing. To use this feature, your compiled object code must contain additional information that the Fix & Continue feature needs. If you leave this build setting off, you can still debug your code, but the Fix & Continue feature is disabled.

The Release build configuration for new projects has the opposite build settings: no debug symbols, symbols stripped, normal optimization, and no Fix & Continue. If you have created your own targets or build configurations, you'll need to set them up accordingly.

Note that Xcode project templates define many of these settings in the target's build settings. If you have a multiple target project, you can easily misconfigure the settings in one target and not realize it. Or, you can set them in the project build settings, where they will be promptly ignored. For these reasons, I recommend moving these basic debug settings into the project build settings level for multi-target projects, and then override these settings only in those targets that require something different (which is rare). This way, you can adjust the level of debugger symbols produced (for example) with a single build setting, rather than having to change this setting in every target.

DEBUGGING EXECUTABLES

With the preliminaries out of the way, you're ready to debug your application. Getting started is as easy as running your program. Choose any of these commands to start your application under the control of the debugger:

  • Build

    DEBUGGING EXECUTABLES
  • Build

    DEBUGGING EXECUTABLES
  • Run

    DEBUGGING EXECUTABLES
  • Run

    DEBUGGING EXECUTABLES

As mentioned in the "Running Your Application" section, the two unqualified "Debug" commands change to "Run" commands when breakpoints are inactive. The two "Breakpoints On" commands simply activate breakpoints and start debugging, identical to Run

DEBUGGING EXECUTABLES

All of these commands start your program under the control of the debugger. The time from which the debugger starts your program until it finishes is referred to as the debug session.

An alternative is available in the targets group if you would like to build and then run or debug a target that is not the active target or executable. Right/Control-click the target's icon in the target smart group and choose either the Build and Start or the Build and Debug command. The effects are the same as changing the active target to the one selected, issuing a Build and Run/Debug command, and then switching the active target back to what it was originally.

You can have only one debug session active for a project. However, you can have concurrent debug sessions from separate projects. A Client and a Server application, both built using separate projects, could each be running in separate debug sessions simultaneously.

The Process Behind the Curtain

Like most modern integrated development environments (IDEs), the Xcode application doesn't perform the actual debugging — or compilation, or linking, or profiling — of your application. Xcode simply provides an intelligent interface to the command-line tools that do the bulk of the work. In the case of debugging, the work is usually done by gdb, the GNU Debugger.

I say usually, because Xcode can also use other debuggers. It understands the JavaBug and AppleScript debuggers, but if you're doing Mac OS X or iPhone OS development, you'll be using gdb.

Xcode doesn't try to hide the debugger; there are plenty of places where you can interact with the debugger directly. What it tries to do is eliminate much of the tedium of using command-line debuggers by replacing the command line with an interactive, graphical user interface.

So while you're working your way through this chapter, keep this in mind:

  • Almost every debugging task you undertake in Xcode is eventually translated into a command sent to gdb, or whatever debugger you're using.

Debugger Spoken Here

Another great advantage to using an intermediate debugger is that your debugger interface remains largely the same regardless of the target application environment. Xcode can debug an application in any of the following environments:

  • A local process running in Mac OS X

  • A remote process running on another Mac OS X system

  • An iPhone application running in a local simulator process

  • An iPhone application running on a remote device

Debugging an application running on the same computer system is the most common scenario, but by no means the only one. The section "Remote Debugging," later in this chapter, shows you how to debug a process interactively that is running on a different computer system.

iPhone application development would seem like it is worlds apart from desktop application development. While the design and implementation might be substantially different, the development tools remain almost identical.

You might have expected a substantial portion of this chapter to be dedicated to the iPhone simulator and iPhone native debugging, but there's really very little to say. Though there's some prerequisite configuration required before an iPhone or iPod Touch can debug applications (see Chapter 22), there is virtually no difference between debugging an iPhone application running on an iPod Touch and debugging a Cocoa application running on your development system. Almost everything in this chapter applies to both.

ATTACHING TO RUNNING EXECUTABLES

As mentioned earlier, the Xcode debugger can attach itself to an already running process on your local system, extracting debug information and taking control of its execution. Xcode may do this for one of four reasons:

  • You create a new breakpoint or enable an existing breakpoint

  • You reactivate existing breakpoints

  • An application you started in Xcode crashes or encounters a trap

  • You choose a process from the Run

    ATTACHING TO RUNNING EXECUTABLES

The first three all apply to the currently running executable started via Xcode, but the behavior is a little different depending on which operating system you're running. If you're running Mac OS X 10.5 (Leopard) or earlier, your application is launched normally and the gdb debugger is started and attached when requested. If your running Mac OS X 10.6 (Snow Leopard) or later, Xcode preemptively starts gdb when it launches your application, but lets your application "run free." When you enable breakpoints, gdb steps in, enables breakpoints, and assumes control of your application's execution. The primary advantage is speed; gdb is launched quietly in the background, and springs instantly into action when requested.

Note

Whether gdb is preemptively started ahead of time or launched post hoc is largely immaterial, except in a few circumstances. Some of the system frameworks include anti-piracy code that either resists being debugged or disables certain features in the presence of the debugger. If you're working with QuickTime or similar libraries, you may have to manually launch your application and then use the Run

ATTACHING TO RUNNING EXECUTABLES

Once your application is running, creating or enabling any breakpoint signals to Xcode that you want to take control of the application with the debugger. The workflow you'd typically use this in looks something like this:

  1. Write some code.

  2. Build and run the application.

  3. Discover that something's not working right.

  4. Start the debugger and have it attach to the already running application.

  5. Debug the problem.

The Run

ATTACHING TO RUNNING EXECUTABLES

When attaching to a running process, Xcode does its best to match the executable with a target in the current project. If it can't it will still attach to the process, but the amount of useful debugging information will be extremely limited.

The third reason Xcode will attach to your running application involves a trap, uncaught signal, or other exception that would normally cause the process to exit immediately. Xcode automatically intercepts these exceptions and — rather than letting your application terminate — attaches the debugger. You are left in a debug session with the program suspended at the location that caused the fatal problem. You can examine the threads, stack, variables, and other information in an attempt to determine what went wrong.

You might find it useful to intentionally cause a trap programmatically. To do this, you can add a debugger trap or a hard trap instruction to your code. The Debugger() or DebugStr() functions request the debugger. For the most part, they act like programmatically defined breakpoints. They will cause the debugger to stop the application, or attach the debugger to an already running application. If left in your code and run in the absence of a debugger, these functions will write a message to the system console every time they are executed — but they won't terminate your application.

A machine trap instruction is essentially a programmatic crash. It can be inserted using an assembly directive, as depicted in the following listing:

#if defined(__ppc__) || defined(__ppc64__)
    asm { trap }
#endif
#if defined(__i386__) || defined(__x86_64__)
    __asm { int 3 }
#endif

These machine language trap instructions cause your application to immediately stop. If launched from Xcode, Xcode will attach the debugger as it would for any unrecoverable exception. If a trap is executed outside of Xcode, your program will immediately terminate. The system will treat your application as if it had crashed.

Also see the "Custom Executables" section. Whether Xcode stops for Debugger() calls or automatically attaches itself when the process crashes can be disabled for individual executables.

IN-EDITOR DEBUGGING

Xcode's in-editor debugging tools let you perform basic and common debugging actions right in your editing window. This means that in many situations you can edit, compile, run, and debug your application without ever switching to another Xcode window or view.

When a debugging session starts, the debugger strip appears at the top of your editor pane, as shown in Figure 18-6. Combined with breakpoint controls in the gutter (which, more than likely, were already visible) and additional symbol inspectors (called datatips), the in-editor debugging controls allow you to do three things:

  • Modify breakpoints

  • Control execution

  • Inspect variables

FIGURE 18-6

Figure 18-6. FIGURE 18-6

Note

A setting in the debugging pane of the Xcode preferences enables the in-editor debugging controls. If you don't see the in-editor debugging controls, check your preferences.

Editing Breakpoints

Breakpoints are described in detail in the "Breakpoints" section of this chapter, but here's the short lesson:

  • Click a line number in the gutter to create a breakpoint

  • Click a breakpoint to enable/disable it

  • Drag a breakpoint to a new line to relocate it

  • Drag a breakpoint out of the gutter to delete it

  • Double-click a breakpoint to reveal it in the breakpoints window

Controlling Execution

The red arrow in the gutter, shown in Figure 18-3, indicates the line of code containing the CPU's program counter. This is, essentially, where your program is executing — with some caveats that I'll get to in a moment.

Selecting the Thread and Stack Frame

The debugger strip appears in the editor pane above the navigation ribbon. On the left is a pop-up control that selects the thread with which you want to work. On the right is another pop-up control that lists and selects the stack frame with which you want to work. In between are a series of debugger control buttons.

The gutter's program counter indicator is for the selected stack frame of the selected thread. If you switch threads using the left-hand control, the program pointer moves to indicate where that thread is currently executing. If you choose a different stack frame, it shows where execution will continue when that stack frame is restored — in other words, the code that called the code in the subsequent stack frame.

Running and Stepping Through Code

In between the task and stack frame pop-up menus are the debugger actions. These are listed, from left to right, in the following table:

ACTION

COMMAND

SHORTCUT

(De)activate Breakpoints

Run

Running and Stepping Through Code

Control+Command+

Continue

Run

Running and Stepping Through Code

Option+Command+P

Step Over

Run

Running and Stepping Through Code

Shift+Command+O

Step Into

Run

Running and Stepping Through Code

Shift+Command+I

Step Out

Run

Running and Stepping Through Code

Shift+Command+T

Debugger Window

Run

Running and Stepping Through Code

Shift+Command+Y

Console Window

Run

Running and Stepping Through Code

Shift+Command+R

The first five actions are described in detail in the "Controlling the Debugger" section. The last two simply open the debugger window (see "The Debugger Window" section) or the debugging console window (already mentioned in the "Monitoring Your Process" section).

Hover Step Controls

If you hover your cursor over a line number in the gutter that corresponds to executable code, or over a function or method name in the code, one of a number of step controls appears.

Hovering the cursor over the currently executing code line reveals a tiny continue button, as shown underneath the cursor in Figure 18-7. If you carefully slide the cursor over to the button and click it, Xcode lets the program continue executing.

FIGURE 18-7

Figure 18-7. FIGURE 18-7

Hovering the cursor over any other executable line in the gutter reveals a continue to here button, as shown in Figure 18-8. The continue to here action sets a temporary breakpoint and then continues execution. Assuming the application doesn't encounter another breakpoint first, the process will run until it gets to the line over which you were hovering.

FIGURE 18-8

Figure 18-8. FIGURE 18-8

Note

The Continue to Here command can also be found in the contextual menu when you Right/Control-click a line the gutter. This can be faster than trying to use the hover controls.

Hovering over a function or method call, as shown in Figure 18-9, reveals a targeted step into button. Much like the continue to here action, it sets a temporary breakpoint at the beginning of the function you're hovering over and then lets the application continue execution. The program runs until it steps into the function you targeted.

FIGURE 18-9

Figure 18-9. FIGURE 18-9

Viewing Variables

Variables in your running application can be examined simply by hovering the cursor over the variable name. This displays a datatip, as shown in Figure 18-10, that reveals the type, name, and value of the variable.

FIGURE 18-10

Figure 18-10. FIGURE 18-10

Simple variables display a single row describing its type, name, and current value.

Complex variables, like objects and structures, include a disclosure triangle that expands to reveal its individual instance variables or fields. Complex instance variables can themselves be expanded, letting you "drill down" in search of the variables for which you're looking.

Note

Datatips only appear for variables in the scope of the current stack frame of the currently selected thread. Changing the thread or stack frame from the debugger strip brings a new set of variables into scope. The section "The Threads Pane" illustrates this in more detail.

By default, the expansion of subvariables is automatic, but you can turn that off. Clicking the disclosure triangle of an expanded variable collapses it again.

A datatip row may contain one of two active controls. One is a value editor. Most mutable scalar values can be changed simply by clicking its value in the datatip, also shown in Figure 18-10.

The other control is a datatip menu, which appears as two arrows on the left-hand side of the datatip. Clicking the arrows pops up the a datatip menu, as shown in Figure 18-11.

FIGURE 18-11

Figure 18-11. FIGURE 18-11

From the datatip menu you can dump (print) the variable's value to the debugger console, open the variable in its own window, jump to the symbol's definition or documentation, enable/disable data formatters, control the order in which subvariables are displayed, and turn datatip auto-expansion on or off.

Variable windows, memory browser windows, types, and data formatters are all described later in the "Examining Data" section.

THE DEBUGGER WINDOW

The debugger window, shown in Figure 18-12, is the master control center for debugging. Debugging controls in the editor or mini-debugger are designed to be unobtrusive or appear only when requested. The debugger window is just the opposite; it presents as much information about the variables and state of your application as possible. The debugging features accessible from the editor pane or the mini-debugger are all duplicates of the features in the debugger window, so the detailed descriptions of each are included in these sections.

FIGURE 18-12

Figure 18-12. FIGURE 18-12

Each project has its own debugger window. You can open the debugger window at any time using the Run

FIGURE 18-12

The debugger window has three main panes: the threads pane, the variables pane, and the listing or editor pane. The debugger pane has an alternate vertical layout to the one shown in Figure 18-12. Use the Run

FIGURE 18-12

You can resize all three panes by dragging the small grey thumb bar that appears at the nexus of the three panes.

The Threads Pane

The threads pane displays the stack of the active thread, as shown in Figure 18-13. Each entry in the list represents a single stack frame, and the selected entry defines the active stack frame. A frame contains the return address of the caller and all of the function's automatic variables. If the address of the function can be translated into a name, the function's name is displayed. Otherwise the address of the function is listed.

FIGURE 18-13

Figure 18-13. FIGURE 18-13

When you stop an application in the debugger, the debugger stops all threads, but the pane displays the call stack of the thread that hit the breakpoint. The left column indicates the relative depth of each function in the call stack. The name of the function currently executing is displayed at the top of the list and always has a stack depth of 0. The name of the function that called the currently executing function is listed underneath that and has a stack depth of 1, and so on all the way back to the function that started the thread.

Note

For the debugger, there are only functions, structures, and variables. Different languages refer to subroutines using various terminologies: procedure, function, member function, method, subroutine, or message handler. Ultimately, each becomes a block of code at an address in memory. When this book uses the words "function" or "subroutine," substitute the term of your preferred paradigm.

The contents of the variables pane and the initial contents of the editor pane are determined by the current selection in the threads pane. When you stop an application, the current (top) function in the call stack of the stopped thread is selected. Select another function in the call stack, and the variables and editor pane change to display the variables and execution location in that function. This allows you to examine not only the variables of the current function, but also the variables and calling address of the functions that called the current function.

You can view another call stack by selecting a different thread from the pop-up menu at the top of the pane, as shown in Figure 18-14.

FIGURE 18-14

Figure 18-14. FIGURE 18-14

You can also step through the threads in your application using these two commands:

  • Run

    FIGURE 18-14
  • Run

    FIGURE 18-14

Change to another thread, and you can examine the local variables and calling address of any frame on that thread's stack. Depending on the language and run time library used, threads may or may not have readable descriptions. If they do, the description will appear next to the thread's identifier.

The Run

FIGURE 18-14

The Listing or Editor Pane

The listing or editor pane displays the source code of your application, a disassembly listing, or both. The latter combination is shown in Figure 18-15. The debugger chooses a view automatically when it needs to display a breakpoint or calling location. If the debugger can correlate the program location with a source file, it displays the source code in an editor pane.

FIGURE 18-15

Figure 18-15. FIGURE 18-15

This is a full-featured editor pane, allowing you to edit your source code right in the debugger window. It also contains all of the breakpoints, hover step controls, and datatips described earlier in the "In-Editor Debugging" section. The only thing it doesn't have is the debugging strip, because all of those functions are provided elsewhere in the debugger window.

You can choose an alternate source code view using one of the following commands:

  • Run

    FIGURE 18-15
  • Run

    FIGURE 18-15
  • Run

    FIGURE 18-15

If the debugger cannot determine a source file that corresponds to the program's location, there is no source view; it displays only the disassembly of the machine codes and the disassembly display commands have no effect. Similarly, only languages that compile directly into assembly language have a disassembly view; Java and AppleScript programs do not show a disassembly view of their byte code or tokens.

Breakpoints and the current execution location are indicated in the gutter of the editor pane. Breakpoints appear as dark blue (enabled), light blue (disabled), or yellow (invalid) markers. The program counter (PC) indicator appears as a red arrow. The breakpoint and location indicators are so critical to the effective use of the debugger that the gutter display is turned on whenever you are in a debug session. If your Xcode Preferences are set not to display the gutter in the editor, the gutters disappear again at the end of your debug session.

The program-counter indicator always points to the current execution location for the selected thread and stack frame. In a source file, it points to the source line most closely associated with the program-counter position. In the disassembly listing, it points to the exact instruction. If the top (level 0) stack frame is selected, the arrow indicates where the current thread is executing. Select another stack frame in the list and it indicates the location where the stack frame above it was called.

Warning

Because the meaning of the PC indicator is relative, make sure you pay attention to what stack frame you have selected. If you choose another stack frame, the PC indicator points to where the current execution location was called from, not where it is. Using a command like Step Over does not step to the next instruction after the PC indicator. It steps to the next instruction in the top stack frame, where the program is actually stopped.

The Variables Pane

The variables pane displays the known data variables for the selected stack frame. The key word here is known. The debugger must have debug symbol information that describes the structure for the selected stack frame. If there is no description available, it cannot interpret or display any data values on the stack and the pane is empty. You will encounter this most often when you stop an application in a system framework or library routine.

Variable Groups

Variables in the display are organized into a hierarchy of groups. The top-level groups are listed in the following table:

VARIABLE GROUP

DESCRIPTION

Arguments

The parameters passed to the current function.

Locals

The automatic variables allocated in the stack frame.

File Statics

Local static variables allocated in the same module.

Globals

Potentially, any global variable in the application.

Properties

AppleScript properties.

Registers

The CPU's hardware registers.

If there are no variables of a given type, the group containing that type is not displayed: a simple C function with no parameters does not display an Arguments group. A method with no automatic variables has no Local group.

Although the Arguments group is technically just more local variables, Xcode groups them for convenience. In object-oriented languages, the arguments include the implied variables such as this or self. Expand the this or self object to view the instance variables of the current object.

The Locals group contains the local (also known as automatic or stack) variables allocated on the stack frame.

The File Statics group contains any static variables defined in the code's module. The scope is immaterial; the defined variables may be local or global.

The Globals group contains any global variables you want to examine in the applications. Because this could, potentially, contain hundreds if not thousands of variables (remember that every library and framework that your application is linked to can declare globals), this group must be manually populated with just the symbols you want to examine. This is explained later in the "Viewing Global Variables" section.

The Registers group, or groups, depends on the architecture of the assembly code and your current processor. Interpreted languages like Java and AppleScript won't have a Registers group. CPUs with vector processing units, floating-point calculation units, or CPUs that have a separate set of 64-bit registers may display addition register groups.

Exploring Variables

Variables are listed by name. Structures, arrays, and objects appear as groups in the listing. Expose the contents of a group to examine its member variables, just like you did with the datatips. The Value column shows the interpreted value of the variable. For simple types (integers, floats, strings) it's the textual representation of the value. For arrays, structures, and objects it's a summary of its content. The summary can be very generic or quite specific, as you'll soon see.

Pointers or references display the address or id of the object to which they are pointing but act like the object to which they refer. Thus, you can expand a pointer to a structure so it shows the member values of the structure. Change the pointer and all member values change accordingly. Xcode dynamically determines the structure of objects when it can. The most obvious example is a generic reference of an id or Object type. Variables of this type impart no information about the class or structure of the object they might reference. When Xcode displays an object reference, it examines the type of the object and adjusts its display to reflect the structure of the actual object. Thus, you will see the member variables of an id or Object reference change, depending on the type of object to which it refers.

Similar to datatips, you can change the scalar value of variables. Simply double-click in the value cell of the variable and edit its value.

Value Summaries

The Summary field is an intelligent interpretation of the variable's contents. Using a system of data formatters, the field can display a more human-readable summary of the object or structure's contents. Xcode has numerous data formatters built in. A simple example is the NSCalendarDate object. Without data formatters, an NSCalendarDate reference would display as an object at a particular address. Exposing its member values would display several variables, shown on the left in Figure 18-16. The _formatString variable contains something cryptic, the _timeZone value contains a pointer to an opaque object, and the _timeIntervalSinceReferenceDate variable contains a big floating-point number. Unless you can convert seconds-since-the-epoch to a calendar date in your head, none of these values are particularly informative.

FIGURE 18-16

Figure 18-16. FIGURE 18-16

This is where data formatters come to the rescue. Xcode includes data formatters for strings, dates, and even the time zone object. Turn data formatters on and the display changes to something far more useful, shown on the right in Figure 18-16. The format string and time zone values are now easily readable, and the date object reference itself displays a practical representation of the date and time encapsulated by that object. You learn how to create your own data formatters, which is even more powerful, later in the "Data Formatters" section. You can enable and disable data formatters using the Debug

FIGURE 18-16

Variable Types

There is also an optional Type column that you can display using the Run

Variable Types

Note

Showing the Type column is probably the first thing I do after opening the debugger window for the first time. Alternatively, you can also Right/Control-click any variable and choose Show/Hide Type Column from the pop-up menu. Sadly, Xcode doesn't preserve this setting between Xcode sessions.

There's a lot more to examining data in the debugger. This brief overview should give you some idea of what you're looking at while you progress to the more practical topic of controlling the debugger and setting breakpoints. Until you can stop your application, there's no data to examine.

CONTROLLING THE DEBUGGER

One of the most elementary, and often most effective, methods of debugging an application is simply to stop it and examine its state. Look at the value of local variables and objects, and see what functions have been called and in what order. You may then want to step through the code one line at a time to witness its order of execution. This, rather passive, method of debugging is often all that's required to determine what your code is doing wrong or unexpectedly.

You control this kind of immediate and interactive debugging through a set of debugging commands. None of these commands (except the Pause command) is available until the debugger has suspended your application. This happens when the execution of your program encounters a breakpoint, some exceptional event, or when you use the Pause command. The most predictable method is to set a breakpoint. It's possible to stop your program using the Debug

CONTROLLING THE DEBUGGER

Breakpoints can do many sophisticated things, and you can set them in a variety of ways, all of which is covered later in the "Breakpoints" section. For now, all you need to do is set and enable simple breakpoints by clicking in the editor pane gutter of any source file. A breakpoint appears as a blue marker that indicates the position of the breakpoint in the source code. Clicking an existing breakpoint toggles its state between enabled (blue) and disabled (grey). Only enabled breakpoints interrupt program execution. To delete a breakpoint, drag the breakpoint out of the gutter or use the Right/Control-click menu to select the Remove Breakpoint command.

After the execution of your program has been suspended, a set of execution control commands becomes available. These are listed in the following table:

COMMAND

SHORTCUT

DESCRIPTION

Continue

Option+Command+P

Resumes execution of your program. Your program will run until it encounters another breakpoint or terminates.

Pause

Option+Command+P

Immediately suspends execution of your program. This is the only command that will stop your program without setting a breakpoint.

Step Into

Shift+Command+I

Executes one line of source code. If the line contains a call to another function, execution is stopped again at the beginning of that function, which is what gives this command its name — you are stepping into the function being called. If the source line does not call another function, this command is equivalent to Step Over.

Step Over

Shift+Command+O

Executes one line of source code and stops before executing the line that follows. If the line contains calls to other functions, those functions are allowed to execute in their entirety.

Step Out

Shift+Command+T

Resumes execution until the current function returns to its caller.

Step Into Instruction

Option+Shift+Command+I

Equivalent to Step Into, but steps through a single machine instruction rather than a full line of source code, that might translate into dozens of machine instructions.

Step Over Instruction

Option+Shift+Command+O

Equivalent to Step Over, but steps over only a single machine instruction.

(continue to here)

Option+click in gutter

Sets a temporary breakpoint and starts the program running.

Sync With Debugger

 

Returns the debugger window's view to showing the current thread, current stack frame, current function, and current PC indicator where the debugger last suspended your process.

Stop

Shift+Command+Return

Forcibly terminates your program's process.

Pause and Continue

The Pause and Continue commands are immediate and self-explanatory. They share the same menu item, toolbar button, and keyboard shortcut. The Pause command is active whenever the process is executing, and the Continue command is active whenever it is suspended.

Generally, the Pause command isn't very useful for working GUI applications, because it tends to suspend the application in a run loop. You will probably find it most edifying when your application is stuck in an endless, or seemingly endless, loop. It can also be helpful to suspend your application while you think about your next move or contemplate where to set a breakpoint. You don't have to stop the program to set a breakpoint, but you don't necessarily want your application running amuck while you decide where it should go.

Step Over and Into

Step Over and Step Into are the two most commonly used debugger control commands. Step Over lets you walk through the logic of a single function, ignoring the details of other functions that it might call. Step Into traces the execution of the program one step at a time, regardless of where that leads.

Note

Step Into, Step Over, Continue to Here, and many other debugger commands work by setting temporary breakpoints. A temporary breakpoint is one created by the debugger for some ephemeral purpose, which it then deletes as soon as it's reached. For example, the Step Into command works by setting a temporary breakpoint at the beginning of the function that you want to step into. It then lets the process execute. Most of the time this works as expected.

You may, however, encounter unexpected behavior if the code encounters another breakpoint first (possibly in another thread) or if there's an exceptional event; the debugger will stop there instead.

A different situation arises if a function exits abnormally (via a longjmp, by throwing an exception, or by terminating a thread) — the program may never execute the code wherein the temporary breakpoint was set. In this situation, the program avoids the breakpoint and continues running indefinitely.

The Step Over command is intelligent about recursive functions. Stepping over a function that calls itself does not stop until all nested iterations of the function have executed and returned, even if that entails executing the same code position that you are stepping over.

Note

If your editor pane is in disassembly view, Step Into and Step Over behave exactly the same way, but they each execute a single machine instruction — instead of the group of instructions generated by the single line of source code.

If the source line contains multiple calls, the Step Into command steps into the first function called. This is significant if a function passes parameters that are obtained by calling other functions. The example in Listing 18-2 illustrates this. If the debugger first stopped at this line and you issued the Step Into command, the debugger would step into getDefaultMode(), not setMode(). That's because the getDefaultMode function is called first to obtain the value of setMode's single argument.

Example 18-2. Nested function calls

setMode(getDefaultMode());     // reset the mode

After you return from getDefaultMode (see the Step Out command), the debugger again returns to this line of source code, but the CPU's program counter is now poised at the call to setMode. This time the Step Into command steps into the setMode function.

Note

This is the situation where the hover step controls are exceptionally useful. Hover your cursor over the function or method name you want to step into and click its step into button. Xcode will step into that specific function, allowing any prerequisite functions to execute without interruption.

Step Into only steps into functions that have debug information and have local source files associated with them. You cannot step into library functions, framework APIs, or kernel code that was not compiled with full debug information. Attempting to step into such a function is treated like a Step Over.

Stepping Out

Step Out is convenient for letting the current function complete its execution and return again to the point where it was called. It's common to step into a function simply to examine the values of its arguments. After you are satisfied the function is behaving correctly, you can then use Step Out to let the function finish and return to where it was called. The same issues about exceptional exits that apply to Step Over also apply here.

Stepping Over and Into Single Instructions

The special Step Into Instruction and Step Over Instruction perform the same actions as the Step Into and Step Over, but each only executes a single machine instruction. These functions work in either source or disassembly view, but are most useful when you're viewing both the source and disassembly of your program's code where the meaning of Step Into and Step Over becomes ambiguous.

Step Into Instruction is also away around the Step Into command's self-imposed limitation of never stepping into a function that doesn't have source information (like a library function). If the function being called has no source code available, Step Into Instruction still steps into it, automatically switching to a disassembly-only view, as required.

Continue to Here

The continue to here command doesn't appear in the menu and has no keyboard shortcut. A continue to here button appears when you hover over an executable line number in the gutter. You can also use this, much quicker, shortcut:

  • Option+click a line number in the gutter

The continue to here action creates a temporary breakpoint and starts your application running, just as if you had set a regular breakpoint and issued the Continue command. A breakpoint indicator is not visible in the gutter or anywhere else in Xcode, and the breakpoint is deleted as soon as it is hit. This makes it extremely easy to skip through blocks of code or around loops by simply clicking where you want to stop next. The same caveats about temporary breakpoints mentioned earlier apply here.

THE MINI-DEBUGGER

The mini-debugger is a compact debugger control window that's intended to be used in situations where switching between your application and Xcode is awkward or impossible. This is particularly true of applications that present full-screen multimedia, games, animation, screen savers, and similar environments.

You open the mini-debugger with the Run

THE MINI-DEBUGGER
FIGURE 18-17

Figure 18-17. FIGURE 18-17

While the application is running, the mini-debugger window has four buttons:

  • Stop

  • Pause

  • (De)activate Breakpoints

  • Xcode

The stop, pause, and toggle breakpoints button each perform their respective action. The Xcode button simply returns you to the Xcode application — often handy in applications where all of the Xcode windows and the menubar are obscured.

Whenever the application is paused, the mini-debugger window expands to show a small editor pane, as shown on the right in Figure 18-17. The pane has all of the in-editor debugger controls described in the "In-Editor Debugging" section.

If you like the mini-debugger, you'll probably want to set the On Start Open Mini Debugger setting in the debugging preferences pane.

BREAKPOINTS

Breakpoints are locations in your program where you want the debugger to take control. Formally, a breakpoint is set at a particular address in memory. When the CPU's program counter matches the address of a breakpoint — that is to say at the instant before the instruction at that breakpoint's address is to be executed — the CPU stops executing your program and passes control to the debugger.

So far you've created only basic breakpoints. The default action of a breakpoint is to halt the execution of your program, hand over control to the debugger, and wait for instructions, but breakpoints are capable of much more.

Before getting into more advanced techniques for defining breakpoints, here's a quick review of the methods for creating a basic breakpoint — one that simply stops the program when encountered:

  • Click in the gutter of a source file.

  • Right/Control-click in the gutter of a source file and choose the Add Breakpoint command.

  • Choose the Run

    BREAKPOINTS
  • Use any of the debugger commands to create a temporary breakpoint and start the program running.

When you're setting breakpoints graphically, Xcode allows you to set a breakpoint on just about any line of the source file. A lot of times this doesn't make any sense, but Xcode can't tell that. When you set a breakpoint in a source file, you're actually setting the breakpoint at the first executable instruction produced by the source file at, or following, the line you clicked. Figure 18-18 shows three breakpoints set in a source file. All three of these breakpoints point to the same address location. The first one is set on a declaration statement that produces no code, and the second one is set on a completely blank line. Only the source code on line 30 produces any executable code in the application. Ultimately, you could set a breakpoint on line 28, 29, or 30 with the same results. The breakpoint is set at the instruction that implements the switch statement.

FIGURE 18-18

Figure 18-18. FIGURE 18-18

In a similar vein, source code that you might not think of as producing code often does. A good example is the closing brace of a C++ function. All functions have to return, and the closing brace of the function body produces code that destroys any automatic objects, pops the stack frame, and returns to the caller. This happens even in void functions. Consequently, you can set a breakpoint at the closing brace of a function if you want to catch the function after the body of the function has executed, but before it returns to the caller.

The concept that this section is trying to express is that there is not always a simple one-to-one correlation between the source code statements and the executable code with which the debugger deals. The debugger does its best to translate between the two, but inconsistencies do occur. Just be prepared for this and understand what's going on.

Breakpoint Types

There are two kinds of breakpoints: source breakpoints and symbolic breakpoints. So far, this chapter has only dealt with source breakpoints. Source breakpoints are associated with a particular line in a source file. You set and see source breakpoints right in the gutter of the source file's editor pane.

Symbolic breakpoints are breakpoints that have been created for a particular symbol — that is, at the address of a symbol defined in the program. There are two important differences between a symbolic breakpoint and a breakpoint set at a line in a source file.

The most obvious is that you don't have to have the source file. Using symbolic breakpoints, you can set a breakpoint at the entry point of any library routine or framework API. For example, you could set a symbolic breakpoint at the free() function. Any code that calls the free(...) function would break into the debugger.

The other important difference is that the breakpoint address is not associated with a line in a source file. You could cut the function from one file and paste it into another, and the breakpoint would still work.

You find out how to create symbolic breakpoints shortly.

Breakpoints Window

Breakpoint management gets far more interesting when you open the breakpoints window, shown in Figure 18-19. You can do this by choosing the Run

Breakpoints Window
FIGURE 18-19

Figure 18-19. FIGURE 18-19

The Breakpoints window has two panes: a Groups & Files pane containing the Breakpoints smart group and a details pane listing the individual breakpoints. It should be noted that this is really just an abbreviated view of your project window, without the editor pane. Everything you can do in the Breakpoints window can be accomplished in the Breakpoints smart group and details pane of your project window.

Breakpoint Groups

The Breakpoints smart group has two subgroups. Each open project creates a group that contains all breakpoints specific to that project. The group might be simply named Project Breakpoints, or if multiple projects have been opened you will see a group named for each project. When you create a source file breakpoint in an open project, it's added to its group. These breakpoints are saved (per user) in the project document, so your breakpoints will persist between Xcode sessions.

The Global Breakpoints group contains breakpoints that are available to all projects. This is particularly useful for complex symbolic breakpoints that you want to use in different projects. Possibly, you could keep breakpoints in a common library that you use in several projects. Global breakpoints are saved in your Xcode Preferences.

Breakpoint Details

The list in the details pane shows the breakpoints selected in the Groups & Files window, or all the breakpoints contained in a selected group or groups. To limit the list of breakpoints to a particular subset, select only that subgroup in the Groups & Files pane. To see all of the breakpoints defined, select the top-level Breakpoints group.

Each breakpoint listed displays an icon, description, enabled check box, location, condition, and continue option. By Right/Control-clicking the column header of the table you can optionally choose to display the (normally hidden) Comments, Last Modified, and Hit Count columns. You may also hide columns that don't interest you.

The icon indicates the type of the breakpoint. Source breakpoints have a source file icon, and symbolic breakpoints have a 3D cube. Source breakpoints are described by the function name and line number within the source file, shown in the Location column, where they are set. Symbolic breakpoints are described by the symbol of the breakpoint address, possibly with a qualifier to distinguish between similar symbols. The location of a symbolic breakpoint is the library or module where it resides.

The name or address of a symbolic breakpoint is editable — double-click the name to change the symbol. Source breakpoints are not. Double-clicking a source breakpoint in the list jumps to that location in the source file. This is the complement of double-clicking a breakpoint in a source file, which jumps to that breakpoint in the breakpoint window.

The Comments column contains a text field for recording your comments about, or a description of, the breakpoint. If you have a lot of comments, open the Info window for the breakpoint and edit the comments there. The Last Modified field records the last time the breakpoint was altered. The Continue column is explained later in the "Breakpoint Actions" section, and the Condition field is explained in the "Iffy Breakpoints" section.

A breakpoint's detail line can be expanded to display the its actions, described later in the "Breakpoint Actions" section.

Breakpoint Details in an Editor Pane

You've seen breakpoints in editor panes more than a dozen times so far, but there's a somewhat obscure command for displaying some details about the breakpoint right in the editor pane, as shown in Figure 18-20.

FIGURE 18-20

Figure 18-20. FIGURE 18-20

The View

FIGURE 18-20

The enabled, auto-continue, and breakpoint condition can be edited right in the editor pane. This can be particularly handy when using the mini-debugger.

Deleting Breakpoints

To delete one or more breakpoints in the breakpoints window, select the breakpoints in the window and press the Delete key. You can also select a group of breakpoints in the Groups & Files pane and choose the Delete command from the Right/Control-click contextual pop-up menu.

To delete a source breakpoint from within a source file editor, click and drag the breakpoint out of the gutter. You can also Right/Control-click the breakpoint and choose the Remove Breakpoint command.

Enabling and Disabling Breakpoints

The check mark column in the breakpoints list shows whether that breakpoint is enabled. You can enable or disable an individual breakpoint by ticking its check box. This is synonymous with clicking the breakpoint's indicator in the source file. Enabled breakpoints are dark blue; disabled breakpoints are light blue.

Selecting breakpoints in the Groups & Files pane lets you to enable and disable breakpoints en masse. Select any combination of breakpoints and breakpoint groups in the Groups & Files pane — these commands do not work in the details list. Right/Control-click one of the selected items and choose either the Enable Breakpoints or the Disable Breakpoints command from the contextual menu. Enabling or disabling a group sets the state of every breakpoint contained in that group.

Hold down the Option key and the Enable Breakpoints command turns into the Enable Only These Breakpoints command. This command enables the selected breakpoints, and then disables all other project breakpoints. This is a quick way of enabling a strategic group of breakpoints you want to focus on while simultaneously disabling all others.

Note

You may notice that some breakpoints display a − sign in their enabled check box. These are enabled breakpoints that the debugger can't set for some reason. Examine the debugger console output (covered later) to find out which breakpoints are having problems. This is most commonly encountered with symbolic breakpoints that the debugger can't resolve. Beyond the obvious reason that the symbol simply doesn't exist in the application's name space, it can also happen when you're using ZeroLink or lazily loaded libraries. These technologies defer the loading and linking of functions until they are actually called, which means that the code for many functions won't be loaded into memory when the program starts executing. Until the debugger can turn a symbol name into an absolute memory address, it can't set a breakpoint.

Creating Symbolic Breakpoints

To create a symbolic breakpoint, first select the Breakpoints group in which you want the breakpoint created. At the bottom of the details pane list is a special placeholder breakpoint with a border around the name Double-Click For Symbol. To create a new symbolic breakpoint, do just what it says, as shown in Figure 18-21.

FIGURE 18-21

Figure 18-21. FIGURE 18-21

The symbol can be any function name known to the linker. This can be a function in your own application or any system API or library to which your application is linked. For C function calls, just the name of the function is sufficient. If there is any ambiguity, Xcode prompts you to choose the specific symbol that you meant. In Figure 18-22, a breakpoint is set on the free symbol, and Xcode wants to know which "free" it's referring to.

FIGURE 18-22

Figure 18-22. FIGURE 18-22

Objective-C and C++ methods must be expressed in their complete form. To set a symbolic breakpoint at the isPrime: method of the SieveOfEratosthenes class, create a breakpoint for the -[SieveOfEratosthenes isPrime:] symbol. Note that the symbol must have a + or - sign indicating a class or member method. In this example, : indicates that the method takes a single parameter. Just like when you're using the @selector operator, isPrime and isPrime: are two different methods. If the method took a second BOOL parameter, the symbol would be something like -[SieveOfEratosthenes isPrime:ignoringMap:].

In C++, the symbol should be a complete, fully qualified, prototype of the method. If the NestOfBugs class contained a member function named catchMe that took a single integer as a parameter, the symbol to use would be NestOfBugs::catchMe( int i ). Unlike the Objective-C symbol, the name of the parameter is included exactly as it was declared in the class statement. gdb will not find the function without a complete copy of its declaration. The return type of a function is not part of its name.

Symbolic breakpoints do not appear as breakpoint indicators in the gutter of the source file editor pane, even if the symbol identifies a function in your source code. Other than the visual differences, symbolic breakpoints are just like source breakpoints and share all of the same capabilities and traits.

Iffy Breakpoints

One of the first things you'll notice about breakpoints is that they always work. Although it might be gratifying to know that the technology is reliable, you may soon discover that it can be a curse as well. Setting a breakpoint in a function that gets called a million times is enough to wear out the button of any mouse if you have to click the Continue button 999,999 times. Furthermore, quite often the problem with a loop will be found at the end, not the beginning. Placing a breakpoint in the middle of a loop can be a study in tedium.

What you really want to do is break at the moment your application is doing something interesting or suspicious. For example, you want to break a loop on its last iteration or just when a parameter is NULL.

You can accomplish this by using a breakpoint conditional. In the Condition field of the breakpoint, enter any C Boolean expression. If the breakpoint is enabled and the conditional expression evaluates to true when the breakpoint is encountered, the breakpoint stops the program. Otherwise, the breakpoint is ignored and the program continues to run.

Conditional Breakpoint Example

The best explanation is an example. The following function calculates a factorial:

static long long int factorial( long long int n )
{
     if (n>=1)
         n *= factorial(n-1);
     return (n);
}

int main (int argc, const char * argv[])
{
     printf("20! = %lld
",factorial(20));
     return 0;
}

You build and run the application, and it produces the following output in the debugging console window:

20! = 0

Clearly, that's not the correct answer. You suspect that the problem is when n is small, so you set a breakpoint at the first line of the factorial() function (the line if (n>=1)). You start the program under the control of the debugger, and it immediately stops in the factorial function. The variable n has a value of 20.

You click Continue and the program recursively calls factorial again, causing the breakpoint to stop again; this time n equals 19.

You can see where this is leading. You'll have to restart the application another 18 times before you get to a value of n that's interesting. Though 18 isn't so bad, 180 would be, and 18,000 would be ridiculous in the extreme.

What you really want to know about is what happens when n is small (2 or less). To find out, you set a breakpoint condition, as shown in Figure 18-23. Now the breakpoint stops only when n is less than or equal to 2.

FIGURE 18-23

Figure 18-23. FIGURE 18-23

With a single breakpoint condition, you've skipped to the 19th invocation of the factorial function in a single debugging step. Now that you're here, you use the Step Over and Step Into commands to walk through the next few invocations of factorial and immediately see what the problem is: When n is 1, the if condition is still true, factorial(n-1) is called, which returns 0, and the multiplication zeros out the total.

The solution is change the conditional to if (i>1).

Conditional Expressions

A breakpoint's conditional expression can contain only primitive C statements. It can't employ preprocessor macros or make use of any variables beyond what appears in the variables pane. In other words, it can only evaluate expressions based on what the debugger knows about your program.

As an example, take the C variable char string[MAX_LEN]. Assuming MAX_LEN was 100, you could test to see if the string buffer contained a character near the end of the array using the expression string[98]!=''. However, you could not use the expression string[MAX_LEN-2]!='' because the debugger doesn't normally know about preprocessor macros.

If there is a problem with the expression, Xcode displays a warning symbol next to the condition in the Breakpoints window. Sometimes this is normal, because an expression might refer to local variables that aren't in scope when the condition is defined. The debugger reevaluates the breakpoint condition when the breakpoint actually occurs, but if the expression is still invalid, it is ignored and the breakpoint acts as if it has no condition. The debugger also notes the problem with a message in the debugger console like "warning: Error parsing breakpoint condition expression."

Warning

Be very careful about expression side effects. The expression i==0 activates the breakpoint when the value of i is zero, and ignores the breakpoint if it is any other value. The expression i=0 sets the value of i to zero and continues executing. Assignment, increment, and decrement operations all have their normal effect on values. Be careful of expressions like o[++k]!=NULL that alter the value of k when the debugger evaluates them. The equivalent expression without side effects would be o[k+1]!=NULL.

Be conservative and defensive when you're using expressions. Don't make assumptions that will cause your expression to miss problems, or cause more problems itself. The following table describes a few examples:

EXPRESSION

RESULTS

i>1000000

Poor. The variable is a signed integer. If the value exceeds MAX_INT, the value will be negative and the condition will never be true. If you're looking for a problem where this integer exceeds its nominal range of 0 to 1,000,000, this expression could miss it.

!(i>=0 && i<=1000000)

Better. The range of the integer is bounded at both ends.

ptr->m!=0

Poor. ptr is a pointer that could be NULL, causing the expression evaluation itself to throw an address error.

(ptr!=0 && ptr->m!=0)

Better. The member value m will not be tested if the ptr is NULL, avoiding possible access errors.

(ptr==0 || ptr->m!=0)

Best. If you really never expect ptr to be NULL, the breakpoint should break on that condition as well.

If your condition requires something to be computed, consider adding some code to your application to help your debugging. Here's an example that assumes that you have defined a DEBUGGING macro and set it to a non-zero value when compiling your code for testing:

#if DEBUGGING
    int actualStrLen = strlen(str);
#endif
    strncpy(buffer,str,1024);

You can now set a breakpoint at the strncpy statement with the condition actualStrLen>=1024.

Breakpoint Ignore Count

A common breakpoint condition is to simply ignore the next few hits. If you hit a breakpoint in a loop that's going to repeat 1,000 times and you want to know what happens toward the end of the loop, you just want to skip over the next 998 occurrences of that breakpoint. This is easily accomplished by setting a breakpoint's ignore count setting.

In the breakpoint window, find the Ignore Count column for the breakpoint and enter a non-zero integer. The next occurrences of that breakpoint will be ignored. To reactive the breakpoint, set the ignore count back to zero.

Breakpoint Actions

In addition to just stopping the program, breakpoints can also perform actions when they are encountered. When a breakpoint is taken, the program stops and control is passed to the debugger. If the breakpoint has breakpoint actions, the debugger immediately performs those actions.

To add or edit actions, expose the breakpoint's contents in the Breakpoints window. For source breakpoints, double-click the breakpoint in the gutter of the source file and Xcode takes you to that breakpoint in the Breakpoints window. Click the + button to add a new action. Click the − button to delete an action. You can't reorder actions, so when you're adding actions, use the + button above the point where you want to new action inserted.

After you've added an action, choose the type of action from the pop-up menu at the top. There are five kinds of breakpoint actions, as listed in the following table:

ACTION

FUNCTION

Log

Logs a message to the system console.

Sound

Plays a sound.

Debugger Command

Executes a command in the debugger.

Shell Command

Executes a shell command.

AppleScript

Executes an AppleScript.

Log a Message

The Log command enables you to generate a message when the breakpoint occurs. How you receive this message is controlled by the two check boxes in the lower-right corner of the action, shown in Figure 18-24. Log outputs the message to the debugger console, and Speak uses the Macintosh text-to-speech technology to say the message out loud.

FIGURE 18-24

Figure 18-24. FIGURE 18-24

The message can contain any of the following special character sequences:

MESSAGE TOKEN

REPLACED WITH

%B

The name of the breakpoint

%H

The number of times this breakpoint has been tripped

%C

The comments attached to the breakpoint

@expression@

Any gdb expression

The open-ended @expression@ form permits you to include anything that the debugger can evaluate at that moment in the program. The example in Figure 18-24 shows a breakpoint that logs the following message:

testTimer fired, userInfo has 3 entries

The expression [[timer userInfo] count] is executed and the resulting value is inserted into the log message. Again, be very careful of side-effects when using expressions.

Make a Sound

The Sound action plays the system sound selected from the pop-up menu.

Have the Debugger Do Something Else

The Debugger Command action is where the power of breakpoint actions really begins to reveal itself. This action enables a breakpoint to execute almost any other gdb command. There's a lot of potential here, but some all-time favorites are the print, backtrace, and breakpoint commands.

Warning

Two gdb commands that you should never use in a breakpoint action are jump and continue . Using jump or continue can interfere with Xcode's ability to execute other breakpoint actions. See the "Breakpoint Continuation" section for information about the action you want your program to continue after hitting a breakpoint.

The print command prints the value of an expression to the debugger console, similar to the Log action. The backtrace command dumps a summary of the call stack.

The log action and the print and backtrace commands are extremely useful, to be sure, but they're all relatively passive. Executing gdb commands automatically opens up a universe of possibilities — or Pandora's box, if you're not careful.

The power of debugger command actions can be illustrated using breakpoints that create and clear other breakpoints. One common problem is trying to debug the behavior of a function when it's called under specific circumstances; circumstances that can't be easily described in a breakpoint conditional. This kind of situation occurs often in complex object-oriented applications where seemingly innocuous methods get called under unusual situations, or at unexpected times. The reason these problems are so difficult to isolate is that the function encountering the problem might be called hundreds, if not millions, of times under normal conditions where the problem doesn't manifest itself; so simply setting a breakpoint at the problematic method is out of the question.

The following example illustrates this kind of puzzle. You're developing an iPhone application with a custom view defined in a nib document. You create many instances of this view, configure it via a -setValues: message, display it, and then discard it.

1 @interface QuickView : UIView {
 2     // ...
 3 }
 4 - (void)setValues:(id)info;
 5 @end
 6
 7 @implementation QuickView
 8
 9 ...
10
11 - (void)setValues:(id)info
12 {
13     // Configure view to display values...
14 }
15
16 @end

So far, so good. While analyzing the performance of your app, you discover that loading every new view from the nib document is taking too much time. You would get better performance if you loaded a few of these view objects from the nib document, and then reused them again later, so you create a simple pool of view objects:

1 @class QuickView;
 2
 3 @interface ViewPool : NSObject {
 4     @private
 5     NSMutableArray *pool;
 6 }
 7 - (QuickView*)view;
 8 - (void)recycleView:(QuickView*)view;
 9 @end
10
11 @implementation ViewPool
12 ...
13 - (QuickView*)view
14 {
15     // Return an existing view from the pool, or load a new one.
16     QuickView *freshView;
17     if ([pool count]!=0) {
18         freshView = [[[pool lastObject] retain] autorelease];
19         [pool removeLastObject];
20     } else {
21         // Load the NIB and extract the single top-level object
22         freshView = [[[NSBundle mainBundle] loadNibNamed:@"QuickView"
23                                                    owner:nil
24                                                  options:nil]
25                      lastObject];
26     }
27     return (freshView);
28 }
29
30 - (void)recycleView:(QuickView*)staleView
31 {
32     [pool addObject:staleView];
33 }
34
35 @end

While testing your program, you discover a problem. The correct order of use should be:

  1. Create a bunch of QuickView objects.

  2. Configure their content using -setValues:.

  3. Display the views.

  4. Return all of the views to the pool.

What's happening in your application is that something is calling -setValues: after the view has been returned to the pool. You suspect that some object is still trying to use the view after it has been recycled, but you can't determine when or why.

This kind of problem can be trapped using breakpoints and breakpoint actions:

  1. Create two breakpoints in ViewPool.m at lines 27 and 32.

  2. Add a breakpoint action to the breakpoint at line 32. Set the action type to Debugger Command, and enter the following gdb command:

    break QuickView.m:13
  3. Add a breakpoint action to the breakpoint at line 27. Set the action type to Debugger Command, and enter the following gdb command:

    clear QuickView.m:13
  4. Build and start the program under the control of the debugger.

Your program runs normally until a QuickView object receives a -setValues: message between being returned to the pool, but before being pulled out again. The sender is the likely culprit.

Here's how the solution works: the breakpoint action in the -recycleView: method creates a breakpoint in the -setValues: method. If any code should invoke -setValues: after one or more view objects have been returned to the pool, the program will stop. Once new view objects are requested again, it's assumed that -setValues: messages are acceptable again, so the breakpoint in the -view method clears (deletes) the -setValues: breakpoint. The result is a breakpoint that only exists between calls to -recycleView: and -view:.

The one annoying aspect of this solution that the breakpoints in -view and -recycleView: still stop execution of your application. The solution to that is to set their continue flag, described later in the "Breakpoint Continuation" section.

Note that the automatically created breakpoints may not appear in Xcode because Xcode didn't create them; but they're fully functional breakpoints nevertheless. Using the debugger command action requires an understanding of the gdb debugger commands and their syntax. Documentation for the gdb debugger is available online at http://www.gnu.org/software/gdb/documentation/.

Run a Script

The Shell Command and AppleScript breakpoint actions can be used to execute any arbitrary command-line program or AppleScript program file.

The usefulness of these actions depends entirely on what you're trying to accomplish. Say you have a database program that is corrupting record data. You could create a breakpoint action to execute a MySQL query statement that would dump the contents of suspect records at strategic points in the program. Another clever trick is to use Mac OS X's screen capture tool (screencapture) to take a snapshot of your display at a critical moment.

The Shell Command action takes a command file and a list of arguments. This must be an executable command file — a binary program or an executable script. You can enter the path directly, or click the Choose button to browse for it. The path can be an absolute or project-relative path. Arguments are space-separated, just as if you executed the command using the shell, and you can include gdb expressions enclosed between two @ characters just like the Log action. Normally, the debugger executes the command asynchronously. That is, it starts the command executing and immediately returns control to the debugger. If you want the debugger to wait until the command is finished before continuing, check the Wait Until Done option.

The AppleScript action accepts an AppleScript in the action pane. The script does not need to be in a handler block. You can compile your script and check for syntax errors using the Compile button and try out your script using the Test button. The AppleScript executes in the context of the gdb process, so interactive AppleScript commands like display can't be used. Commands like delay, beep, and say work just fine. The script can also contain gdb expressions enclosed between @ characters.

Breakpoint Continuation

The earlier example of using breakpoints to set breakpoints glossed over a serious problem. The breakpoints with the actions will still break. After the breakpoint has been hit, and all of its actions have been executed, the debugger still stops your program and waits for instructions. In the example of trying to trap a call that occurs during a destructor, this defeats the purpose of having breakpoints created and deleted automatically. What you really want is for the breakpoint to execute its actions and immediately resume execution of your program. This is exactly what the continue option does.

The continue option is the CD-player-style "play" column in the breakpoints list, as shown in Figure 18-25. Checking this option for a breakpoint means that the breakpoint does not return control to you. The actions of the breakpoint are executed, and the program continues executing exactly as if you had clicked the Continue button.

FIGURE 18-25

Figure 18-25. FIGURE 18-25

The continue option makes all manner of debug automation and checking possible. There are, however, some hazards. Debug actions are executed asynchronously. That is, the debug action merely starts the action. The debugger does not normally hang around waiting for them to complete, so be careful about creating breakpoints that will start, or queue up, hundreds of breakpoint actions. This can be particularly annoying when you're using audio feedback.

The Shell Script breakpoint action has the Wait Until Done option that suspends the debugger and your program until the Shell Command completes. Breakpoint actions can be combined with a breakpoint condition. The breakpoint and all of its actions are executed only if the condition evaluates to true.

Importing and Exporting Breakpoints

Pretend that you are working on a project with another programmer. You've isolated a problem using a complex set of breakpoints. How do you send those breakpoints to your team members so that they can reproduce the problem and fix it? Project breakpoints are saved on a per-user basis in the project, so you can't just give them a copy of the project document. When they load the project, they won't see any of the project breakpoints you've set. And scrawling breakpoint descriptions on napkins doesn't sound particularly efficient either.

The solution is to export the breakpoints to a file using the Export Breakpoints command. Select any set of breakpoints or breakpoint groups in the Groups & Files pane and Right/Control-click the selection. Choose the Export Breakpoints command to export the selected breakpoints to a text file.

The Import Breakpoints command creates a new subgroup with the name of the breakpoint export file and populates it with its contents. Rename and reorganize the imported breakpoints as you see fit.

As a single developer, you can use these commands to archive a collection of breakpoints for some future purpose or move some breakpoints between projects without resorting to making them global breakpoints.

Breakpoint Templates

Xcode provides a number of preconfigured breakpoints that you can insert by Right/Control-clicking a line in your source code and then selecting any of the breakpoint templates from the Built-In Breakpoints menu. You can also create your own breakpoint templates, which will appear in the same menu.

To create a reusable breakpoint, create and configure a breakpoint. Once you are happy with it, create a breakpoint group in the Groups & Files pane named Template Breakpoints (Right/Control-click and choose Add

Breakpoint Templates

Any breakpoints that appear in the Template Breakpoints group also appear in the Right/Control-click contextual menu in any source file's gutter. Creating a breakpoint from one of the templates inserts a breakpoint that's a copy of the breakpoint in the template. The only thing that is different is its location.

EXAMINING DATA

Now that you've learned all about controlling the flow of your application while debugging, let's return to examining the content of variables. "The Variables Pane" section briefly introduced the variables pane of the debugging window. Now look at that pane in a little more detail and look at other ways of examining the contents of memory.

To recap, the variables pane (previously shown in Figure 18-4) displays the known variables within the scope of the selected stack frame. Variables are grouped by type. Structures and objects appear as groups, forming a hierarchy of containers and values. These are described by the debug symbol information attached to your program.

As you step through your program, the debugger compares the values that were displayed when your program was last started against the values that appear when the debugger stopped it again. Any values that are different are highlighted in red, as shown in Figure 18-26 — it may be difficult to see in this black-and-white illustration, but the value of variable j is red.

FIGURE 18-26

Figure 18-26. FIGURE 18-26

The code was stopped at line 57 where the value of j was undefined. The Step Over command was issued. The debugger allowed one statement (the for statement) to be executed and stopped the program again. The value of j is now 4 and Xcode highlights the change in the display. Using the Step Over command again returns the value to black again, because that statement did not alter its value. It doesn't matter how much the program executes between stops. As long as the variables pane is showing the same set of variables at the next stop, Xcode highlights whatever values are now different.

Scalar Formats

The Value column displays the primitive value of each variable. For scalar values, this is a numeric value. For structures and pointers to structures, it is the address of the structure or the value of the pointer. The default display format for scalar values is Natural. For signed integers and floating-point numbers, the column displays a signed decimal value. Unsigned integers display an unsigned decimal number. Character types display both the decimal and Unicode representations of the value. Pointers and structures are shown as hexadecimal memory addresses. The natural format is usually sufficient, but you can manually choose a different representation. The choices are as follows:

  • Hexadecimal

  • Decimal

  • Unsigned Decimal

  • Octal

  • Binary

  • OSType

You can find these formats in the Run

Scalar Formats

Viewing Data in Another Window

You can also choose to examine the contents of a value or structure in a separate window. Double-click the variable name, choose the Run

Viewing Data in Another Window

If a variable type isn't even close to the type of data it represents, you can use the Run

Viewing Data in Another Window
FIGURE 18-27

Figure 18-27. FIGURE 18-27

Xcode then attempts to interpret the value of that variable using the type cast. This is particularly useful for generic types such as void*. As shown in Figure 18-27, the pointer to a string was assigned to a void* variable. By using the View Value As command, you can coerce the debugger to interpret the void* value as if it was a char* variable, shown on the right.

Sometimes you just need to look at memory. Using the Debug

FIGURE 18-27
FIGURE 18-28

Figure 18-28. FIGURE 18-28

The browser displays a block of memory in hexadecimal and ASCII formats, much like the hexdump command. The Address field determines the starting address of the display. Initially, this is the address of the selected scalar, structure, or object in the variables pane. If the selected variable is a pointer or object reference, the address is the value of that pointer (a dump of what the pointer points to, not a dump of the pointer's value). The Bytes menu lets you select how much memory is disassembled in the window. Choose one of the preset values or enter your own. Use the up and down arrows between the Address and Bytes field to move one page of memory forward or backward. The Word Size and Columns menus control the number of bytes in each column of hexadecimal values, and the number of columns in each line, respectively.

There is only one memory browser window. Selecting a new address using the View As Memory command simply changes the address in the Address field. The Address field has a pop-up menu that keeps a short history of previously viewed addresses. If you need to follow a pointer to another address that you find in a block of memory, simply copy the address from the hexadecimal listing and paste it into the Address field (prefixing it with 0x).

If you try to view memory that is outside the address space of your application, the memory browser displays A's for the bytes it can't access.

Viewing Global Variables

The Globals group in the variables pane contains a selected set of global variables that you want to examine. Normally, this group has nothing. It's impractical for this group to contain every global variable in your application's process space. The group could contain hundreds of variables, be impossible to navigate, and place a huge burden on the debugger display.

Instead, the group starts out empty. Use the global variables window, shown in Figure 18-29, to add variables to this group or merely browse the global variables in your application. You can open this window using the Run

Viewing Global Variables
FIGURE 18-29

Figure 18-29. FIGURE 18-29

The global variables window is divided between a list of modules on the left and the list of global variables in each module on the right. Select a module, and Xcode lists all of the global variables that your application has access to on the right. To search for a particular variable name, enter some fragment of the variable's name in the search field at the top of the window.

Figure 18-29 examines the global variables declared in a program named DataBlock. The listing tells you the filename the variable is declared in, its current value, and its type. By selecting the box in the View column, this variable is added to the Globals group in the variables pane. The viewed global variables are recorded in the user preferences of the project document. After these variables are added, they will always appear in the Globals group of your variables pane until they are removed or until the variable itself no longer exists.

Expressions

Another way to view variables is through expressions. Expressions appear in the Expressions window, shown in Figure 18-30. You can open this window using the Run

Expressions
FIGURE 18-30

Figure 18-30. FIGURE 18-30

These are debugger expressions and are subject to all of the limitations and caveats of breakpoint conditions. In the Expressions window, each expression acts like a variable. You can alter its display format or open it in a separate window. You can also add type casts to an expression to coerce the interpretation of the expression's value.

Expressions are interpreted within the context of the currently selected stack frame, and those expressions retain that context. In the example previously shown in Figure 18-30, there are two expressions that resolve to value of the integer i, but these two variables are from different stack frames. Each was added to the window and a different stack frame was selected in the threads pane. The first one was added while a function was executing, but that function has now returned. Therefore, the context that defined that variable no longer exists and the expression is marked as "out of scope."

To delete an expression, select it in the Expressions window and press the Delete key.

Expressions are very useful for examining the contents of array values. An expression like stack[1] examines the second element in the stack array. The expression buffer[index] examines whichever element the variable index refers to.

DATA FORMATTERS

The summary column in the variables pane is designed to present a compact, informative explanation of the value or object. For some objects, like strings, the summary value is obvious. For objects like a collection it might display the size of the collection — not a lot of detail, but still informative. For an opaque FSRef structure, it might convert that structure into a readable filename — very informative, indeed. This descriptive transformation is done using data formatters.

Xcode includes many data formatters, but they won't help with any object that you define. You can create your own data formatters to summarize complex data structures and objects in the debugger.

Creating a Custom Data Formatter

Creating your own data formatters is very easy. It is really nothing more than a format string with placeholders for values or expressions derived from the variable being summarized. Any regular text in the data formatter is displayed verbatim. There are two kinds of placeholders: references and expressions.

References are delimited by two percent characters (%reference%). A reference can refer to a single member variable by name. If the variable is contained in a substructure, use the appropriate period (.) separated variable name. You cannot use operators such as pointer dereferences or array indexes. For that, you need to use an expression. Taking the class that's defined in Listing 18-3, the reference to the integer record_no of DataBlock class would be %header.record_no%.

Example 18-3. Sample class

typedef struct {
    int record_no;
    unsigned long checksum;
} BlockHeader;

@interface DataBlock : NSObject
{
    @public
        BlockHeader     header;
        NSMutableData*  data;
}

An expression is any debugger expression; the same kind of expression that you can add to the Expressions window. In fact, it's good to think of an expression in a data formatter as an expression in the Expressions window, as you'll see in moment.

Expressions are contained between matching braces ({expression}). Unlike references, expressions do not assume the context of the value being examined. To refer to the object being examined, use the $VAR macro in the expression. $VAR will be replaced with the name of the variable when the data formatter is evaluated. Using the previous class as an example again, the expression to access the record_no value would be {$VAR.header.record_no}. If you're now guessing that you can refer to other variables in the context of the current stack frame, you're correct. However, this isn't a good idea, which is explained later. Limit your evaluation to the structure or object being examined.

The advantage of expressions over references is that they are more expressive. You can perform math, include conditionals, and even call member functions. Again using the class defined in Listing 18-3, here are some valid expressions:

  • {$VAR.header.record_no}

  • {$VAR.header.checksum&0x0000ffff}

  • {$VAR.data?(int)[$VAR.data length]:0}

Combining these two techniques, you can now create a data formatter for the DataBlock object type. Start by running the program and stopping the debugger with an instance of the DataBlock class in scope. Make sure that Run

Sample class
FIGURE 18-31

Figure 18-31. FIGURE 18-31

After it has been entered, this text becomes the data formatter used for every instance of the DataBlock class. The debugger resolves the references and expressions for each instance, creating the more informative summary shown in Figure 18-32.

FIGURE 18-32

Figure 18-32. FIGURE 18-32

The syntax used for references and expressions can be extended to obtain other information about the value or expression. The final display value can be something different from the value to which the expression evaluates. The other types of information that can be extracted from an expression are chosen using one of the column selectors listed in the following table. The "column" you are selecting is one of the columns in the variables pane or the Expressions window. In essence, the result of a reference or expression is treated as if it had been entered into the Expressions window. The column selector lets you choose which column in the window will be used as the result of the expression. You can think of an expression result as an object with four properties (value, name, type, and summary) — the column selector lets you choose which property to display.

COLUMN SELECTOR

DESCRIPTION

{expression}:v, %reference%:v

The value of the expression — that is, the primitive numerical value that would appear in the Value column of the Expressions window. This is the default column. Omitting the column selector is equivalent to using :v.

{expression}:t, %reference%:t

The type of the final data object to which the expression evaluates. A numerical expression would result in a primitive data type, such as int or double. The type of an expression that refers to a member variable, or calls a function, will be the type of the expression's result. For example, the expression {[$VAR owner]}:t would display the type of the object returned by the method owner.

{expression}:s, %reference%:s

This selector results in the text that would appear in the Summary column of the expression. Because the Summary column can be formed using data formatters, this is a way of using other data formatters in portions of your data formatter. You can only use this on expressions that have a summary display. Expressions that result in primitive values do not have any content in their Summary column.

{expression}:n, %reference%:n

The name of the variable or expression that would appear in the Expression column of the Expressions window. The column is self-referential and not particularly useful.

The type column (:t) can be useful for displaying the type or class of a member value. For example, if you have a class that manages a collection of homogenous objects, a data formatter of collection of {[$VAR lastObject]}:t would tell you what kind of objects your collection contains.

The summary column selector is the most useful. You can use it to construct data formatters from other data formatters.

To get a feel for creating your own data formatters, look at the following example. Let's assume you have a project with the DataBlock class shown in Listing 18-3, and a subclass named StampedDatablock, shown in Listing 18-4.

Example 18-4. DataBlock subclass

@interface StampedDataBlock : DataBlock
{
    @private
        NSCalendarDate*    createdDate;
        NSCalendarDate*    modifiedDate;
}

To create a custom data formatter for these two classes, follow these steps:

  1. Build the application, start it under the control of the debugger, and stop at a breakpoint where a DataBlock and StampedDataBlock object are in scope, as shown in Figure 18-33.

  2. Set the data formatter for the block variable to record %header.record_no%, {(int)[$VAR.data length]} bytes.

  3. Set the data formatter for the trackedBlock variable to {(DataBlock*)$VAR}:s, created %createdDate%:s.

  4. Continue stepping through the program.

FIGURE 18-33

Figure 18-33. FIGURE 18-33

The first data formatter created for the DataBlock class summarizes its contents by accessing its record_no and data instance variables. The debugger now presents a much friendlier summary of the object's state.

The StampedDataBlock formatter is a little trickier. The StampedDataBlock class does not inherit the data formatter for DataBlock. Data formatters for each type are independent of one another.

Two problems are inherent in creating a data formatter for the new subclass. First, you don't want to repeat everything you wrote for the DataBlock formatter. Secondly, you don't want to write a formatter for the NSCalendarDate member object. The summary column selector lets you avoid both of these problems by setting the data formatter for the StampedDataBlock class to {(DataBlock*)$VAR}:s, created %createdDate%:s. The first expression casts the object to an instance of its superclass and obtains text that would appear in its Summary column, effectively calling the data formatter you created for the DataBlock class. The second reference obtains the value of createdDate and inserts what would appear in its Summary column, essentially using Xcode's built-in data formatter. The final result, shown in Figure 18-34, is a data formatter that extends the data formatter of its superclass using a built-in data formatter supplied by Xcode.

FIGURE 18-34

Figure 18-34. FIGURE 18-34

Troubleshooting Data Formatters

Data formatters can be very useful during debugging. You can create data formatters that quickly summarize the state of complex objects. This allows you to concentrate on the high-level logic of your application, rather than spending all of your time digging through the member variables of objects trying to decode their content. Writing data formatters, however, can be a frustrating experience. If there is anything the debugger doesn't like about your data formatter, it won't use it. The following table lists some of the more common problems you can encounter while creating data formatters:

TYPE OF PROBLEM

SOLUTION

Syntax

Be extra careful about the syntax of your expressions and references.

Quotes

Double quotes in the body of an expression must be escaped with a backslash. Example: name "{[$VAR getProperty:"name"]}:s". Notice that the quotes inside the expression are escaped, but not in the text outside the expression.

Unknown types

The debugger often does not know the data type of values returned by functions. If you have any doubts, cast the result: author {(NSString*)[$VAR authorName]}:s

Execution problems

Expressions that call functions have to function perfectly. The formatter name {[$VAR name]}:s will fail if the method name throws an exception, tries to access a NULL variable, can't allocate memory, or any of a limitless number of similar run time problems. The functions that you call using data formatters should be extremely defensive.

Null summary

You cannot use the :s column selector if the expression results in a data type that has no summary column content.

Invalid references

Expressions that use other variables in the current stack frame context will fail when interpreted in a different stack frame or execution context where those variables don't exist. Data formatters should concern themselves only with examining the contents of the structure or object.

ZeroLink, dynamically loaded libraries

This is yet one more situation where dynamically loaded libraries can trip you up. Expressions executed in the debugger will not cause unreferenced symbols to load. If your application hasn't caused a symbol or function to load yet, a data formatter that uses that function or type will fail.

Temporary Objects

(For Objective-C programmers) Be warned that creating auto-released objects in your data formatters may result in memory leaks. An example would be {(NSString*)[NSString stringWithCharacters:$VAR.uStr.unicode length:$VAR.uStr.length] }:s. The problem here is that the NSString object is created in the context of the debugger and has no auto-release pool. You will see a "leak" message in the debugger log.

Side Effects

Data formatters can call functions in your application. Side effects, such as altering instance variables or releasing objects, can have unexpected consequences in your application and your debugging efforts.

If you are having problems getting a formatter to work, break it down into its individual components and subexpressions and try each one at a time. Slowly build up the expression until you get it working or find the element that thwarts your efforts. Try expressions without the column selector. Cast return values liberally. Replace macros and function calls with constants. Turn off ZeroLink. Add the same function call to your code and try debugging it.

Data formatters you define are stored in the ~/Library/Application Support/Apple/Developer Tools/CustomDataViews/CustomDataViews.plist file. Data formatters are global to all projects and are not stored in the project document. Sharing data formatters with other developers will require some copying and pasting, or you may just want to exchange CustomDataViews.plist files.

Beyond Data Formatter Strings

Although data formatters can do a lot, they are limited to what can be expressed in a format string. If you need a data formatter that exceeds these capabilities, you can develop your own data formatter plug-in. The descriptions for doing so are in the DataFormatterPlugin.h file, buried inside the Xcode application itself at /Developer/Applications/Xcode.app/Contents/PlugIns/GDBMIDebugging.xcplugin/Contents/Headers/DataFormatterPlugin.h. This file contains detailed information about formatter strings, the format of CustomDataViews.plist, and how to create a data formatter plug-in, among other topics.

In brief, you create a data formatter plug-in by creating a bundle. The bundle contains its own CustomDataViews.plist file. Unlike data formatter strings that you type into the debugger window, the data formatter strings in the bundle's CustomDataViews.plist file can call any of the functions defined in the plug-in bundle. The sample ManagedObjectDataFormatter project produces a data formatter plug-in for managed objects. You can find it by searching the Xcode documentation for ManagedObjectDataFormatter. Use this project as a template for creating your own data formatter plug-ins.

Object Descriptions

Like data formatters, many object-oriented languages have adopted conventions for converting any object into a textual representation. In Java, this is the toString() function. Objective-C uses the -[NSObject description] method. If you are using an object that supports one of these standards, you can use the Run

Object Descriptions

WATCHPOINTS

Watchpoints are breakpoints for data. You can make any variable a watchpoint. Whenever the debugger detects that the value of that variable has changed, it stops your application.

Watchpoints sound great, but they are fairly limited. The biggest problem is that your application can't execute any code where the watchpoint variable is out of context, so they are mostly useful for global variables that are always in scope and for catching state changes in a loop.

You set a watchpoint by first selecting a variable in the variables pane. Choose the Run

WATCHPOINTS
FIGURE 18-35

Figure 18-35. FIGURE 18-35

You can choose to acknowledge the event and leave the watchpoint set, or disable the watchpoint by clicking the Disable button. Watchpoints are automatically deleted whenever your application exits the context where the watchpoint variable exists. Watchpoints are not retained between debug sessions.

Note

You can create an effect similar to a watchpoint using a breakpoint conditional like i!=0 . It's not as convenient as a watchpoint, but it's more durable.

To remove a watchpoint, select the variable being watched and choose Run

FIGURE 18-35

CHANGING DATA AND CODE

So far, this chapter has taken a rather passive approach to debugging. You've viewed code and variables in countless ways, but you haven't actually changed anything. Xcode lets you alter both data and code while your application is executing. This can be a huge time-saver when you're debugging. You can change the values of parameters to test specific cases, or correct a value that was miscalculated and continue testing.

Changing variables is easy. Select a primitive variable and choose the Edit Value command from either the Run

CHANGING DATA AND CODE

If the variable is a pointer, you can change the address of the pointer or you can expand the variable and Xcode allows you to change any primitive values to which the pointer points.

The Magic Fix

It's simple enough to poke a new value into a variable and continue executing, but what if the code itself is incorrect? Xcode allows you to fix that too.

This bit of magic — and it really is something close to magic — is a feature called Fix & Continue. As the name implies, it enables you to recompile code in your application and continue debugging it without restarting your program. Use of this feature depends on some prerequisites. The debug version of your application must be built with the following:

  • The Fix & Continue (GCC_ENABLE_FIX_AND_CONTINUE) build setting checked

  • Compiled using gcc version 3.3 or later

  • Full debug symbols

  • No optimization

If, for any reason, the debugger can't use Fix & Continue, the Fix command will be disabled while debugging.

Using this feature is deceptively simple. Say, for example, you discover a bug in your source code while you're debugging. Listing 18-5 shows a common programming mistake: a loop with a missing increment statement.

Example 18-5. Bad loop

Token findOneToken( const char* s )
{
    while ( *s!='' && isspace(*s) )
          s++;
Token token;
    token.word = s;
    token.length = 0;
    while ( *s!='' )
        {
        char c = *s;
        if (isspace(c))
            break;
        token.length++;
        }

    return (token);
}

After stepping through the second loop a few times, it becomes obvious that it gets stuck because the statement c = *s should have been c = *s++.

To correct this code, simply edit the statement so that it reads c = *s++ and choose Run

Bad loop

If that was all that needed to be done, you could continue debugging the application. Replacing the buggy code has, unfortunately, created another situation. Before you added the increment operator, the s variable wasn't being incremented — but token.length was. The length value now has a non-zero value and won't agree with the length of the string when the function returns.

Can you continue debugging your application without having to restart it? You have two ways of addressing this. The first would be to use the variables pane and simply edit the value of token .length, setting it back to 0. Another way is to alter the program counter so that the program continues executing at a different location in the code. Here the PC indicator is being dragged back up to the token.length = 0 statement so that the entire second loop starts over from the beginning, as shown in Figure 18-36.

FIGURE 18-36

Figure 18-36. FIGURE 18-36

When the execution is continued, the program starts again at the top of the (now bug-free) loop, reinitializes token.length to 0, and executes correctly.

Magic Fix Limitations

Fix & Continue does have some limitations. Here are a few:

  • Fix is not supported by all debuggers. Support for Fix & Continue comes and goes in gdb.

  • You cannot redefine typedef variables, data structures, classes, or function arguments.

  • You cannot redefine the automatic variables on the stack frame.

  • You cannot redefine global data variables.

  • You cannot make any change to your application's resources, such as icon or nib files.

  • You cannot fix a bad reference to a function by renaming the function.

In short, you can make any change that alters only the executable code of one or more functions. You can't make a fix that alters the data types or linkages that are, or could be, used anywhere else in the application.

You should be aware of a couple other caveats about how Fix & Continue works. Fix & Continue replaces the code of a function that was executing and changes the current program counter so that execution continues in the new code. However, it does not change the program counter in any other stack frame. Say that Function A calls Function B. If you stop the program in Function B and fix Function A, when Function B returns it will return to the old Function A, not the corrected one. The corrected Function A won't be called until something else calls Function A again.

Fix & Continue only compiles and replaces the in-memory image of a single file. If you make changes in several files, you will need to perform a Fix & Continue on each one.

Also note that Fix & Continue only patches the memory image of the running application. It does not alter the original executable file that was produced by the last build. If you restart your application the old (bug-ridden) version is executed. Worse, the executable code is now out of sync with the modified source files. Make sure you follow each debugging session where you use Fix & Continue with a new build to incorporate the changes you made into the final product.

DEBUGGER CONSOLE

The debugger console has been mentioned several times in this chapter. To access it, choose the Run

DEBUGGER CONSOLE

Like many of Xcode's interfaces, the debugger window is just a graphical front-end to the gdb (or Java, or AppleScript) debugger that runs underneath it. The debugger console window is a shell window that interacts with the debugger process directly. When you click the Continue button in Xcode's debugger window, Xcode just sends a continue command to gdb. Any information that gdb outputs is visible in the debugger console window.

If you are having problems with the debugger, the debugger console window is the first place to look. Problems setting breakpoints, resolving symbols, or evaluating expressions are logged there.

FIGURE 18-37

Figure 18-37. FIGURE 18-37

More interesting is that the debugger console window is a fully interactive terminal window. Through this window you can type commands directly into the debugger. The debugger provides many features that are not available through the graphical interface provided by Xcode. Of course, this requires an understanding of the gdb (or Java debugger) commands and their syntax. You can learn the basics by entering the help command at the (gdb) or JavaBug> prompt. The AppleScript debugger has no interactive commands.

SHARED LIBRARIES

One miscellaneous debugger tool is the shared library window, shown in Figure 18-38. Opened with the Run

SHARED LIBRARIES
FIGURE 18-38

Figure 18-38. FIGURE 18-38

The Module column shows the name of each shared library. The Address column shows the address in the application's memory space where the library has been loaded. If the field is blank, the library has not been loaded into memory yet. The complete path to the selected library is shown at the bottom of the window.

The Starting Level and Current Level columns show what level of debugging symbols should be loaded for each library when the debugger starts and right now, respectively. The debugger can avoid loading symbols for a library, load only the external declarations, or read all debugging symbols including source file line information. The less debugging information loaded, the faster the debugger starts up and runs — and the less it knows about your application.

Normally, the debugger loads only the external declarations. This is the superficial information about the library. Whenever it needs to know more detailed information, it automatically loads any remaining debugger symbols that describe data structures, source file line numbers, and so on. You can watch this process at work. Start an application and set a breakpoint very early in the application, like at the first line of main(). Open the shared library window and the global variables window. Start looking through the libraries in the global variables window. As you browse each library for global variables, the status of the loaded symbols in the shared library window changes from None or External to All as you force the debugger to load additional debugging symbols for each library — debug symbol information that the debugger needs to display the global variables in each library.

You can manually load the symbols for a library into memory by changing the setting in the Current Level column. The change occurs immediately. The Starting Level column determines what the Current Level column will be set to when the library is initially loaded. You can set this to a particular level or use the Default setting. If set to Default, the level used will either be the Default Level for System Libraries or User Libraries, as appropriate, set with the two global pop-up menus at the top of the window. The default level of External is known as "lazy" symbol loading; the idea is to get your application running in the debugger as quickly as possible by loading only the minimal amount of information and worrying about the details later. You can disable Lazy Symbol Loading in the Debugger pane of the Xcode Preferences. Disabling Lazy Symbol Loading changes the User Libraries default from External to All.

The Reset button at the bottom sets the Starting Level of all libraries to Default.

You can manually add or remove libraries from the list by clicking the + and − buttons at the bottom of the window. To add a library, browse to the location of the library and open it. Remember that in the file browser, the Shift+Command+G key combination opens the Go To sheet, allowing you to enter a path to normally invisible directories like /usr/lib.

The shared libraries window is mostly informational, but it can be used to give hints to the debugger telling it to load — or avoid loading — debug symbol information at strategic times. If you are debugging a very large application, this can speed up the debugger by not loading unnecessary symbols or speed up your debugging workflow by preloading symbols you need. You cannot use this window to force libraries to load or unload or to force symbol information that the debugger is using out of memory.

CUSTOM EXECUTABLES

So far you've been running and debugging simple applications without much thought to the environment in which those applications were running. When you created a target to produce your application, Xcode also created a matching product and an executable. The executable, which appears in the Executables smart group of the Groups & Files pane, defines the execution environment for your application. It defines what binary program will be executed when it is launched, what parameters and environment variables will be passed to it, and what its I/O file descriptors will be attached to. You can customize the environment settings of an executable created by Xcode, or you can create your own.

You may want to customize or create a custom executable for several reasons. For example:

  • You need to pass command-line parameters to the process when it is started.

  • You need to set environment variables for the process, or choose a different working directory before the process is started.

  • You want to redirect the input or output of the tool to something other than the run or debugging console windows.

  • You need to debug an executable that Xcode didn't produce or for which Xcode doesn't automatically create a product and executable, such as a program produced by an external build process.

  • Your executable is launched via a script or some other process.

  • The project you are developing is a plug-in or a framework that can't be executed on its own. You need to launch an application that will load the plug-in and exercise it.

General Settings

Open the Info window for an existing executable, or choose the Project

General Settings
FIGURE 18-39

Figure 18-39. FIGURE 18-39

The Path option is the path, relative to the build product's directory, to the program that will be launched when the executable is started. Normally this is the binary application produced by your target. Change this if you want a different program executed instead. An example would be a UNIX program that is started by a shell script that checks the state of the program, gathers configuration information, and so on, before launching the binary program. If your product is started by such a script, enter the path to the script here.

At the bottom of the window is the current or working directory that will be set before the executable is launched. This is important to some executables that expect to find resources or perform work on files relative to the current directory. Normally this is set to the build directory for the product. That is, the current directory will be the same directory that contains the executable. The build product directory will change depending on which build configuration is active. You can alternatively choose the project directory or a custom directory. Enter the custom directory path, or click the Choose button to browse for a folder.

The Use Suffix When Loading Frameworks option passes a special flag to the dynamic linker. It tells the system's run time library loader to search for alternate versions of framework libraries. Many libraries are provided in alternate versions designed to aid in debugging or profiling. They may include additional integrity checks or log informational messages to the system log that are useful during development. When set to No, the loader links your application to the standard system libraries.

The Use For Standard Input/Output option determines where the stdout, stdin, and stderr file descriptors will be connected when the executable is launched. The Pseudo Terminal connects your application to the run or debugging console you've been using throughout this chapter. The Pipe choice is only useful for remote debugging, as described later. The System Console choice directs the program's output to the system console. Macintosh applications launched by the user, and command-line programs launched without a shell, normally have their output redirected to the system console log. You can review the system console log using the Console utility provided with Mac OS X. When set to the System Console choice, stdin is connected to null.

Arguments and Environment

Use the Arguments pane to pass additional arguments and environment variables to your program. This can be extremely useful for testing command-line applications or setting special features of the run time system.

To add an argument, click the + button beneath the Arguments pane and type in the argument value, as shown in Figure 18-40. You can later edit arguments by double-clicking their values and reorder them by dragging. The check box in the left column is an enabled setting. Only arguments that are enabled are passed to the executable. This makes it easy to keep several commonly used arguments in the list and quickly select just the ones you want. Select an argument and click the − button to delete it entirely.

The environment variables pane works exactly the same way as the arguments, except that this pane defines named variables that are defined in the environment of the process. The values of environment variables can also reference any of the following build settings: SYMROOT, SRCROOT, OBJROOT, BUILT_PRODUCTS_DIR, and TARGET_TEMP_DIR. Chapter 17 covers referencing build settings in general and the meaning of these build settings in particular.

FIGURE 18-40

Figure 18-40. FIGURE 18-40

Debugging

The Debugging pane, shown in Figure 18-41, controls additional settings that affect the execution environment of your program when you launch it under the control of the debugger. The When Using option controls which debugger Xcode will start to debug your application. The debugger chosen must be capable of debugging the kind of application that the executable produces. The Java debugger cannot debug a binary executable. Xcode sets this appropriately for executable products produced from targets. For custom executables, you need to tell Xcode which debugger is appropriate.

FIGURE 18-41

Figure 18-41. FIGURE 18-41

The Use For Standard Input/Output option controls the connections to the program's I/O. You may want to set this to System Console if the output of the program is obscuring the output of the debugger itself in the Debugger Console window. It is also possible to distinguish between the output of the debugger and your program by coloring their text differently in the Debugger Console window. (See the Debugger pane settings in the Xcode preferences.) If you are doing remote debugging, this option must be set to Pipe.

The next two options configure the gdb debugger for remote execution. The "Remote Debugging" section in this chapter explains how to configure a program for remote debugging.

The Start Executable After Starting Debugger option automatically starts your application running as soon as the debugger is loaded. Normally this is checked, but you may not want this to happen. Turning this option off launches the debugger, but performs no further action. This permits you the opportunity of making special adjustments in the debugging environment, such as setting breakpoints or editing static variables, before the program starts running. You can even use the debugger command to attach to an already running instance of your application, rather than launching a new one.

The Break on Debugger() And DebugStr() option sets the USERBREAK environment variable before your application is started. The presence of this environment variable causes the Debugger() and DebugStr() functions defined in Core Services to send a SIGINT signal to your program if either of these functions are called. Without this setting, these functions do nothing or just log a message to the console. When running under the debugger, a SIGINT signal suspends your program just as if it hit a breakpoint. This option sets this environment variable only when the executable is launched for debugging. To have it set all of the time, set the USERBREAK environment variable to 1 in the Arguments pane.

The Auto-Attach Debugger on Crash option causes the debugger to attach to the executable's process should it crash. This is equivalent to stopping your executable immediately after it crashes, but before the process is terminated, and issuing the Run

FIGURE 18-41

Note

The Auto-Attach Debugger on Crash option is really only meaningful in Mac OS X 10.5 (Leopard) and earlier. In 10.6 (Snow Leopard), Xcode preemptively starts the gdb debugger every time you launch your application from within Xcode, so in effect it's already attached.

The Additional Directories to Find Source File In pane lists the paths to where the debugger can find the source file used to build the executable being debugged. Normally you don't need to add anything here because Xcode automatically searches all of the source directories in your project. However, if you have included source files outside your project or the executable was built from source files that Xcode doesn't know about — files in an externally built target, for instance — add those directories here. You can click the + button and type in the path, or drag a folder from the Finder and drop it into the list.

Selecting an Executable

The active executable that you select using the Project

Selecting an Executable

When you switch targets, Xcode examines the active executable. If the active executable is the one created for the product produced by the current target, and the target you are switching to produces an executable product, the active executable is changed to match the new active target. For most projects, this means that the active executable will "follow" the active target as you change between them. However, if you have targets that don't produce an executable, or have created and made custom executables active, changing the target may not change the executable. You need to be especially watchful if you have created aggregate targets. An aggregate target that builds both a client and server application will not select an active executable when you make that target active. You must specify which executable, the client or the server, needs to be launched when you choose Run or Debug.

DEBUGGER PREFERENCES

You can use the Debugging pane of the Xcode preferences, shown in Figure 18-42, to configure a few common debugging features.

FIGURE 18-42

Figure 18-42. FIGURE 18-42

Starting on the left are the Fonts and Colors preferences. Select a category of text from the pop-up menu, and then change the font, style, and color of the font using the Set Font button. With these settings, you can alter the appearance of text that appears in the run and Debugger Console windows. This makes it possible, or at least easier, to differentiate between the text output by the debugger and the text output by your program. By default, Xcode colors the debugger's prompt and bolds the text sent to the debugger. All output appears the same.

The Instruction Pointer Highlight color is the color used to highlight the currently executing line of source code in the debugger. If you prefer to see a different color, click the color well or drag a color into the color well to change it.

The On Start setting lets you choose to automatically open certain windows when you start your application using the debugger. The choices are:

  • Do Nothing

  • Show Console

  • Show Debugger

  • Show Console & Debugger

  • Show Mini Debugger

If you tend to use the in-editor debugging controls, set this to Show Console. Otherwise, choose Show Debugger or Show Console & Debugger so that the debugger window will automatically open when you begin debugging.

If you are using the mini-debugger, the best choice is to open it automatically; by the very nature of programs that you'd want to use the mini-debugger with, opening it after you've started your application can be awkward.

The GDB Log setting will optionally write an extremely detailed log of your debugging session to a text file for later analysis. This is particularly useful if you're trying to diagnose a problem with gdb commands, breakpoint actions, and so on. Enter the path to where you want the file written — just remember that this is a global setting that affects all projects. If the path is left empty, Xcode writes to a temporary file.

Load Symbols Lazily controls the default level of symbols that load when modules and dynamic libraries are loaded into memory. Enabling lazy loading causes only the minimal amount of debug information to be loaded for each module initially, deferring the loading of more complete symbol information until it's needed. Turning it off causes the debugger to immediately load everything it knows about every library loaded into memory. This makes starting the debugger slower, but makes more complete debug information available. See the "Shared Libraries" section for more details.

The Disassembly Style setting controls the output of the Build

FIGURE 18-42

Clearing the In-Editor Debugger Controls will turn off the normal in-editor debugging features in each editor pane. You'll have to use the debugger window for your debugging.

REMOTE DEBUGGING

The gdb debugger supports remote debugging. The debugger runs on one computer, while your application runs on a different computer. What actually happens is that another copy of the debugger is started on the remote computer along with your program, and the two debuggers communicate via a network connection. The remote instance of the debugger transmits all of the pertinent information about your application to the local debugger so you can see what's going on. Commands issued to the local debugger are, similarly, forwarded to the remote debugger for execution.

Remote debugging permits you to test your application in an environment different from that of your development system. A typical requirement is the need to debug your application using an earlier version of the operating system. Xcode, and even the computer you're developing on, may not be compatible with the OS you need to run under. Even if it were, building your application and then rebooting your computer into an older version of the OS to test it is both tedious and unproductive.

Remote debugging is also useful for debugging interactive code. Video games and drag-and-drop handlers can be nearly impossible to debug on a single machine, because the sequence of user events needed to test the problem are interrupted by the debugger itself. The mini-debugger is a great tool, but it still requires user interaction on the same system running the application.

Debugging your application remotely requires some special configuration of both computers. Specifically, you must:

  • Pre-authorize an ssh login account on the remote computer.

  • Create a shared build location accessible by both computers via the same path.

  • Configure the executable for remote debugging.

Remote debugging works through the Secure Shell (ssh) remote login facility built into Mac OS X. ssh provides secure communications paths using a public-key encryption system. The primary reason for using ssh for remote debugger communications is not security — although that's valuable if you need it. Instead, Xcode leverages a powerful feature of ssh called tunneling that lets it communicate with a remote debugger process as if it were running locally. But the side effect of using ssh is that it requires a secure connection to be established first, and that requires authentication. Normally this is done interactively using a password. For debugging, this is awkward. To get around the need for a password, you need pre-authorized access to the remote machine so that the local computer can connect directly to the remote computer without any human intervention.

You create pre-authorized ssh logins by manually generating and exchanging parts of a public/private key pair. (If you are curious, a typical ssh login authenticates a user and then spontaneously generates a temporary public/private key pair for that session. Pre-creating a public/private key pair skips both of these steps.) To create a pre-authorized login on the remote computer, follow these steps (the commands you would enter are in bold):

  1. On the local computer, open a Terminal window and generate an RSA public/private key pair using the ssh-keygen tool:

    local:~ james$ ssh-keygen -b 2048 -t rsa
    
    Generating public/private rsa key pair.
    Enter file in which to save the key (/Users/james/.ssh/id_rsa):
    Enter passphrase (empty for no passphrase):
    Enter same passphrase again:
  2. Press Return when Xcode asks for a filename. This uses the default RSA filename for your account. If Xcode asks to overwrite the file, answer with a y. Enter a passphrase or press Return to leave it blank. (A blank passphrase is less secure, but is still acceptable and more convenient in a low-security environment.) Confirm the passphrase by entering it again. If you are successful, a new private key is written to ~/.ssh/id_rsa and your public key is written to ~/.ssh/id_rsa.pub, as follows:

    Your identification has been saved in /Users/james/.ssh/id_rsa.
    Your public key has been saved in /Users/james/.ssh/id_rsa.pub.
  3. On the remote computer, make sure Remote Login is enabled in the Sharing pane of the System Preferences. This allows ssh connections from other computers.

  4. Log in to the remote computer using the account you plan to debug under. This verifies that the network connection works and that the remote computer is configured to accept ssh logins. In this example, I'm logging in to the computer whiterabbit using a special account I created just for testing. Use the account name and address of your remote computer in place of test and whiterabbit.local.

    local:~ james$ ssh [email protected]
    Password:
    Last login: Wed Sep 21 15:39:42 2005
    Welcome to Darwin!
    whiterabbit:~ test$
  5. You now need to transfer the public key you just generated to the remote computer. One way is to use ssh 's file transfer capability to send the id_rsa.pub file to the remote computer. Open a second Terminal window (you still have more work to do in the remote shell you just connected to, so leave that alone for the moment) and enter the following command:

    local:~ james$ scp ~/.ssh/id_rsa.pub
    [email protected]:development_rsa.pub
    Password:
    id_rsa.pub                              100% 1123   1.1KB/s
    00:00

    Again, supply the password of the remote account and substitute the correct account name and computer address. This command copies the id_rsa.pub file from the local .ssh directory into the development_rsa.pub file in the home folder of the remote computer.

  6. Return to the Terminal window with the ssh shell session on the remote computer. Use the ls command to verify that the development_rsa.pub file was transferred.

  7. You now need to append the public encryption key in the development_rsa.pub file to the list of authorized computers for this account. To do this, use the following commands:

    whiterabbit:~ test$ mkdir ~/.ssh
    whiterabbit:~ test$ cat ~/development_rsa.pub >> ~/.ssh/authorized_keys
    whiterabbit:~ test$ rm ~/development_rsa.pub
    whiterabbit:~ test$ chmod go-rwx ~/.ssh/authorized_keys

    The .ssh directory and authorized_keys file may already exist, in which case you don't want to overwrite them. You just want to append the new key to the existing file. This is a text file, so it can also be edited using nano, vim, or your favorite text editor. The last two commands delete the public key file that was transferred and rescind all non-user access to the authorized_keys file for security purposes.

  8. The remote computer is now pre-authorized to accept secure connections from your current account on the local computer to the account you just configured on the remote computer. Verify this by logging out of the current remote session and connecting again, like this:

    whiterabbit:~ test$ exit
    logout
    Connection to whiterabbit.local closed.
    local:~ james$ ssh [email protected]
    Enter passphrase for key '/Users/james/.ssh/id_rsa':
    Last login: Tue Oct 25 09:49:46 2005 from marchhare.local
    Welcome to Darwin!
    whiterabbit:~ test$

    This time, ssh prompted for the passphrase used to generate the key, not for the password of the test account on the local computer. If successful, you know that ssh used the key for this computer to connect to the test account on the remote computer. If you want to change any of these variables in the future — you want to connect from a different development machine or from a different account or to a different account — you must repeat these steps.

The next step is to create a shared build location accessible to both computers. Both the development and remote computer must have direct access to the entire build folder containing both the final product as well as all intermediate build files. More importantly, the UNIX path to the folder must be identical on both computers. You have three easy ways of accomplishing this.

  • The first, and probably simplest, solution is to employ a third-party file server. Create a build folder on a file server separate from your local or remote computer. (The "Build Locations" section in Chapter 17 discussed different ways of relocating your project's build folder.) You can now mount the build folder on both the local and remote computer using the same path.

  • The second is a hybrid approach. Configure your project to build to a local folder in a common, publicly accessible folder like /Users/Shared/Projects/DistantBugs. Turn on the file sharing services of OS X and connect to it from the local machine. Now create a symbolic link on the remote computer so that the build folder can be reached on both machines using the same path. You must use the command-line tools to create symbolic links. The following example mounts the main volume of a development system (Griffin, in this example) as a network volume on a remote computer, and a symbol link is created in the remote computer's Shared folder that links to the same folder on the development system:

    ln -s /Volumes/Griffin/Users/Shared/Projects /Users/Shared/Projects

    Now, any build folders that are created in the development computer's /Users/Shared/Projects folder will appear at the same location in the remote computer's file system.

  • The third method of getting both computers access to the same build folder would be to simply copy the entire build folder to the remote computer. For a one-shot test, this might be the easiest solution. However, if you were constantly rebuilding the project, this would be both inconvenient and inefficient. You could automate the process by creating a target script phrase to copy the contents of the build folder to the remote computer. If you decide to go this route, consider using a utility like rsync to quickly transfer only the portions of the build folder that change after each build. Remember that the location of the copy must reside at the same location as the original, or at least have an equivalent UNIX path.

The last step in this process is to configure Xcode to start the debugging session remotely. You do this is in the Debugging pane of the executable's Info window, previously shown in Figure 18-41.

Check the Debug Executable Remotely Via SSH option. When you do this, the Standard Input/Output option changes to Pipe. Leave it that way; this choice must be set to Pipe for remote debugging to work.

Start your debugging session as you normally would. The first time you do, Xcode asks for your passphrase to decode the private key you generated earlier, as shown in Figure 18-43. Once it has your private key, it connects to the remote computer and starts the debugging session. After this point, debugging your application remotely isn't significantly different from debugging it locally.

FIGURE 18-43

Figure 18-43. FIGURE 18-43

If anything goes wrong — problems connecting to the remote computer, accessing the product on the remote computer, or starting the debugger — consult the debugger console window for clues. Both the ssh client and the debugger output copious diagnostic messages to the console. Anything that goes wrong should be documented there.

DEBUGGING AIDES

A number of miscellaneous tools and features are scattered around the debugger, Xcode, and the operating system itself that will help you find bugs in your code. The "Custom Executables" section covered loading the debug variant of frameworks and enabling Debugger() and DebugStr() calls. The following sections describe a few more Xcode facilities.

Catching a Throw

The command Run

Catching a Throw

Stopping for Debugger() and DebugStr()

The Run

Stopping for Debugger() and DebugStr()

Guard Malloc

A very useful debugging feature for C programmers is the Guard Malloc library. Choosing the Run

Guard Malloc

It should be noted that Guard Malloc can significantly slow down your application, but the additional execution time is usually worth it, because out-of-bounds memory accesses are particularly difficult to debug.

Debug Variables

Debug variables are either environment variables or preference property values that invoke special behavior in the libraries and frameworks and are useful for debugging.

Note

There is also a wide range of debugging support functions that your application can call directly, but a discussion of those is beyond the scope of this book. The best resource is Apple's Technical Note #2124, Mac OS X Debugging Magic. You can find it in the Xcode documentation.

Environment variables can be set using the Arguments pane of the executable's Info window. Preference values can be set using the defaults command-line tool. You can read the man page on the defaults tool for the details of setting user default values, but here's a simple example:

defaults write com.my.SimpleApp NSShowAllViews YES

The com.my.SimpleApp file is the user preferences file associated with the application. By default, this is the Identifier set in the target's Properties pane. The command sets the NSShowAllViews property to YES, which causes the AppKit framework to draw a colored border around every NSView. Setting property values only works for Carbon and Cocoa applications that use the user defaults framework.

The following table describes a few environment variables useful for C programming:

ENVIRONMENT VARIABLE

DESCRIPTION

MallocScribble

Fills deallocated memory with 0x55s. If your program reads the data again, it should be obvious in the debugger that the data is from a stale block.

MallocGuardEdges

Adds guard pages before and after large allocation blocks. This will catch attempts to access data well beyond the edge of each allocated block. It can be used independently of the Guard Malloc library.

MallocStackLogging

Logs the stack state for each allocated block. There are tools that will read this log and assist you in debugging memory allocation problems, especially memory leaks.

If you think you have a problem with loading dynamic libraries, try some of the environment variables described in the following table:

ENVIRONMENT VARIABLE

DESCRIPTION

DYLD_IMAGE_SUFFIX

Searches for libraries with this suffix first. Use this to load alternate variants of libraries. A lot of the system libraries include debug versions that can be loaded by setting the suffix to _debug.

DYLD_PRINT_LIBRARIES

Logs the names of each library, as it is loaded. If you think you're loading the wrong dynamic library, set this variable to 1.

DYLD_PRINT_LIBRARIES_POST_LAUNCH

Logs the names of loaded libraries, but it only starts logging after your application has started executing. This avoids logging a lot of the core libraries.

DYLD_PREBIND_DEBUG

Logs pre-binding diagnostics information about your application.

If you're debugging memory leak or retain/release problems in Cocoa, consider setting some of the environment variables described in the following table:

ENVIRONMENT VARIABLE

DEFAULT

DESCRIPTION

NSZombieEnabled

NO

If this is set to YES, NSObjects are "zombified" instead of being deallocated. A zombie object has all of its message handlers replaced with a call that will break into the debugger. This will catch an attempt to send a message to an object that has already been released and deallocated.

NSDeallocateZombies

NO

Set this to YES and the memory for zombie objects will actually be released. The NO setting is the safest, but can result in memory leaks that themselves may cause your application to misbehave.

NSHangOnUncaughtException

NO

Normally an uncaught NSException will cause a Cocoa application to terminate. Set this variable to YES and it will simply hang instead, allowing you to break into it using the debugger and examine its state.

NSEnableAutoreleasePool

YES

Setting this value to NO defeats the functionality of auto-release pools. Use this if you want to keep around all of the objects that an auto-release pool would normally release.

NSAutoreleaseFreedObject CheckEnabled

NO

This is a very handy setting for finding double-release bugs. Setting this variable to YES will cause the auto-release pool to log an error message if the pool contains an object that has already been released.

NSAutoreleaseHighWaterMark

0

This is a useful diagnostics tool to check for situations where you are putting an excessive number of objects into an auto-release pool. Set it to a number other than 0 and the system will log a warning whenever more than that number of objects have been added to the pool.

NSAutoreleaseHighWater Resolution

0

Use this setting to log a warning message for every N number of objects above the high-water mark level. The high-water mark level emits a single warning when the number exceeds that value. This setting emits a warning for every increment. Setting the high-water mark to 1000 and the resolution to 50 would log a message when there were 1000, 1050, 1100, 1050, . . . objects in any auto-release pool.

The two preference settings described in the following table are useful when you're debugging an AppKit application that is having drawing or layout problems. You must set preferences in the preference file of the application before you launch it.

PREFERENCE KEY

DESCRIPTION

NSShowAllViews

Define this preference and set it to YES and the AppKit window manager will draw a colored border around each NSView in the application. This makes it very easy to see spacing and layout problems.

NSShowAllDrawing

Define this preference and set it to YES and AppKit will draw a colored rectangle before drawing each NSView. This makes it very easy to see what components in your windows are drawing, when, and in what order. Also check out the various Quartz Debug features.

This is just the tip of the iceberg. There are literally hundreds of variables, system calls, and development tools to help you track down and debug your application. Most are collected and maintained in Apple Technical Note #2124, Mac OS X Debugging Magic, which you can find in the Xcode documentation or read online at http://developer.apple.com/mac/library/technotes/tn2004/tn2124.html.

SUMMARY

You should now have at your disposal a cornucopia of tools and techniques for isolating, trapping, examining, and correcting any aberrant behavior in your application. You are likely to spend a lot of time using the debugger. Knowing how to harness the debugger's capabilities can save you hours of time.

Getting your application to build and execute correctly may be all you intended to accomplish. For some applications, even this isn't the end of the development process. You may not be satisfied that your application merely runs. You want it to run fast — and that is the subject of the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset