Click here to Skip to main content
15,881,281 members
Articles / Xcode

Debugging with Xcode 8

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
12 Apr 2017CPOL7 min read 12.6K   2  
Advanced debugging features to improve developer productivity

Introduction

Apple has added new debugging tips and commands to Xcode 8 for developers to be more productive. We all know that the LLDB (Low-Level Debugger) is the debugger behind Xcode for both iOS and Mac OS. It works closely with LLVM compiler to bring more capabilities. We are interacting with it via Xcode debugger console.
This article explains advanced debugging features to improve developer’s productivity. Following are the tips for better debugging.

  • Declaration of new variable, function, or closure in debugging console
  • "Use Terminal” feature in Xcode 8
  • Setting alias command
  • Figuring App Crash
  • parray and poarray commands

Expression Parser

Let us consider a scenario, an application has downloaded JSON file or some web content. There is a need for checking some specific data from the large JSON file or filter some data from web content. For example, the application has downloaded the maintenance history of a particular product and you wanted to know if there is some maintenance that happened in the current month. Probably you may stop running the application and write a function which can filter the content and display it and then start debugging the app from first. It is not only a time-consuming process but also non-productive. Just for better understanding and debugging, we are stopping the execution and writing the method. What if we can add custom predicates in debugging time and execute it with program data.

Xcode is providing a facility to add new variables, methods or closures in debugger console itself. We can achieve this through expression parser.

For example - declaring a new variable in debugger console and using it with program object while debugging. These variables, functions or closures will be retained as long as debug session is alive.

Let us look at some examples.

In Swift

Declaring a Variable in Debugging Console
Swift
(lldb) (lldb) expr let $USDollorValue=64;
(lldb)  expr $USDollorValue
(Int) $R1 = 64

Variable USDollorValue represents the data 64 and it can be used for any computation with program values in debug console. Variable has to be declared with a ‘$’ symbol for reusing it.

Using the Variable with Program Values

In the below example, USDollorValue will be multiplied with a program variable called totalAmt.

Swift
1472
Declaring a Function in Debugging Console

At lldb prompt, type expr and then the command will turn to multiline expression mode to declare user-defined functions. Add ‘$’ symbol before the function, and then we can reuse it by passing arguments. Here is an example to show formatted output student name and totalMark. Here totalMark is program variable.

Swift
(lldb) expr
Enter expressions, then terminate with an empty line to evaluate:
1 func $studentReport(sname:String) -> Void {
2   let tlAmt = String(totalMark)
3   let StudentReport:String = "Reported Student Name is"+sname+" with total mark "+tlAmt;
4   print(StudentReport);
5 }
Calling the Function in Debug Console.
Swift
(lldb) expr $studentReport(sname:"John")
Reported Student Name isJohn with total mark 23

In C, C++ and Objective C

In this case, expr command won't work directly for declaring variable, functions or closure. Here, we have to use top-level expression mode.

Top-level expression mode allows the controller to come out of the current function to declare variable or functions globally.

Declaring variables called USDollorValue
Objective-C
(lldb) expr --top-level --
Enter expressions, and then terminate with an empty line to evaluate:
1 $USDollorValue=64;
2
Using the Variable USDollorValue with Object Variable called totalAmt
Objective-C
(lldb)  expr $USDollorValue *self.totalAmt;
3328
Declaring a C Function for Sorting, in Debug Console
Objective-C
(lldb) expr --top-level --

Enter expressions, then terminate with an empty line to evaluate:
1  void $printInSortedOrder(int *number,int n){
2    for (int i = 0; i < n; ++i){
3    for (int j = i + 1; j < n; ++j){
4    if (number[i] > number[j]){
5    int a =  number[i];
6    number[i] = number[j];
7    number[j] = a;
8  }}}
9   for (int i=0;i<n; i++) {
10   printf("\n%i",number[i]);}
11  }
12
Calling the function in debug console, passing an array called rateValue with number of digits 4
Objective-C
(lldb) expr $printInSortedOrder(rateValue, 4)
2
7
9
10
Declaring C function which computes the square root of given number using Math library function.
Objective-C
(lldb) expr --top-level --
Enter expressions, then terminate with an empty line to evaluate:
1 void $squareRoot(int num)
2 {
3   printf("Using Math function in C to calculate square root \n");
4   float answer=sqrt(num);
5   printf("square root = %f",answer);
6 }
7
Output of the function will be:
Objective-C
(lldb) expr $squareRoot(16)
  Using Math function in C to calculate square root 
  square root = 4.000000
