Chapter 19: Debugging

The most difficult part in writing software is debugging. Debugging is hard. It’s even harder when you’re writing software in a low-level language (compared to high-level languages like Java/C#). Instead of a stack trace, you’ll often hear iOS developers using buzzwords like dSYM files, symbolication, crash dumps.

This chapter introduces you to LLDB, Apple’s Lower Level DeBugger. I’ll explain some commonly used terms like dSYM, symbolication, and others that are traditionally different from other programming languages. A lot changed when Apple replaced GDB with LLDB, and nearly everything that I talk about in this chapter is specific to LLDB. If you’re still using GDB, it’s high time to change to the newer LLDB. I’ll show you some of Xcode’s features that will help you with debugging and that will unleash the power of the LLDB console. Later in this chapter, you’ll find different techniques for collecting crash reports, including a couple of third-party services.

LLDB

LLDB is a next-generation high-performance debugger built using reusable components from LLVM, including the complete LLVM compiler, that includes LLVM’s Clang expression parser and the disassembler. What this means to you, the end user/developer, is that LLDB understands the same syntax that your compiler understands, including Objective-C literals and Objective-C’s dot notation for properties. A debugger with compiler-level accuracy means that any new feature added to LLVM will automatically be available to LLDB.

Xcode’s previous debugger, GDB, didn’t really “understand” Objective-C. As such, something as simple as po self.view.frame was more complicated to type. You need to type po [[self view] frame]. When the compiler was replaced, there was a need to improve the debugger. Because GDB was monolithic, there was no workaround; it had to be rewritten from scratch. LLDB is modular, and providing the debugger with API support and a scripting interface was one of the design goals. In fact, the LLDB command-line debugger links to the LLDB library through this API. In the “Scripting Your Debugger” section later in this chapter I show you how to script LLDB to make your debugging session easier.

Debugging with LLDB

Debugging with LLDB offers little advantage over GDB for most developers. In most cases, you won’t even notice the difference, except for a few obvious changes like support for Objective-C properties and literals. However, knowing how LLDB works internally and the subtle differences it brings along will make you a better developer and one who can indeed push the limits. After all, you picked up this book to learn about the advanced concepts that would help you push the limits, right? The next few sections aim at explaining these concepts in detail. With that, it’s time to get started.

Debug Information File (dSYM file)

The debug information file (dSYM) stores debugging information about the target. What does it contain? Why do you need a debug information file in the first place? Every programming language you’ve ever written code for has a compiler that converts your code either to some kind of intermediate language that is understandable by the runtime or to machine code that gets executed natively on the machine’s architecture.

A debugger is commonly integrated within your development environment. The development environment often allows you to place breakpoints that stop the app from running and allows you to inspect the values of variables in your code. That is, a debugger effectively freezes the app in real time and allows you to inspect variables and registers. There are at least two important types of debuggers: symbolic debuggers and machine language debuggers. A machine language debugger shows the disassembly when a breakpoint is hit and lets you watch the values of registers and memory addresses. Assembly code programmers generally use this kind of debugger. A symbolic debugger shows the symbol/variable you use in your application when you’re debugging through the code. Unlike a machine language debugger, a symbolic debugger allows you to watch symbols in your code instead of registers and memory addresses.

For a symbolic debugger to work, there should be a link or a mapping between the compiled code and the source code you wrote. This is precisely what a debug information file contains. Some languages, such as Java, for example, inject debug information within the byte code. Microsoft Visual Studio, on the other hand, has multiple formats including a standalone PDB file.

Languages such as PHP, HTML, or Python are different. They usually don’t have a compiler, and to some extent, they’re not classified as programming languages. PHP and Python are technically scripting languages, whereas HTML is a markup language.

Debuggers use this debug information file to map the compiled code, either intermediate code or the machine code, back to your source code. Think of a debug information file as a map you would refer to if you were to visit an unknown city as a tourist. The debugger is able to stop at the correct location according to the breakpoints you place in your source code by referring to the debug information file.

Xcode’s debug information file is called as a dSYM file (because the file’s extension is .dSYM).

A dSYM file is technically a package containing a file with your target name in DWARF scheme.

When you create a new project, the default setting is to create a debug file automatically. The Build Options, which is under the Build Settings tab, of your project file (see Figure 19-1) shows this setting.

9781118449974-fg1901.eps

Figure 19-1 The Debug Information Format setting in your target settings.

The dSYM file is automatically created every time you build the project. You can also create a dSYM file using the command-line utility dsymutil.

Symbolication

Compilers, including the LLVM compiler, convert your source code to assembly code. All assembly codes have a base address, and the variables you define, the stack you use, and the heap you use are all dependent on this base address. This base address could change every time the application runs, especially on iOS 4.3 and above—operating systems that introduced Address Space Layout Randomization. Symbolication is the process of replacing this base address with method names and variable names (collectively known as symbols). The base address is the entry point to your application, normally your “main” method unless you are writing a static library. You symbolicate other symbols by calculating their offset from the base address and mapping them to the dSYM file. Don’t worry; this symbolication happens (almost transparently) when you’re debugging your app in Xcode or when you profile it using Instruments.

Instruments also needs the debug information file to symbolicate the running target and locates it using Spotlight. If you added the .dSYM extension to the exclusion filter in Spotlight, you will not see the variable names (symbol names) in Instruments. More often, this happens when your disk permissions, especially permissions to the Derived Data directory, are corrupted. A quick run by a disk utility should fix that and get you up and running.

Xcode’s Symbolication

However, there are times when you want to symbolicate a binary or a crash report (more often the latter) manually. Later in this section, I discuss the various kinds of crash reports that you can use to analyze and fix issues in your app, including a homegrown crash reports collection tool and a couple of third-party crash-reporting services. When you’re using the Apple-provided iTunes Connect crash reports, you only see addresses and hex codes scattered throughout. Without proper symbolication, you cannot understand what is going on. Fortunately, Xcode symbolicates crash reports when you just drag a crash report to it. Now, how does Xcode know the corresponding dSYM file? For this “automatic” symbolication to work, you use Xcode’s Build and Archive option to build your app for submission to the App Store.

Inside the xcarchive

A quick look at the xcarchive bundle created by Xcode reveals the following directories: dSYMs, Products, and an Info.plist file. The dSYMs directory contains the list of dSYM files for all the target/static libraries you included in your project. The Products directory contains the list of all executable binaries. The Info.plist file is the same plist file in your project. The Info.plist file plays a very important role in identifying the version of the target/dsym in the xcarchive bundle. In fact, when you drag a .crash file from iTunes Connect to your Xcode, Xcode internally looks up the archives for an Info.plist file that matches the crash report and picks up the .dSYM file from the dSYMs directory inside that archive. This is the reason why you should never delete a submitted archive. When you delete your archives after submission, you’ll end up in hot water when you try to symbolicate a crash report.

Committing Your dSYMs to Your Version Control

The other way to store your dSYMs is to commit them to your version control. When you get a crash report from iTunes Connect, check out the dSYM that corresponds to the submitted version and symbolicate your crash reports by matching them with the dSYMs. This way, all developers on a team will have access to the dSYM files, and debugging crashes can be done by anyone on the team without e-mailing the dSYMs around.

Instead of committing dSYMs for every commit to your develop or feature branch, consider committing them only when you make a release. If you’re using Git as explained in Chapter 2, you should be committing dSYMs to the master branch for every release. There are other alternatives, including some third-party services that do server-side symbolication. I discuss them in the “Collecting Crash Reports” section later in this chapter.

Breakpoints

Breakpoints are a way to pause your debugger and inspect your symbols and objects in real time. Some debuggers, including LLDB, allow you to move the instruction pointer and continue debugging from a different location. You can set LLDB breakpoints in your app right from Xcode. You just scroll to the line where you want the breakpoint to be placed and click on Xcode’s, Product⇒Debug⇒Add breakpoint at Current line menu, or press Cmd+.

The Breakpoint Navigator

Breakpoints that you added to your project are listed automatically in your Breakpoint navigator. You can access the Breakpoint navigator using the keyboard shortcut Cmd+6.

The Breakpoint navigator also allows you to add breakpoints for exceptions and symbols.

Exception Breakpoints

An exception breakpoint stops program execution when your code causes an exception to be thrown. Some of the methods in the Foundation.framework’s NSArray, NSDictionary, or UIKit classes (like the UITableView method) throw exceptions when certain conditions cannot be met. This includes trying to mutate an NSArray or trying to access an array element that is beyond the array’s bounds. A UITableView throws an exception when you declare the number of rows to be “n” and don’t provide a cell for every row. In theory, debugging exceptions might look easy, but understanding the source of your exception could be fairly complicated. Your app will just crash with a log that prints out the exception that caused the crash. These Foundation.framework methods are used throughout your project, and you won’t really understand what happened by looking at the log if you don’t have an exception breakpoint set. When you set an exception breakpoint, the debugger pauses the execution of the program just after the exception is thrown, but before it is caught, and you get to see the stack trace of the crashed thread in your Breakpoint navigator.

To make things clear, compare debugging an app with and without the exception breakpoint enabled.

Create an empty application in Xcode (any template should work). In your app delegate, add the following line:

NSLog(@”%@”, [@[] objectAtIndex:100]);

This line creates an empty array, accesses the 100th element and logs it. As this is not legally allowed, Xcode crashes with the following log in Console and takes you to the infamous main.m.

2012-08-27 15:25:23.040 Test[31224:c07] (null)

libc++abi.dylib: terminate called throwing an exception

(lldb)

No one can understand what is happening behind the scenes from that cryptic log message. To debug exceptions like these, you set an exception breakpoint.

You set an exception breakpoint from the Breakpoint navigator. Open the Breakpoint navigator and click the + button on the lower-left corner, choose Add Exception Breakpoint and accept the default settings to set a new exception breakpoint, as shown in Figure 19-2.

Run the same project again. You should see the debugger pausing your application and stopping exactly on the line that raised the exception, as illustrated in Figure 19-3.

9781118449974-fg1902.eps

Figure 19-2 Adding an exception breakpoint

9781118449974-fg1903.tif

Figure 19-3 Xcode breaking at a breakpoint you just set

Exception breakpoints can help you understand the root cause of the exception. The first thing I do when I start a new project is to set an exception breakpoint. I highly recommend you doing so.

When you want to run your app quickly without hitting any breakpoints, disable all of them with the keyboard shortcut Cmd-Y.

Symbolic Breakpoints

Symbolic breakpoints are breakpoints that pause your program’s execution when a particular symbol gets executed. A symbol can be a method name or a method in a class or any C method (objc_msgSend).

You set a symbolic breakpoint from the Breakpoint navigator, much like how you set an exception breakpoint. Just choose Symbolic Breakpoint instead of Exception Breakpoint. Now in the dialog, type the symbol that you are interested in, as shown in Figure 19-4.

9781118449974-fg1904.tif

Figure 19-4 Adding a Symbolic Breakpoint

Now type application:didFinishLaunchingWithOptions: and press Enter. Build and run your application now. You should see the debugger stopping your application as it starts and showing the stack trace.

Well, the symbol you watched didn’t provide any advantage in addition to placing a normal breakpoint in your application:didFinishLaunchingWithOptions:. Symbolic breakpoints are often used to watch interesting methods like

-[NSException raise]

malloc_error_break

-[NSObject doesNotRecognizeSelector:]

In fact, the first exception breakpoint you created in the previous section is synonymous to a symbolic breakpoint on [NSException raise].

The malloc_error_break and [NSObject doesNotRecognizeSelector:] are symbols that are useful for debugging memory-related crashes. If your app crashes with an EXC_BAD_ACCESS, setting a symbolic breakpoint in one or both of these symbols will help you localize the issue.

Editing Breakpoints

Every breakpoint you create can be edited from the Breakpoint navigator. You edit a breakpoint by Ctrl+Clicking the breakpoint and choosing Edit Breakpoint from the menu. You will see a breakpoint-editing sheet, as shown in Figure 19-5.

9781118449974-fg1905.tif

Figure 19-5 Editing breakpoints

Normally, a breakpoint stops program execution every time the line is executed. You can edit a breakpoint to set a condition and create a conditional breakpoint that is executed only when the set condition is reached. Why would this be useful? Imagine you are looping through a large array (n>10000). You’re sure that the objects after 5500 are malformed and want to find out what is going wrong. The traditional approach is to write additional code (in your application’s code) to check for index values greater than 5500 and remove this code after your debugging session is over.

For example, you could write something like

  for(int i = 0 ; i < 10000; i ++) {

    if(i>5500) {

      NSLog(@”%@”, [self.dataArray objectAtIndex:i]);

    }    

  }

and place a breakpoint in the NSLog. The cleaner way is to add this condition to the breakpoint itself. In Figure 19-5, the sheet has a text field for adding a condition. Set this condition to i>5500 and run your app. Now, instead of breaking for every iteration, your breakpoints stop only when your condition is met.

You can customize your breakpoints to print out a value or play an audio file or execute an action script by adding an action. For example, if the objects that you’re iterating are a list of users, and you want to know if a said user is in your list, you can edit your breakpoint to break when the object that you’re interested in is reached. Additionally, in the action, you can select from a list of audio clips to play, execute an Apple script and/or perform a variety of other functions. Click the Action button (refer to Figure 19-5) and choose the custom action, Sound. Now instead of stopping at your breakpoint, Xcode plays the audio clip you selected. If you’re a game programmer, you might be interested in capturing an Open GL ES frame when a particular condition occurs, and this option is also available through the Action button.

Sharing Breakpoints

Your breakpoints now have code (or rather code fragments) associated with them that you want to save to your version control. Xcode 4 (and above) allows you to share your breakpoints with your coworkers by committing them to your version control. All you need to do is Ctrl-Click a breakpoint and click Share. Your breakpoints are now saved to the xcshareddata directory inside the project file bundle. Commit this directory to version control to share your breakpoints with all other programmers in your team.

Watchpoints

Breakpoints provide you with the ability to pause the program execution when a given line is executed. Watchpoints provide a way to pause the program execution when the value stored at a variable changes. Watchpoints help to solve issues related to global variables and track which method updates a said global variable. Watchpoints are like breakpoints, but instead of breaking when a code is executed, they break when data is mutated.

In an object-oriented setting, you don’t normally use global variables to maintain state, and watchpoints may not be used often. However, you may find it useful to track state changes on a singleton or other global objects like your Core Data persistent store coordinator or API engines like the one we created in Chapter 14. You can set a watchpoint on accessToken in the RESTfulEngine class to know the engine’s authentication state changes.

You cannot add a watchpoint without running your application first. Start your app, and open the watch window. The watch window, by default, lists the variables in your local scope. Ctrl+click a variable in the watch window. Now click on the watch <var> menu item to add a watchpoint on that variable. Your watchpoints will now be listed in the Breakpoint navigator.

The LLDB Console

Xcode’s debugging console window is a full-featured LLDB debugging console. When your app is paused (at a breakpoint), the debugging console shows the LLDB command prompt. You can type any LLDB debugger command into the console to help you with debugging, including loading external python scripts.

The most frequently used command is po, which stands for print object. When your application is paused in debugger, you can print any variable that is in the current scope. This includes any stack variables, class variables, properties, ivars, and global variables. In short, any variable that is accessible by your application at the breakpoint can be accessed via the debugging console.

Printing Scalar Variables

When you’re dealing with scalars like integers or structs (CGRect, CGPoint, etc.), instead of using po, you use p, followed by the type of struct.

Examples include

p (int) self.myAge

p (CGPoint) self.view.center

Printing Registers

Why do you need to print values on registers? You don’t store variables in CPU registers, right? Right, but the registers hold a wealth of information about the program state. This information is dependent on the subroutine calling convention on a given processor architecture. Knowledge of this information reduces your debug cycle time tremendously and makes you a programmer who can push the limits.

Registers in your CPU are used for storing variables that have to be accessed frequently. Compilers optimize frequently used variables like the loop variable, method arguments, and return variables in the registers. When your app crashes for no apparent reason (apps always crash for no apparent reason ‘til you find the problem, right?), probing the register for the method name or the selector name that crashed your app will be very useful.

The C99 language standard defines the keyword register that you can use to instruct the compiler to store a variable in the CPU registers. For example, declaring a for loop like for (register int i = 0 ; i < n ; i ++) will store the variable i in the CPU registers. Note that the declaration isn’t a guarantee and the compiler is free to store your variable in memory if there are no available free registers.

You can print the registers from the LLDB console using the command register read. Now, create an app and add a code snippet that causes a crash.

int *a = nil;

NSLog(@”%d”, *a);

You will create a nil pointer and try accessing the value at the address. Obviously, this is going to throw an EXC_BAD_ACCESS. Write the preceding code in your application:didFinishLaunchingWithOptions: method (or any method you like) and run the app in your simulator. Yes, I repeat, in your simulator. When the app crashes, go to the LLDB console and type the command to print the register values.

register read

Your console should show something like the following.

Register Contents (Simulator)

(lldb) register read

General Purpose Registers:

       eax = 0x00000000

       ebx = 0x07408520

       ecx = 0x00001f7e  Test`-[MKAppDelegate

       application:didFinishLaunchingWithOptions:] + 14 at

       MKAppDelegate.m:13

       edx = 0x00003604  @”%d”

       edi = 0x07122070

       esi = 0x0058298d  “application:didFinishLaunchingWithOptions:”

       ebp = 0xbfffde68

       esp = 0xbfffde30

        ss = 0x00000023

    eflags = 0x00010286  UIKit`-[UIApplication _addAfterCACommitBlockForViewController:] + 23

       eip = 0x00001fca  Test`-[MKAppDelegate

       application:didFinishLaunchingWithOptions:] + 90 at

       MKAppDelegate.m:19

        cs = 0x0000001b

        ds = 0x00000023

        es = 0x00000023

        fs = 0x00000000

        gs = 0x0000000f

(lldb)

The equivalent on a device (ARM processor) looks like the following.

Register Contents (Device)

(lldb) register read

General Purpose Registers:

        r0 = 0x00000000

        r1 = 0x00000000

        r2 = 0x2fdc676c

        r3 = 0x00000040

        r4 = 0x39958f43  “application:didFinishLaunchingWithOptions:”

        r5 = 0x1ed7f390

        r6 = 0x00000001

        r7 = 0x2fdc67b0

        r8 = 0x3c8de07d

        r9 = 0x0000007f

       r10 = 0x00000058

       r11 = 0x00000004

       r12 = 0x3cdf87f4  (void *)0x33d3eb09: OSSpinLockUnlock$VARIANT$mp + 1

        sp = 0x2fdc6794

        lr = 0x0003a2f3  Test`-[MKAppDelegate

        application:didFinishLaunchingWithOptions:] + 27 at

        MKAppDelegate.m:13

        pc = 0x0003a2fe  Test`-[MKAppDelegate

        application:didFinishLaunchingWithOptions:] + 38 at

        MKAppDelegate.m:18

      cpsr = 0x40000030

(lldb)

Your output may vary, but pay close attention to the eax, ecx, and esi on the simulator or r0-r4 registers when running on a device. These registers store some of the values that you’re interested in. In the Simulator (running on your Mac’s Intel processor), the ecx register holds the name of the selector that is called when your app crashed. You print an individual register to console by specifying the register name as shown below

register read ecx.

You can also specify multiple registers like

register read eax ecx.

The ecx register on Intel architecture and the r15 register on ARM architecture hold the program counter. Printing the address of the program counter will show the last executed instruction. Similarly, eax (r0 on ARM) holds the receiver address, ecx (r4 on ARM) and holds the selector that was called last (in this case, it’s the application:didFinishLaunchingWithOptions: method). The arguments to the methods are stored in registers r1-r3. If your selector has more than three arguments, they are stored on stack, accessible via the stack pointer (r13). sp, lr, and pc are actually aliases to the r13, r14, and r15 registers, respectively. Hence, register read r13 is equivalent to register read sp.

So *sp, *sp+4 , and so on, will contain the address of your fourth and fifth arguments, and so on. On Intel architecture, the arguments start at the address stored in ebp register.

When you download a crash report from iTunes Connect, it normally has the register state, and knowing the register layout on ARM architecture will help you better analyze the crash report. The following is a register state from a crash report.

Register State in a Crash Report

Thread 0 crashed with ARM Thread State:

    r0: 0x00000000    r1: 0x00000000      r2: 0x00000001      r3: 0x00000000

    r4: 0x00000006    r5: 0x3f871ce8      r6: 0x00000002      r7: 0x2fdffa68

    r8: 0x0029c740    r9: 0x31d44a4a     r10: 0x3fe339b4     r11: 0x00000000

    ip: 0x00000148    sp: 0x2fdffa5c      lr: 0x36881f5b      pc: 0x3238b32c

  cpsr: 0x00070010

Using otool, you can print the methods used in your app. Match the address in your program counter using grep to see which exact method was being executed when the app crashed.

otool -v -arch armv7 -s __TEXT __cstring <your image> | grep 3238b32c

Replace <your image> with the image of the app that crashed. (You will have either committed this to your repository or stored it in the application archives in Xcode.)

Note that what you learned in this section is highly processor-specific and may change in the future if Apple changes the processor specification (from ARM to something else). However, once you understand the basics, you should be able to apply this knowledge to any new processor that comes along.

Scripting Your Debugger

The LLDB debugger was built from the ground up to support APIs and pluggable interfaces. Python scripting is one of the benefits of these pluggable interfaces. If you’re a Python programmer, you’ll be pleasantly surprised to learn that LLDB supports importing Python scripts to help you with debugging, which means that you can write a script in Python, import it into your LLDB, and inspect variables in LLDB using your script. If you’re not a Python programmer, you can skip this section and probably won’t lose anything.

Assume that you want to search for an element in a large array containing, say, 10,000 objects. A simple po on the array is going to list all ten thousand objects, which is tedious to manually go through. If you have a script that takes this array as a parameter and searches for the presence of your object, you can import it into LLDB and use it for debugging.

You start the Python shell from the LLDB prompt by typing script. The prompt changes from (lldb) to >>>. Within the script editor, you can access the LLDB frame using the lldb.frame Python variable. So lldb.frame.FindVariable(“a”) will get the value of the variable a from the current LLDB frame. If you’re iterating an array to look for a specific value, you can assign the lldb.frame.FindVariable(“myArray”) to a variable and pass it to your Python script.

The following code illustrates this.

Invoking the Python Script to Search for Occurrence of an Object

>>> import mypython_script

>>> array = lldb.frame.FindVariable (“myArray”)

>>> yesOrNo = mypython_script.SearchObject (array, “<search element>”)

>>> print yesOrNo

The preceding code assumes that you wrote a SearchObject function in a file mypython_script. Explaining the implementation details of the python script is outside the scope of this book.

NSZombieEnabled Flag

A debugging chapter is not complete without mentioning the NSZombieEnabled environment variable. The NSZombieEnabled variable is used to debug memory-related bugs and to track over release of objects. NSZombieEnabled is an environment variable that, when set, swizzles out the default dealloc implementation with a zombie implementation that converts an object to a zombie object when the retain count reaches zero. The functionality of the zombie object is to display a log and break into the debugger whenever you send a message to it.

So when you enable NSZombies in your app, instead of crashing, a bad memory access is just an unrecognized message sent to a zombie. A zombie displays the received message and breaks into the debugger allowing you to debug what went wrong.

You enable the NSZombiesEnabled environment variable from Xcode’s schemes sheet. Click on Product⇒Edit Scheme to open the sheet and set the Enable Zombie Objects check box, as shown in Figure 19-6.

Zombies were helpful back in the olden days when there was no ARC. With ARC, if you’re careful with your ownerships, normally you won't have memory-related crashes.

Different Types of Crashes

Software written using a programming language crashes as opposed to web applications written (or scripted) in a scripting or a markup language. Because a web application runs within the context of a browser, there is little possibility that a web app can corrupt memory or behave in a way that could crash the browser. If you’re coming from a high-level language background, terms used by Xcode to denote various crashes may be cryptic to you. This section attempts to shed light on some of these. Crashes are usually signals sent by the operating system to the running program.

9781118449974-fg1906.tif

Figure 19-6 Enabling Zombie objects

EXC_BAD_ACCESS

An EXC_BAD_ACCESS occurs whenever you try to access or send a message to a deallocated object. The most common cause of EXC_BAD_ACCESS is when you initialize a variable in one of your initializer methods but use the wrong ownership qualifier, which results in deallocation of your object. For example, you create an NSMutableArray of elements for your UITableViewController in the viewDidLoad method but set the ownership qualifier of the list to unsafe_unretained or assign instead of strong. Now in cellForRowAtIndexPath:, when you try to access the deallocated object, you’ll crash with a EXC_BAD_ACCESS. Debugging EXC_BAD_ACCESS is made easy with the NSZombiesEnabled environment variable that you found out about in the last section.

If you don’t understand ownership qualifiers, turn back to Chapter 5 to catch up.

SIGSEGV

A signal segment fault (SIGSEGV) is a more serious issue that the operating system raises. It occurs when there is a hardware failure or when you try to access a memory address that cannot be read or when you try to write to a protected address.

The first case, a hardware failure, is uncommon. When you try to read data stored in RAM and the RAM hardware at that location is faulty, you get a SIGSEGV. But more often than not, a SIGSEGV occurs for the latter two reasons. By default, code pages are protected from being written into and data pages are protected from being executed. When one of your pointers in your application points to a code page and tries to alter the value pointed to, you get a SIGSEGV. You also get a SIGSEGV when you try to read the value of a pointer that was initialized to a garbage value pointing to an address that is not a valid memory location.

SIGSEGV faults are more tedious to debug, and the most common case reason a SIGSEGV happens is an incorrect typecast. Avoid hacking around with pointers or trying to a read a private data structure by advancing the pointer manually. When you do that and don’t advance the pointer to take care of the memory alignment or padding, you get a SIGSEGV.

SIGBUS

A signal bus (SIGBUS) error is a kind of bad memory access where the memory you tried to access is an invalid memory address. That is, the address pointed to is not a physical memory address at all (it could be the address of one of the hardware chips). Both SIGSEGV and SIGBUS are subtypes of EXC_BAD_ACCESS.

SIGTRAP

SIGTRAP stands for signal trap. This is not really a crash signal. It’s sent when the processor executes a trap instruction. The LLDB debugger usually handles this signal and stops at a specified breakpoint. If you get a SIGTRAP for no apparent reason, a clean build will usually fix it.

EXC_ARITHMETIC

Your application receives an EXC_ARITHMETIC when you attempt a division by zero. This should be a straightforward fix.

SIGILL

SIGILL stands for SIGNAL ILLEGAL INSTRUCTION. This happens when you try to execute an illegal instruction on the processor. You execute an illegal instruction when you’re trying to pass a function pointer to another function, but for one reason or other, the function pointer is corrupted and is pointing to a deallocated memory or a data segment. Sometimes you get an EXC_BAD_INSTRUCTION instead of SIGILL, and though both are synonymous, EXC_* are machine-independent equivalents of this signal.

SIGABRT

SIGABRT stands for SIGNAL ABORT. This is a more controlled crash where the operating system has detected a condition that is not safe and asks the process to perform cleanup if any is needed. There is no one silver bullet to help you debug the underlying error for this signal. Frameworks like cocos2d or UIKit often call the C function abort (which in turn sends this signal) when certain preconditions are not met or when something really bad happens. When a SIGABRT occurs, the console usually has a wealth of information about what went wrong. Since it’s a controlled crash, you can print the backtrace by typing bt into the LLDB console.

The following shows a SIGABRT crash on the console.

Printing the Backtrace Often Points the Reason for a SIGABRT

(lldb) bt

* thread #1: tid = 0x1c03, 0x97f4ca6a libsystem_kernel.dylib`__pthread_kill

  + 10, stop reason = signal SIGABRT

    frame #0: 0x97f4ca6a libsystem_kernel.dylib`__pthread_kill + 10

    frame #1: 0x92358acf libsystem_c.dylib`pthread_kill + 101

    frame #2: 0x04a2fa2d libsystem_sim_c.dylib`abort + 140

    frame #3: 0x0000200a Test`-[MKAppDelegate application:didFinishLaunchingWithOptions:] + 58 at MKAppDelegate.m:16

Watchdog Timeout

This is normally distinguishable because of the error code 0x8badf00d. (Programmers, too, have a sense of humor — this is read as Ate Bad Food). On iOS, this happens mostly when you block the main thread with a synchronous networking call. For that reason, don’t ever do a synchronous networking call.

For more in-depth information about networking, read Chapter 14 of this book.

Custom Error Handling for Signals

You can override signal handling using the C function sigaction and providing a pointer to a signal handler function. The sigaction function takes a sigaction structure that has a pointer to the custom function that needs to be invoked, as shown in the following code.

Custom Code for Processing Signals

void SignalHandler(int sig) {

   // your custom signal handler goes here

}

// Add this code at startup

struct sigaction newSignalAction;

memset(&newSignalAction, 0, sizeof(newSignalAction));

newSignalAction.sa_handler = &SignalHandler;

sigaction(SIGABRT, &newSignalAction, NULL);

In this method, a custom C function for SIGABRT signal is added. You can add more handler methods for other signals as well using code similar to this.

A more sophisticated method is to use an open source class written by Matt Gallagher. Matt, author of the popular cocoa blog Cocoa with Love made the open source class UncaughtExceptionHandler that you can use to handle uncaught exceptions. The default handler shows an error alert. You can easily customize it to save the application state and submit a crash report to your server. A link to Matt’s post is provided in the “Further Reading” section at the end of this chapter.

Collecting Crash Reports

When you’re developing for iOS, you have multiple ways to collect crash dumps. In this section, I explain the most common way to collect crash reports, namely, iTunes Connect. I also explain about two other third-party services that also offers server-side symbolication in addition to crash reporting.

iTunes Connect

iTunes Connect allows you to download crash reports for your apps. You can log in to iTunes Connect and go to your app’s details page to download crash reports. Crash reports from iTunes are not symbolicated, and you should symbolicate them using the exact dSYM that was generated by Xcode when you built the app for submission. You can do this either automatically using Xcode or manually (using the command line utility symbolicatecrash).

Collecting Crash Reports

When your app crashes on your customer’s device, Apple takes care of uploading the crash dumps to its servers. But do remember that this happens only when the user accepts to submit crash reports to Apple. Although most users submit crash reports, some may opt out. As such, iTunes crash reports may not represent your actual crash count.

Symbolicating iTunes Connect Crash Reports Using Xcode

If you’re the sole developer, using Xcode’s automatic symbolication makes more sense, and it’s easier. There is almost no added advantage of using manual symbolication. As a developer, you only have to use Xcode’s Build⇒Archive option to create your product’s archive and submit this archive to the App Store. This archive encapsulates both the product and the dSYM. Don’t ever delete this archive file, even after your app is approved. For every app (including multiple versions) that you submit, you need to have a matching archive in Xcode.

Symbolicating iTunes Connect Crash Reports Manually

When there’s more than one developer, every developer on the team needs to be able to symbolicate a given crash report. When you depend on Xcode’s built-in Archive command to store your dSYMs, the only developer able to symbolicate a crash is the one who submits apps to the App Store. To allow other developers to symbolicate, you may have to e-mail archive files around, which is probably not the right way to do it. My recommendation is to commit the archives to your version control for every release build.

From the Organizer, go to the Archives tab and Cmd+click the archive that you just submitted. Reveal it in Finder, copy it to your project directory, and commit your release branch. If you’re following my advice from Chapter 2, you’ve tagged your repository for every release. Now when you get a crash report from Apple, check out the archive file from your version control that corresponds to the crash report. That is, if your crash report is for version 1.1, get the archive from your version control that corresponds to version 1.1. Open the archive file’s location in Terminal.

You can now symbolicate your crash report manually using either the following symbolicatecrash.sh shell script

symbolicatecrash MyApp.crash MyApp.dSYM > symbolicated.txt

or using atos in interactive mode, as shown here:

atos -arch armv7 -o MyApp

If your crash report matches your dSYM file, you should see your memory addresses symbolicated in the text file.

Using atos in interactive mode will be handy in many cases where you just want to know the address of the thread’s stack trace that crashed. atos assumes that your dSYM file is located in the same location as your application.

symbolicatecrash is a script that’s not in your %PATH% by default. As such, you might get a command not found error when you type the preceding command on Terminal. As of Xcode 4.3, this script resides in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash. Even if this location changes in the near future, you should be able to run a quick search for this file from the command line using the command find /Applications/Xcode.app -name symbolicatecrash -type f.

Third-Party Crash Reporting Services

Whew, that was painful! But as the saying goes, necessity is the mother of invention. A painful crash analysis process leads plenty of third-party developers to make alternative services that, apart from crash log collection and analysis, do symbolication on the server-side.

You can use TestFlight and HockeyApp to collect and symbolicate crash reports. TestFlight is free while HockeyApp is a paid alternative. Both have their own advantages and disadvantages, but an in-depth discussion of them is outside the scope of this chapter. The following section briefly compares them to iTunes Connect.

Advantages of TestFlight or HockeyApp over iTunes Connect

Both TestFlight and HockeyApp provide an SDK that you normally integrate with your app. These SDKs take care of uploading crash reports to their servers. Although Apple uploads crash reports only when the user consents, these SDKs will always upload the crash reports. That means that you get more accurate statistics on the number of times a particular kind of crash occurs. Since crash reports normally don’t have personally identifiable information, uploading them without the user’s consent is allowed within the rules of the App Store.

Secondly, you can upload a dSYM to TestFlight and symbolicate crash reports for the live versions of your app. For adhoc builds, TestFlight’s desktop client automatically uploads dSYMs. Server-side symbolication means that you don’t have to know anything about symbolicatecrash or atos commands or use the Terminal. In fact, both these services upload your dSYM files to their server. You and their SDK collect crash reports from users’ devices.

Summary

In this chapter, you read about LLDB and debugging. You learned about breakpoints, watchpoints, and how to edit and share those edited breakpoints. You discovered the power behind the LLDB console and how to use a Python script to speed up your debugging. You also read about the various types of errors, crashes, and signals that normally occur in your iOS app, and you found out how to avoid and overcome them. Finally, you learned about some third-party services that allow you to symbolicate crash reports on the server.

Further Reading

Apple Documentation

The following documents are available in the iOS Developer Library at developer.apple.com or through the Xcode Documentation and API Reference.

Xcode 4 User Guide: “Debug Your App”

Developer Tools Overview

LLVM Compiler Overview

Read the header documentation in the following header files. You can get these files by searching for them using Spotlight.

exception_types.h

signal.h

WWDC Session

WWDC 2012: “Session 415: Debugging with LLDB”

Other Resources

Writing an LLVM Compiler Backendhttp://llvm.org/docs/WritingAnLLVMBackend.html

Apple’s “Lazy” DWARF Schemehttp://wiki.dwarfstd.org/index.php?title=Apple’s_%22Lazy%22_DWARF_Scheme

Hamster Emporium archive. “[objc explain]: So You Crashed in objc_msgSend()”http://www.sealiesoftware.com/blog/archive/2008/09/22/objc_explain_So_you_crashed_in_objc_msgSend.html

Cocoa with Love. “Handling Unhandled Exceptions and Signals”http://cocoawithlove.com/2010/05/handling-unhandled-exceptions-and.html

furbo.org. “Symbolicatifination”http://furbo.org/2008/08/08/symbolicatifination/

LLDB Python Referencehttp://lldb.llvm.org/python-reference.html

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

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