Hour 19. Getting the Bugs Out


What You’ll Learn in This Hour:

How to use the debugger to find out why your program crashed

How to use the debugger to find out how your program is working

How to manipulate your running program to test ideas and potential fixes for problems


In the preceding hour, you learned about using unit tests, which are a great tool for validating that everything is still working right when it’s working right. However, they are only so helpful when something is working wrong. When your code is not behaving as planned, you need to be able to dig into it and check the values that variables are holding and verify that execution is following the path through your code that you were planning.

So far in this book, we have been using NSLog() to write debugging output to the console to keep an eye on these internal behaviors of applications. Variations on this theme, adding output statements to code so that it can self-document the process of its execution and the states of its variables as it goes, are the universal route by which everybody comes to debugging. It’s easy, it’s intuitive, and it’s a huge time-eating productivity killer.

Completely ignoring the fact that NSLog() is intended for logging purposes and not for debugging, and that you have almost certainly looked at your console logs and cursed other programmers whose products blather reams of useless (to you) debugging output into the log where you’re trying to find a critical system fault, using log output for debugging output is still a poor idea. Each NSLog() call can output only a predetermined collection of data from a single point in your program, and it’s almost never the case that you get exactly the right output, from exactly the right location, on the first try. And then there’s the annoyance of having to clean the useless NSLog()s back out of the code again once you’re confident that a routine is working properly.

Thankfully, there is a better way: using a proper debugger.


By the Way

Make sure to remove no-longer-necessary NSLog() statements before distributing your application. This output lands in the system log on your users’ machines, and filling their logs with unnecessary debugging is impolite at best and a route to poor feedback in the App Store and other public venues if users actually start to notice.


Debuggers are a programmer’s best friend. When you’re running your program through a debugger, you can pause your program anywhere, examine the contents of all the variables, and step through your code one line at a time to observe how values are being calculated and stored.

The only downside of debuggers is that they look scary to use. This appearance—and it’s only an appearance; after all, using a debugger under Xcode is, if anything, easier than using NSLog()—prevents many programmers from even trying to use debuggers. Don’t be one of those programmers. If you can write code, you can use a debugger, and if you can use a debugger, writing code gets easier.


By the Way

If you are sitting there reading this and thinking, “Ah, well, the debugger might not be that bad, but I’m getting on okay with NSLog(). I’ll worry about the debugger later.” Reconsider. If you look at the debugger and think “that’s a little intimidating,” don’t panic. Really, the debugger is “hard,” the same way “computers are hard.” Everyone thinks that when starting out, so you’re in perfectly good company. Everyone quickly discovers that those concerns were misplaced. You will, too, and be a better programmer in the end with the debugger.


Getting Started with the Debugger

You have almost certainly been using the debugger already even if, perhaps, you weren’t aware of it. Unless you fiddled with things earlier and have only been building for release when following instructions in this book so far, every time you have run your projects, you have been running them in the debugger. So, no need to be shy; you’ve been introduced.