Declaring a Custom Predicate at Run Time

Consider a JSON file as follows:

JavaScript
{
  "modelno": "MD4563",
   
   "maintenance":
     [
       {
     "month":"january",
     "values":["lcddisplay","filament"]
       },
        {
     "month":"february",
     "values":["Nozzle diameter","Automatic grade"]
       }
     ]
}

Let us create a new predicate, which can filter the maintenance details that in the month of January.

Let us declare the custom predicate in lldb console.
Objective-C
(lldb) expression

Enter expressions, then terminate with an empty line to evaluate:

1 NSPredicate *$predicateM = 
[NSPredicate predicateWithFormat:@"month == %@",@"january"];

2
Calling the Predicate
Objective-C
(lldb) po [[modelNo valueForKey:@"maintenance"] 
filteredArrayUsingPredicate:$predicateM];

Output will be
<__NSSingleObjectArrayI 0x17000ecf0>(

{
    month = january;
    values =     (
        lcddisplay,
        filament
    );
}
)

“Use Terminal” Feature in Xcode 8

One of the new features in Xcode 8 is to initiate new standalone console for application input and output at the same time using Xcode default console for debugging alone. Enable “Use Terminal” from schema options.

Image 1

So now we can handle application input and output in the stand-alone terminal and at the same time, we can debug the application in Xcode default console. The advantage of this feature is that developers can manage application I/O in a separate console. We can utilize this option if we don’t want to jumble with application input, output and debugger output on same Xcode default console.

Command Alias

Developers can be more productive if the debugger command is customizable. What if we can assign an alias for the command, which is being used frequently?

This command helps the developers to set alias name for frequently used commands and can proceed with their execution in a productive manner.

For example, we use command "thread continue" , to resume from breakpoint and continue with the application. Here "thread continue" is used frequently.

Also in Xcode 8, help text can be added to alias command.

Let us set an alias called “tc” for the command “thread continue”.

Objective-C
(lldb) command alias -h"continue the current thread" -- tc thread continue

Here “command alias” is the lldb command to set an alias. The text following –h is the help text which describes what the new alias command actually doing. '--' followed by “tc” is the alias name for the command “thread continue”. Hereafter we can type “tc” in lldb console instead of thread continue.

Executing the command "tc":

Objective-C
(lldb) tc
Resuming thread 0x6753 in process 1838
Process 1838 resuming

For getting all the details about the command “tc”, type:

Objective-C
(lldb) help tc

 continue the current thread
 Syntax: tc <thread-index> [<thread-index> [...]]
'tc' is an abbreviation for 'thread continue

The alias commands are active or valid as long as the debugging session is alive. Repeatedly setting an alias for commands before starting debugging is a tiresome activity. LLDB has one solution for this. LLDB has initialization file called ‘.lldbinit ‘. We can add all the required alias commands to the .lldbinit file so that all the commands will be initialized at debugger launch time, then the alias commands will be available across the projects.

Figuring App Crash

When we get runtime crash, mostly Xcode can show its stack trace. But there are some scenarios where Xcode won’t be able to show much detail about the crash. Sometimes, it occurs with a type of crash like “EXE_BAD_ACCESS” or crashes occurred from third party libraries.
Let us see some scenario to debug this kind of crash.

Let us look into figuring app crashes by reading machine registers. Registers are the part of the processor, which hold the small set of data. As soon as Xcode hits at crash point, we can list all the registers by the command “Register read”, so the output will look like:

