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
(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
.
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.
(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.
(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
(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
(lldb) expr $USDollorValue *self.totalAmt;
3328
Declaring a C Function for Sorting, in Debug Console
(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
(lldb) expr $printInSortedOrder(rateValue, 4)
2
7
9
10
Declaring C function which computes the square root of given number using Math library function.
(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:
(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:
{
"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.
(lldb) expression
Enter expressions, then terminate with an empty line to evaluate:
1 NSPredicate *$predicateM =
[NSPredicate predicateWithFormat:@"month == %@",@"january"];
2
Calling the Predicate
(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.
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
”.
(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
":
(lldb) tc
Resuming thread 0x6753 in process 1838
Process 1838 resuming
For getting all the details about the command “tc
”, type:
(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:
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:
(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:
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:
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.
parray <count> <arrayName>
poarray <count> <arrayName>
(lldb) parray 10 transArray
(lldb) poarray 3 transArray