Load up iBLine (use the version from the Hour 19 folder in the source available from http://teachyourselfxcode.com/—I’ve added one more feature to it to support the exercises in this hour) and run it by clicking the Run button on the toolbar. Check the output section in the Debug area. Yours should look like what’s shown in Figure 19.1. Notice our NSLog() lines down at the bottom and how it says GNU gdb... at the top? gdb is the debugger. You’re already using it. See, that was easy.

Image

Figure 19.1. The Debug area shows NSLog() output lines with gdb as the debugger.

Associating Debuggers with Targets

If the output section in the Debug area doesn’t inform you that you’re using gdb or lldb, the build action that’s currently configured for your target doesn’t have a debugger associated with it. To fix this, choose Edit Scheme from the Product menu. In the scheme editing dialog, display the Info tab for the Run action and configure the Run action for debugging and select a debugger. As of Xcode 4.2 and iOS 5, you must select the gdb debugger for iOS. You can select either gdb or lldb for OS X applications. Your settings should be similar to those shown in Figure 19.2.

Image

Figure 19.2. Selecting GDB from the debugger settings.


Did You Know?

The current external functionalities of gdb and lldb are very close. Their internal functionalities, and what will probably be possible in the future, are rather different.

gdb is the GNU debugger, from the Free Software Foundation. It’s very powerful, well supported, and has stood behind the creation of pretty much every piece of complex software in the OS X and UNIX world for the past several decades. Unfortunately, as a GNU General Public License application, some severe limitations apply as to what Apple can do with it. To alleviate the difficulties caused by these limitations, Apple has recently been developing their own debugger, lldb. lldb will be much more tightly integrated into Xcode, and will eventually provide enhanced functionality that’s not possible with gdb.

Unfortunately, they’re not there yet. Apple recommends using lldb for OS X applications, but lldb is not yet available for use with iOS architectures. Rather than including support for the enhanced lldb features in the limited cases where it’s applicable, Apple has currently only exposed the lldb functions that are common with gdb. Therefore, at this point, as long as the debugger is supported on the architecture for your target, it makes little difference which you select. In the future, as lldb matures and Apple exposes more of its functionality in Xcode, it will certainly become the debugger of choice, and in all likelihood, support for gdb will be dropped entirely.


Putting the Debugger to Work

Of course, while you’re now over that hurdle of starting to use the debugger, it hasn’t done anything much for you yet. This is because you haven’t asked it to. The debugger’s job is to watch your application for you, grab its collar if it messes up, and let you poke around in the running application internals to figure out if and why the application is misbehaving. If your application hasn’t misbehaved, and you haven’t asked the debugger to intervene, the debugger sits there in the background minding its own business.

The easiest way to get the debugger to step out of the background is to cause your application to do something untoward (for example, to try to use memory that doesn’t belong to it). As a matter of fact, as you have been working through examples and developing your Xcode skills, you have probably even seen it do this but didn’t realized what was happening.

I’ve added some code to iBLine so that you can intentionally run the program off the rails and see what gdb does about it. To herd iBLine into generating an error, follow these steps:

1. Make sure you’re using the project version from the Hour 19 folder in the source available from http://teachyourselfxcode/.

2. Make sure your scheme is set up to build a debug version for the Run action and that the gdb debugger is configured for the Run action.

3. Run iBLine from the toolbar.

4. Add three points, and then click Done to make sure that they’re plotted.

5. Click the Info button (the italic i) to go back to adding more points.

6. Before you add a new point, click-drag across the second point listing in the UITableView. It should display a standard iPhone-style Delete option, as shown in Figure 19.3.

Image

Figure 19.3. The UITableView about to send a delete message for an entry.

7. Click Delete. Click Done to return to the graph to verify that the middle point has actually been removed.

8. Click the Info button again. Add another point and check the graph to verify that the new point has been appended.

9. Return to the FlipsideViewController and click-drag-swipe across each of the points listed in the UITableView, deleting each of them.

10. Once the UITableView is empty, enter another pair of values into the X and Y coordinate boxes and click the Add button again.

Whoa. Something’s wrong. The iPhone application didn’t respond as expected, and the Xcode interface jumped back to the front. There’s some unfamiliar content in the Navigator, and a green bar is highlighting a line of code in BetterList.m in the Editor area. It probably looks like what’s shown in Figure 19.4.

You have probably seen this before. That green bar, and the cryptic comment “Program received signal EXC_BAD_ACCESS.” When you have hit this previously, you knew something was wrong and probably went in and added some NSLog() statements to try to figure out where the application was going sideways. Without knowing it, here, too, you have been using the debugger. It’s the debugger that highlighted that line of code for you, and although EXC_BAD_ACCESS might seem cryptic, the debugger is trying to tell you something useful about what went wrong.

Image

Figure 19.4. The debugger highlighted the line where the application received the EXC_BAD_ACCESS signal.


By the Way

Documenting every response and return code from gdb and lldb would require an entire book (or two). EXC_BAD_ACCESS is one you’ll see often, and it means that your program tried to access a location in memory and for some reason it could not. Usually that reason is because the memory it is trying to access does not belong to it.

To make the fullest possible use of the debugger, we strongly recommend that you buy a book on gdb, or if Apple has gotten lldb completely integrated by the time you read this, a book on lldb. You don’t need to read and learn the material, but a reference book to consult when the debugger says something cryptic will prove mighty handy.


EXC_BAD_ACCESS means that your program failed (at the command line this would be a segmentation fault), because the operating system caught it trying to access memory that it wasn’t allowed to access. This can happen for a number of reasons, including logic errors where some variable has been corrupted and points off into the wilderness. However, the most common cause is attempting to do something with a nil (or Nil, or NULL) pointer. nil, Nil, and NULL pointers all point to memory location zero, and nothing should ever be living at memory location zero, so your program shouldn’t be trying to either read or write to it.

Useful information from the debugger, indeed, but don’t go writing NSLog() statements into the surrounding code to try to catch that zero pointer just yet. Along with telling you where the problem happened, gdb also tells you about all the variables in the current scope. Yes, you get that for free, too. Look on the left side of the Debug area shown in Figure 19.4. Notice the list of items there? self, _cmd, anitem, newNode? This area is the Variables View. Look more closely at the routine where that EXC_BAD_ACCESS fault occurred. Yes, the appropriately named Variables View is listing the variables in the routine where execution is currently paused.

Try expanding the listing for newNode and for self. The list should now look similar to that shown in Figure 19.5. Notice that the head, tail, and current variables for self, all DLNode pointers, are pointing to location 0x0? You have just found a bug that’s been hanging around in BetterList since we first wrote it. If you call the remove method when there’s a single item in the list, the item gets deleted but the BetterList instance hangs around, pointing to a nil current item and to nil head and tail items. The append method, however, assumes that its instance has at least one node in it and dutifully tries to add the new list item that it creates, after the previous list tail. How long would it have taken you to find that bug using NSLog() statements? Unless you have seen that bug coming since we implemented BetterList back in Hour 14, “Planning for Reuse: Frameworks and Libraries,” I bet the answer is “a lot longer than it took to click the reveal triangle by the self variable.”

Image

Figure 19.5. After expanding the listing for newNode and self, we discover that all the DLNode pointers point to 0x0.

Proactive Debugging

Of course, waiting until your program goes off the tracks and only looking at the debugger when it does isn’t the most efficient way of programming either. Pleasantly, debuggers are just as adept at letting you browse around the innards of an otherwise healthy program. This lets you verify how your program is running and check that everything is going to plan. Before we get started, let’s take a step back and become familiar with the debugging interface as presented in Xcode. Figure 19.6 labels the important areas and controls.

Image

Figure 19.6. The debugging interface in Xcode.

• In the Debug Navigator, you’ll find a list of threads. Until you’re up to programming threads, most of this information will be irrelevant to you. However, under the primary thread, you can find the call stack for your application. This lists the methods currently running. Examining the call stack can give you insight into how your program arrived at the routine its in and whether there are peculiarities, such as your method being invoked from somewhere unexpected. If you look closely at the Debug Navigator shown in Figure 19.6, you can see that the top (most recent) method invoked is the BetterList append: method. This method was messaged from the FlipsideViewController plotPoint method, which was messaged from something in the NSObject class. Twenty-one methods further up the stack, the grand-daddy C main routine is still running. If you want more detail about the methods between the originating main and the location where your program is currently stopped, you can adjust the available detail in the Debug Navigator by sliding the detail slider at the bottom of the Debug Navigator area.

• A Breakpoint Navigator can also be displayed in the Navigator area. The Breakpoint Navigator lists all the breakpoints you currently have set for the target and lets you toggle them on and off as needed. You learn more about breakpoints and how to use them in the next section.

• In the main Editor area, the debugger shows the region of the code where your program is currently paused, with the current line highlighted. If you want to have line numbers displayed in the gutter as shown in Figure 19.6, you can enable them by opening the Xcode preferences from the Xcode menu and checking the Show Line Numbers check box in the Text Editing tab. If you have breakpoints set in the region, they show up as blue flags in the gutter. Line 154 has an active breakpoint, and line 159 has an inactive breakpoint in Figure 19.6. You learn about breakpoints and how to use them in the next section.

If you hover over variables in the code shown in the main Editor area, Xcode displays pop-up datatips. Figure 19.7 shows a typical example. Here, we’re paused in BetterList’s append method, just after the newNode has been allocated, its item assigned, and its previous and next pointers filled in. Because all the members of the DLNode structure are pointers, the values you’re seeing here are hexadecimal memory locations. If you were to hover over a simple variable, or a structure or object with simple variable components, you would see their integer, float, and so on values displayed in the datatip.

Image

Figure 19.7. Hovering over variables in the main Editor area causes pop-up datatips to appear.


By the Way

Having line numbers displayed in the gutter makes it a lot easier to figure out which breakpoints in the Breakpoint Navigator go with which lines in code displayed in the Editor area.


• At the bottom-left of the main Editor area is a small panel of Step buttons that give you control over what the debugger does next. Your options are as follows:

Continue: The Continue button instructs the debugger to release its control over the application and let the application get on with business. Of course, if the application just crashed because of an EXC_BAD_ACCESS fault, it’s not going to accomplish anything else even if you ask it to continue.

Step Over: The Step Over button has a slightly confusing name. In most cases, it really means “do the next step” in the program. If the next step is a simple statement, that statement is executed, and the debugger pauses again. If the next step contains a method invocation, the entire method invocation is executed as “a step,” and the debugger pauses after the method returns.

Step Into: This button behaves identically to Step Over for simple statements, but instead of stepping over method and procedure invocations, Step Into enters the called method and pauses on the first statement in it.

Step Out: This button does not really mean step, as the Step Over and Step Into buttons do. Instead, it means “finish up everything in the current method, and then pause again once control returns to whatever routine called this one.” This behavior is not contextually dependent on whether the next step is a method invocation or a simple statement.


Did You Know?

Beside these, there’s an icon to set up a simulated location. This lets you test location-aware code, but doesn’t have anything to do with the debugger. We’re not sure why that functionality is wedged in there.


• At the bottom-right of the main Editor area is a jump bar where you can browse through the calling stack that has gotten the program to this point and examine variables and memory content in any frame of the stack, in addition to the immediate line where your program is paused.

• The Console log output section with which you’re already familiar is beneath the main Editor area on the right.

• The Variables View, which you were briefly introduced to earlier, is beneath the main Editor area on the left. This view shows current variables and their values. It has a search window so that you can limit the variables to only those most relevant, and it has options to restrict itself to only local scope variables, all variables including global and static variables accessible from the current scope, and an Auto setting that tries to make intelligent guesses about what variables are most relevant.

Compound variables (structures and objects) are hierarchically expandable in the Variables View, to let you browse child variables or to collapse them and reduce clutter.

Each top-level variable shown includes a small icon to indicate its scope: A for argument variables, passed in to the current scope; L for local variables defined in the current scope; S for static variables; and G for global variables.


Did You Know?

Unfortunately, as of Xcode 4.2, global variable display is broken when using gdb. If you can switch your project to use lldb, the global variables show up properly. If you can’t, keep your fingers crossed that Apple fixes this bug. Global variable display worked just fine in earlier versions of Xcode, so the difficulty here is not an inherent limitation of gdb.


Working with Breakpoints

Now that you’re familiar with where to find all the tools to use the debugger, it’s time to start actively using the features. Every debugger’s big hammer is its ability to set breakpoints, and gdb and lldb are no different. You set breakpoints to tell the debugger where you want it to pause the program, and then you use the other tools at your disposal to study the state of the application while it is paused.

You can set as many breakpoints in your code as you like, anywhere that you need one. Every time that your running program tries to execute any of the statements marked with a breakpoint, the debugger interrupts it and brings control back to you and Xcode, where you can examine the state of the program, and even edit some aspects of the running program on-the-fly.

Setting Breakpoints

To set a breakpoint, simply find the location in your program where you want to interrupt the program and click in the gutter. A blue flag icon will appear, and the next time you run your program, it pauses at that statement. To experiment with this, set a breakpoint for the remove method, in BetterList. Follow these steps to set it:

1. Navigate to the BetterList.m file included in the BetterList subproject in your iBLine project. Display BetterList.m in the main Editor area.

2. Click in the gutter beside the line that defines the (void) remove instance method. A blue flag will appear; it should be on line 168 if you have line numbers turned on.

3. Now run iBLine in the iPhone simulator by selecting the iBLine iPhone simulator scheme and clicking the Run button on the Xcode toolbar.

4. Add two points, and then swipe to delete the first. Click Delete.

5. The iPhone simulator should pause, and Xcode should immediately pop to the foreground, with the entry point for the remove method highlighted, as shown in Figure 19.8. The highlighted line is line 170, the creation of the tempNode, because this is the first executable statement after the method entry point.

Image

Figure 19.8. Xcode has appeared in the foreground with line 168, the entry point for the remove method, highlighted, as well as line 170, the first executable statement after the method entry point.


Did You Know?

The line hasn’t executed yet. You can tell this by looking at the Variables View, where the tempNode currently actually has a nonzero value. This value is random bits lying about in the memory where the tempNode variable lives. It’s not zero because C languages know that initialization costs time and assume that you’ll deal with initialization if and when you need to.


Maneuvering a Paused Application

iBLine is now paused at the first step of the remove method, letting you examine the variables and the calling stack, but you’re not just limited to sitting here or jumping to the next breakpoint. You can also continue your program’s execution in baby steps and follow its progress in the debugger. The details of the baby steps your program will take are controlled by the Step buttons at the bottom of the main Editor area (labeled in Figure 19.6) or by tiny icons that replicate those on the stepping buttons and that appear next to the gutter as you cursor-over lines of code.

The simplest stepping action is simply to Continue Program Execution, which causes the program to run either until it hits another breakpoint or to completion, or a crash, whichever comes first. This is probably the most common action to take after a program has paused at a breakpoint.

The next stepping action is the Step Over action. This is probably the next most common action, and clicking it will proceed through the current method, one line at a time, remaining at the level of the method where the breakpoint occurred until it reaches the end of the method (or the return call). After hitting the end of the method, it steps up into the parent calling method and proceeds in a similar fashion. At no point does it step down into methods that are called.

The third option is the Step Into action. This action performs the same task as the Step Over action, except it always steps down into methods that are called, instead of executing them completely in a single step.

The fourth option is the Step Out action. This action completes all the remaining steps in the current method and pauses the program again after control returns to the method that invoked the one containing the breakpoint.

Continuing Program Execution

You can now start exploring the program control functionality. From where we paused earlier (at the end of step 5 in the preceding section), proceed as follows:

1. Click the Continue button. Control should return to the iPhone simulator. The second point you created should still remain in iBLine’s UITableView.

2. Click the Project Navigator in the Navigator area so that you can see the files for your project. Select the FlipsideViewController implementation file.

3. Scroll down to the tableView method that is listening for an editingStyle message (it should be line 173) and set a breakpoint for the method. You should now have a breakpoint set for the remove method for BetterList and for the tableView dataSource method that handles deleting cells from the UITableView.

4. Back in the iPhone simulator, add another point, and then delete the first one on the list again.

The iPhone simulator pauses, and Xcode comes to the front with the debugger again. This time it should be paused in the FlipsideViewController method where we just set the breakpoint. Your display should look something like what is shown in Figure 19.9. Remember that the line that the debugger is paused on hasn’t executed yet.

Image

Figure 19.9. Xcode has appeared in the foreground again, paused in the FlipsideViewController method, where we set our breakpoint.

Stepping Forward One Line

Sometimes you really want to know about what the program is doing at other lines near your breakpoint, and setting individual breakpoints for all of them seems silly. The Step Over action is here to solve that problem. From where the debugger is paused in step 4 in the preceding section, do the following to single step through your program:

1. Pay attention to the line where Xcode is currently paused and to the contents of the Variables View.

2. Click the Step Over icon at the bottom of the main Editor area. The current line updates, stepping into the conditional statement and pausing on [thePoints removeObjectAtIndex:indexPath.row].

3. Practice stepping forward a few more times. Note that each time you do you move one statement further in the execution of the current method, whether that statement is a simple variable assignment or a complex method invocation. When you try to step over [myPointsList remove], however, you’ll find that you’re interrupted down in the remove method for BetterList. This is because you have left a breakpoint for the remove method. Go ahead and Continue Program Execution after you hit the breakpoint at remove, and then add another point or two in iBLine, and delete another one to get back to the breakpoint in the FlipsideViewController.

Stepping Forward Several Lines

If you want to continue for several lines, but not uncontrollably to the next breakpoint, you can set a one-time Continue to Here breakpoint. From where Xcode paused in step 3 in the preceding section, we are probably more interested in the activity around [myPointsList remove] than at the top of the function. To jump directly there without having to click Step Over several times, just do this:

1. Right-click the line containing [myPointsList gotoItemNum:indexPath.row].

2. In the pop-up menu that appears, select Continue to Here, as shown in Figure 19.10. Xcode restarts iBLine and leaves it running until it is ready to execute the line you just indicated, where it pauses again.

Image

Figure 19.10. To continue for several lines, but not as far as the next breakpoint, set a Continue to Here breakpoint.

Using the Step Into Action

So far, we’ve been using the step actions to step over method invocations without going down into them to see how they’re working. We can get into the remove method because we’ve set a breakpoint there. But what if we want to check the functionality of a method where we haven’t yet set a breakpoint? This is what the Step Into action is for.

When execution is currently paused on a statement that when executed will result in a method or procedure invocation, the Step Into action transfers control into that method (or procedure) and pauses execution again at its first executable statement. If we want to know what goes on in the gotoItemNum method of myPointsList, we can Step Into this method, as follows:

1. Make sure the current line highlight indicates that iBLine is paused at the [myPointsList gotoItemNum:indexPath.row] statement.

2. Click the Step Into action icon at the bottom of the main Editor area.

Control transfer to the BetterList gotoItemNum method, where you can examine variables and again step, continue, or otherwise maneuver in the paused application.

Using the Step Out Of Action

When you have finished exploring in the gotoItemNum method, returning to the FlipsideViewController method that called gotoItemNum is easy. Just click the Step Out Of button, and any remaining instructions in gotoItemNum are executed, control return to the FlipsideViewController, and execution pauses again, on the line immediately following the call to gotoItemNum.

One Dog, Many Tricks

You should already see that the capabilities of the debugger are a good replacement for the variable-checking uses for which we’ve previously abused NSLog(). However, the debugger is not limited just to stopping at every breakpoint it encounters and reporting variable values. In addition to this already-quite-useful functionality, the debugger can make “intelligent” choices about pausing at breakpoints, perform automated actions when it encounters specific conditions at a breakpoint, and it can also edit live variables in a running application. Put together, these move the debugger far beyond a tool for simply reporting or exploring the state of an application and convert it into a tool that, in collaboration with unit tests, can be used proactively to watch for and report errors.

Conditional breakpoints, or watchpoints, are breakpoints that have been configured to only pause program execution when certain conditions are met. Causing iBLine to fault, due to trying to add a new item after a nil BetterList tail node is reasonably easy because we just have to add a point and then delete it and try to add another one. But what if the error only happens if we have a dozen (or a hundred) points allocated? Hitting the breakpoint in remove and continuing every time just to get to see what happens when the list is on its last item seems painful. It would be much easier if the breakpoint for remove activates only if the current node is the only node.

Conditional breakpoints can do that. To configure the breakpoint for remove so that it only activates when it is about to delete the only node in a list, follow these steps:

1. Navigate to the breakpoint for remove and right-click it. You can do this using the Breakpoint Navigator in the Navigator area.

2. From the pop-up menu, choose Edit Breakpoint.

A pop-up dialog appears where you can set a condition for the breakpoint, configure how many times to ignore it, add an action that should be carried out when the breakpoint is encountered, and configure the breakpoint to automatically continue after the action if that serves your purposes.

3. In the Condition field, enter the text head == tail. If the head and the tail are the same item when you are entering this method, there must be only one node in the list. Your configuration should look like Figure 19.11. Click Done.

Image

Figure 19.11. Setting a conditional breakpoint for when the head and the tail are identical.

4. Go ahead and run iBLine, add three or four points, and then start deleting them.

5. When the debugger pauses at the breakpoint in the FlipsideViewController, disable that breakpoint by left-clicking it once. It turns a dim bluish-gray to indicate that that it is currently disabled. Click Continue Program Execution to get things moving again.

6. Continue to delete points until you get to the last item in the list. Xcode should let you delete all of them except that last one without pausing in the debugger any more.

7. Try to delete the last point. The iOS simulator pauses, and Xcode comes to the front, paused at the BetterList remove method. If you examine the variables in the Variables View, you’ll see that head and tail do indeed point to the same location (and not coincidentally, current does, as well).

Instead of configuring a condition based on program variables, you could have configured the conditional breakpoint to become active only after the code had passed through the routine some number of times, or configured automatically executed actions to be conducted when the breakpoint was reached. Consult your debugger reference to learn about some of the actions that you can connect to a breakpoint, but be aware that you can also attach execution of shell scripts, AppleScripts, and other actions of essentially arbitrary complexity. A sufficiently demented application of this capability could have a running program conditionally detecting an adverse condition at a breakpoint and using an action to externally reconfigure program parameter files in an attempt to alleviate the problem. With a sufficient dose of determination, there are very few questions regarding program state that you can’t answer with breakpoints and actions.

The other jaw-droppingly useful capability of good debuggers is the ability to modify program memory contents (its variables) on-the-fly while it is running. As a dramatic example of the power of this, let’s try an experimental fix for our problem with the BetterList remove method.

Thinking through the problem, it is clear that the remove method itself doesn’t fail. It successfully removes the final item from the list. The thing that fails is the attempt to add a new thing to a now-empty list. We might be able to fix this by adding a new method to BetterList to repopulate a list that has lost its last item, but maybe a better solution is just to pitch lists that have become empty into the trash and instantiate new ones when necessary.

Looking at the plotPoint code in the FlipsideViewController, you’ll find that it issues the append (where we’ve been having trouble) if the pointsList is non-nil, but in cases where it is nil, it invokes the DataPhile setupPointsList method to create a new list. This sounds promising.

To test whether it is worth the effort of adding code to BetterList so that it zaps itself more completely out of existence when the list becomes empty, we can use the debugger to simulate this condition. Follow these steps:

1. Make sure your breakpoint for remove is still in place and still configured as a conditional breakpoint that will pause only when the head and tail are identical.

2. Run iBLine and add a few points to verify that everything is working properly.

3. Delete the points. The breakpoint at remove should not activate until you delete the last point in iBLine.

4. Use the Step Out action. This should bring you to the code for the FlipsideViewController on the line immediately following the message [myPointsList remove].

5. In the Variables View, reveal the variables under the self compound variable group.

6. Find the entry for myPointsList. It contains a BetterList pointer and a valid memory value, as shown in Figure 19.12. This is our culprit. If the BetterList remove method were set up to annihilate the BetterList instance when the list became empty, this variable would be nil, and there’s a good bet that a subsequent add would properly reinitialize a new BetterList instance and everything would continue to work.

Image

Figure 19.12. Our problem is that myPointsList has a BetterList pointer and a valid memory value. The BetterList remove method should annihilate the BetterList instance when the list is empty.

7. Variable editing to the rescue! To find out what happens if BetterList zeros this pointer, double-click the value shown beside myPointsList, and enter 0x0 as a new value, as shown in Figure 19.13 and then press Return.

Image

Figure 19.13. Editing the pointer’s value to 0x0 (hexadecimal zero, nil) to see what happens if BetterList zeros the pointer.


Watch Out!: And Another Bug Bites the Programmer

In all likelihood, when you press Return to submit your edited value for the myPointsList pointer, several variables previously accessible in the Variables View vanish. This shouldn’t happen, but at the moment, the only way to get them back appears to be quitting Xcode and restarting it.

If you find yourself in this situation, you can still access and edit the contents of variables by right-clicking them to bring up their datatip and then editing the value presented in the datatip line.


8. Click Continue Program Execution and try adding a new point to iBLine. Voilà. No more error, the point adds, and iBLine runs along quite happily, as though nothing is wrong.

With that little bit of experimentation, and no code written at all, you have just verified that you can you can fix BetterList and repair the crash by making sure that the entire list instance is torn down when the last list item is deleted and making sure that the DataPhile keeps itself updated with respect to the actual state of the underlying BetterList instance.

Summary

In this hour, you learned about using a debugger under Xcode. Because the functionality currently available for lldb and gdb are identical in Xcode (minus the unintentional differences due to bugs), what you have learned applies to either Apple’s preferred lldb for OS X targets or the legacy gdb for iOS targets. It might still take you a few days practice of setting a breakpoint and using the Variables View to completely replace NSLog() output in your programming habits, but after you have made that transition you’ll find that the debugger becomes an incredibly powerful tool for both verifying your program’s functionality as you are building it and for identifying problems when something goes wrong.

Q&A

Q. Is there a way to cause the debugger to output specific variables when it hits a breakpoint, rather than all of them in the scope?

A. You can limit what’s shown in the Variables View, using its search field, but this capability is not very flexible. If you require more flexibility, you can configure an action for a breakpoint and set the action type to Debugger Command. Using the p command (or one of several other output-control variants, see your debugger reference), you can tell the debugger to print the value for a specific variable or variables. The output from these commands appear in the console output area.

Q. Can the debugger be used to debug unit tests as well as the main program?

A. Yes. The behavior is a little bit flaky at the moment, especially if you’re trying to do this with gdb. When you are using gdb, Xcode doesn’t know quite as much about what the debugger is doing internally and seems to sometimes get a bit confused about exactly where it’s pausing and how it got there. This will almost certainly improve when Apple finishes the transition to lldb.

Q. Sometimes when I step through program execution, I can step over a perfectly explicit statement like x=x+5, and watching the Variables View, nothing happens to the value of x. What’s up?

A. You mostly likely have optimization enabled, in addition to the debugger. The optimizer rewrites portions of your code so that it runs more efficiently, and there is not always a line-per-line match between the code you wrote and what the optimizer produces. As a result, when you are debugging an application that’s used the optimizer, the “current line” in your code doesn’t necessarily correspond to exactly what the program is doing at any given point.

Q. What does that big Breakpoints button up on the toolbar do?

A. It enables or disables all breakpoints in the code (without changing their individually enabled or disabled status).

Q. What happens if you step into a system framework method?

A. That depends on whether the source for the method is available, but in general, the debugger does exactly what you asked. If the source isn’t available, you end up in an assembly language listing derived from the library object file, but you are paused at the first executable statement of the framework method.

Workshop

Quiz

1. How can you make the debugger skip over a breakpoint when it’s hit for boring setup purposes in your application and only begin pausing once the breakpoint is being encountered for interesting user interaction?

2. What happens if you try to “Continue to Here” but the program runs across code containing another breakpoint along the route to “here”?

3. Can you Continue Program Execution after stopping for an EXC_BAD_ACCESS fault?

Answers

1. Edit the breakpoint to make it conditional. If you’re always doing the same setup, you will probably hit the breakpoint a fixed number of times before interesting things start to happen. In this case, you can simply set the breakpoint to ignore that number of passes before beginning to activate.

2. Execution stops at the intervening breakpoint anyway.

3. That’s a trick question. Most literally, yes, although your program will still be suffering from the same memory fault and will immediately fall on its face again. If you’re clever and can alleviate the fault by editing a variable value, however, it is possible to rescue a program from this condition.

Activities

1. Set a breakpoint in one of the anonymous blocks used by iBLine to pass functionality to BetterList’s walklist method. Trace execution through the walklist method as the block is called.

2. Replace the annoying NSLog() statements in iBLine that are just producing status messages to confirm that execution has reached specific routines with breakpoints that have logging actions attached and Automatically Continue selected. Observe that you can silence all that annoying logging by using the global button on the toolbar to turn off all the breakpoints.

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

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