Objective-C
General Purpose Registers:

       rax = 0x0000000000000000
       rbx = 0x00006080000792c0
       rcx = 0x0000000000000004
       rdx = 0x0000000000000000
       rdi = 0x000060000000d260
       rsi = 0x0000000110e8aaea  "initWithData:encoding:"
       rbp = 0x00007fff55a603f0
       rsp = 0x00007fff55a602d0
        r8 = 0x0000000000000040
        r9 = 0x00006000000b4c10
       r10 = 0x000000000000000a
       r11 = 0x000000010bbd4a98  Foundation`-[NSPlaceholderString initWithData:encoding:]
       r12 = 0x000000010c110ac0  libobjc.A.dylib`objc_msgSend
       r13 = 0x000000010c11497a  "release"
       r14 = 0x000060800027c800
       r15 = 0x0000000000000000
       rip = 0x000000010a1c2b78  
       MedicationAssistant`-[MedVPAViewController didReceiveVoiceResponse:] + 
       104 at MedVPAViewController.m:180
    rflags = 0x0000000000000246
        cs = 0x000000000000002b
        fs = 0x0000000000000000
        gs = 0x0000000000000000

Here we have to find out which argument in which register causes the crash. For that, we can explore each frame from Stack Frame. Let us read from 0th frame to top most frame till we can point out the exact function which causes the error.

For example, I have a call stack that looks like:

Eg:- Stack Frame
UIApplicationMain     -Frame4
DidFinishLaunching    -Frame3
Function1             -Frame2     
Function2             -Frame1                                             
Function3             -Frame0

We can point to the youngest Frame by typing “Frame Select 0” in lldb console. We will receive the frame details like:

Objective-C
(lldb) frame select 0

frame #0: 0x0000000109e35b4b MedicationAssistant`-
[MedVPAViewController didReceiveVoiceResponse:withValuePointer:]
(self=0x00007fb2b0e10b60, _cmd="didReceiveVoiceResponse:withValuePointer:", 
data=0x0000000000000000, x=110) + 123 at MedVPAViewController.m:180
   177         
   178         NSString *responseString = 
   [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
   179         int *status = NULL;
-> 180         *status = x;
   181         NSError *jsonError;
   182         NSRange range ;
   183         NSString *parsedData=nil;

The above output says the crash happened in didReceiveVoiceResponse:withValuePointer: in line 180, where you are assigning x value to a pointer variable called status. Now let us check from where the value x is being passed. For that, let us move to the immediate top frame.

Either we can give the command:

Objective-C
Lldb) frame select 1
Or
Lldb) up

Use “up” and “down” commands to move the pointer to younger or older frames in a stack frame.

The above commands help the debugger to point out the immediate top frame

And the result will look like:

Objective-C
frame #1: 0x0000000109ecea1b MedicationAssistant`-
[SpeechToTextModule gotResponse:](self=0x00006080001dd3d0, 
_cmd="gotResponse:", jsonData=0x0000000000000000) + 
75 at SpeechToTextModule.m:280
   277    
   278     - (void)gotResponse:(NSData *)jsonData {
   279         [self cleanUpProcessingThread];
-> 280         [delegate didReceiveVoiceResponse:jsonData withValuePointer:framesize];
   281     }
   282    
   283     - (void)requestFailed:(NSError *)error

From the above output, we can conclude that didReceiveVoiceResponse:withValuePointer: is invoked from SpeechToTextModule::GotResponse method and there is something wrong with data in “framesize”. This way, by exploring the stack frame, we can almost figure out the cause of the crash.

parray and poarray

When we are working with NSArray or NSDictionary, we use “p” or “po” commands to display its value. ”p” and “po” are the commonly used powerful commands.
But in case we are trying to display a C-Array pointer, it will not display all the values in an array. It just prints the object pointer value alone. If you are working with C code and wanted to print all the values in a C pointer array while debugging, before Xcode 8, we may need to write a code to traverse through the array and print each value. But from Xcode 8, we have two new commands to print C pointer array in debugging console. Here debugger introduced 2 new commands, which will work with C pointer arrays.

Objective-C
parray  <count> <arrayName>
poarray  <count> <arrayName>
(lldb) parray 10 transArray
(lldb) poarray 3 transArray

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead HCL TECH
India India
Technical lead in Mobility COE , part of the Technology Office in Engineering and R&D Services group of HCL Technologies. Having good expertise in iOS native application development. Now involved in research and development of applications in iOS and watchOS.

Comments and Discussions

 
-- There are no messages in this forum --