- Debugging Swift code with LLDB
- Ahmed Sulaiman
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: VernonVan
- Proofreader: ZhiyuanSun, Danny1451
Debug Swift code with LLDB
As engineers, we spend about 70% of our time debugging, 20% thinking about architecture and communicating with team members, and only 10% of our time actually writing code.
Debugging is like being a detective in a crime movie, and you’re also a murderer.
— Filipe Fortes (Twitter
So it’s really important that we be as happy as possible 70 percent of the time. The LLDB is here to rescue us. The fantastic Xcode Debugger UI displays all the information available to you without typing in a single LLDB command. However, the console is also an important part of what we do. Now let’s examine some of the most useful LLDB techniques. I use them myself every day for debugging.
Where to start?
The LLDB is a huge tool with many useful commands built in. I won’t go through them all, but walk you through the most useful commands. Here’s our plan:
- Get variable values:
expression
.e
.print
.po
.p
- Get the state of the entire application as well as language-specific commands:
bugreport
.frame
.language
- Control application execution process:
process
.breakpoint
.thread
.watchpoint
- Honorable mention:
command
.platform
.gui
I also have a table of useful LLDB command descriptions and examples that you can paste on your Mac to memorize 🙂
Through this article links to download full-size version – www.dropbox.com/s/9sv67e7f2…
1. Obtain variable values and status
Commands: expression, e, print, Po, p
One of the basic functions of the debugger is to get and modify the value of a variable. This is why Expression or E was created (with more advanced functions, of course). You can simply execute any expression or command at run time.
Suppose you are now debugging the method valueOfLifeWithoutSumOf() : sum two numbers and subtract by 42 to get the result.
Continue to assume that you keep getting the wrong results and you don’t know why. So here are some things you can do to find the problem:
Or… A better approach is to use LLDB expressions to modify values at run time while figuring out where the problem is. First, set a breakpoint where you are interested and run your application.
To print the specified variable in LLDB format you should call:
(lldb) e <variable>
Copy the code
Use the same command to execute some expressions:
(lldb) e <expression>
Copy the code
(lldb) e sum
(Int) $R0= 6 // You can also use the following$R0(LLDB) e sum = 4 (LLDB) e sum (Int)$R2= 4 // The variable sum will be the same until the end of this debugging"4"
Copy the code
The expression command also has flags. Use a double dash after expression — to separate the flag from the actual expression, like this:
(lldb) expression <some flags> -- <variable>
Copy the code
The expression command has almost 30 different flags. I encourage you to explore them more. To see the complete document, type the following command in the terminal:
> lldb
> (lldb) help Command to get all variables
> (lldb) help expression Get subcommands for all expressions
Copy the code
I’ll stay a little longer on the following expression signs:
-D <count>
(--depth <count>
) – Sets the maximum recursion depth when dumping aggregate types (default is infinity).-O
(--object-description
If possible, use the description API of the specified language.-T
(--show-types
) – Displays variable types when dumping values.-f <format>
(--format <format>
) — Specifies a format to use for display.-i <boolean>
(--ignore-breakpoints <boolean>
) – Ignore breakpoints while running expressions.
Suppose we have an object called Logger that has some string and structure-type properties. For example, you might just want to know the attributes of the first level, using the -d flag and the appropriate level depth value, like this:
(lldb) e -D 1 -- logger
(LLDB_Debugger_Exploration.Logger) $R5 = 0x0000608000087e90 {
currentClassName = "ViewController"
debuggerStruct ={...}
}
Copy the code
By default, LLDB iterates through the object indefinitely and shows you the full description of each nested object:
(lldb) e -- logger
(LLDB_Debugger_Exploration.Logger) $R6 = 0x0000608000087e90 {
currentClassName = "ViewController"
debuggerStruct = (methodName = "name", lineNumber = 2, commandCounter = 23)
}
Copy the code
You can also use e-o — to get the description of the object or more simply use the alias Po, as in the following example:
(lldb) po logger
<Logger: 0x608000087e90>
Copy the code
It’s not very descriptive, is it? To get a more readable description, your custom class must comply with the CustomStringConvertible protocol and implement var Description: String {return… } properties. Next, just use Po to return a readable description.
I also mentioned the print command at the beginning of this section. Basically print
equals expression —
. But the print command cannot take any flags or extra arguments.
2. Get the status of the entire APP and the command of the specified language
bugreport
, frame
, language
Do you often copy and paste crash logs into task manager for later consideration? The LLDB provides a handy command called bugreport that generates a complete report of the current state of the application. This command is useful when you accidentally trigger a problem but want to solve it later. To restore the state of your application, you can use Bugreport to generate reports.
(lldb) bugreport unwind --outfile <path to output file>
Copy the code
The final report looks like the example in the screenshot below:
Example of the bugreport command output.
Suppose you want to get an overview of the current stack frame for the current thread,frame
Commands can help you:
Use the following code snippet to quickly get the current address and the current environment conditions:
(lldb) frame info
frame # 0: 0x000000010bbe4b4d LLDB-Debugger-Exploration`ViewController.valueOfLifeWithoutSumOf(a=2, b=2, self=0x00007fa0c1406900) -> Int at ViewController.swift:96
Copy the code
This information is useful in breakpoint management, which I’ll cover later in this article.
LLDB has several language-specific commands, including C++, Objective-C, Swift, and RenderScript. In this article, we focus on Swift. These are two commands: demangle and refcount.
Demangle, as its name implies, is used to recombine Swift type names (because Swift generates type names at compile time to avoid namespace problems). If you want to learn more, I suggest you check out WWDC14’s “Advanced Swift Debugging in LLDB” session.
Refcount is also a fairly straightforward command to get the number of references to a given object. Let’s take a look at an example of object output using the object we discussed in the previous section — Logger:
(lldb) language swift refcount logger
refcount data: (strong = 4, weak = 0)
Copy the code
Of course, this command is very helpful when you are debugging certain memory leak problems.
3. Control the application execution process
process
, breakpoint
, thread
This section is my favorite, because with these commands in the LLDB (especially the Breakpoint command), you can automate a lot of routine tasks while debugging, which can greatly speed up your debugging.
With Process you can basically control the debugging process and link to specific targets or stop the debugger. But because Xcode already does this for us automatically (Xcode connects to LLDB whenever a target is run). I won’t talk too much here, but you can read how to connect to a target from a terminal in this Apple guide – “Using LLDB as a Standalone Debugger”.
Using Process Status, you can know where the debugger is stopped:
(lldb) process status
Process 27408 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
frame # 0: 0x000000010bbe4889 LLDB-Debugger-Exploration`ViewController.viewDidLoad(self=0x00007fa0c1406900) -> () at ViewController.swift:6966, 67,let a = 2, b = 2
68 let result = valueOfLifeWithoutSumOf(a, and: b)
-> 69 print(result)
70
71
72
Copy the code
To continue target execution until the next breakpoint is reached, run this command:
(lldb) process continue(LLDB) c // Or just type"c"This is the same as the previous commandCopy the code
This command is equivalent to the “continue” button on the Xcode debugger toolbar:
The breakpoint command allows you to manipulate breakpoints in any way possible. Let’s skip the most obvious commands: breakpoint enable, breakpoint disable, and breakpoint delete.
First, to view all of your breakpoints you can use the list subcommand in the following example:
(lldb) breakpoint list
Current breakpoints:
1: file = '/Users/Ahmed/Desktop/Recent/LLDB-Debugger-Exploration/LLDB-Debugger-Exploration/ViewController.swift', line = 95, exact_match = 0, locations = 1, Resolved = 1, Hit count = 1where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.valueOfLifeWithoutSumOf (Swift.Int, and : Swift.Int) -> Swift.Int + 27 at ViewController.swift:95, address = 0x0000000107f3eb3b, resolved, hit count = 1
2: file = '/Users/Ahmed/Desktop/Recent/LLDB-Debugger-Exploration/LLDB-Debugger-Exploration/ViewController.swift', line = 60, exact_match = 0, locations = 1, resolved = 1, Hit count = 1where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.viewDidLoad () -> () + 521 at ViewController.swift:60, address = 0x0000000107f3e609, resolved, hit count = 1
Copy the code
The first number in the list is the ID of the breakpoint by which you can refer to the specified breakpoint. Now let’s set some new breakpoints in the console:
(lldb) breakpoint set -f ViewController.swift -l 96
Breakpoint 3: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.valueOfLifeWithoutSumOf (Swift.Int, and : Swift.Int) -> Swift.Int + 45 at ViewController.swift:96, address = 0x0000000107f3eb4d
Copy the code
In this example -f is the file name of the breakpoint you want to place, and -l is the line number of the new breakpoint. A more concise way to set the same breakpoint is to use shortcut B:
(lldb) b ViewController.swift:96
Copy the code
Similarly, you can set breakpoints with the specified re (such as the function name) using the following command:
(lldb) breakpoint set-- fun-regex valueOfLifeWithoutSumOf (LLDB) b -r valueOfLifeWithoutSumOf // Simplified version of the previous commandCopy the code
Sometimes it is useful to set a breakpoint to hit only once, and then instruct the breakpoint to delete itself immediately. Of course, there is a command to handle this:
(lldb) breakpoint set --one-shot -f ViewController.swift -l 90
(lldb) br s -o -f ViewController.swift -l91 // A simplified version of the previous commandCopy the code
Now we come to the most interesting part – automated breakpoints. Did you know that you can set a specific action to be executed when a breakpoint stops? Yes, you can! Would you use print() in your code to get the values you’re interested in while debugging? Please don’t do that again. Here’s a better way. 🙂
With the breakpoint command, you can set up the command to execute correctly when a breakpoint is hit. You can even set breakpoints that are “invisible” without breaking the process. Technically, these “invisible” breakpoints actually break execution, but you won’t notice them if you add the “continue” command to the end of the chain.
(lldb) b ViewController.swift:96 // Let's add a breakpoint first Breakpoint 2: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.valueOfLifeWithoutSumOf (Swift.Int, and : Swift.Int) -> Swift.Int + 45 at ViewController.swift:96, Address = 0x000000010c555b4d (LLDB) breakpoint command add 2DONE'to end. > p sum // Print the variable "sum" value > p a + b // run a + b > DONECopy the code
To make sure you add the correct command, use the breakpoint command list
subcommand:
(lldb) breakpoint command list 2
Breakpoint 2:
Breakpoint commands:
p sum
p a + b
Copy the code
When the next breakpoint hits, we should see the following output on the console:
Process 36612 resuming
p sum
(Int) $R0 = 6
p a + b
(Int) $R1 = 4
Copy the code
That’s great! This is exactly what we want. You can make execution smoother by adding the continue command at the end of the chain so that you don’t stop at the breakpoint.
(lldb) breakpoint commandAdd 2 // Prepare some command Enter your debuggercommand(s). Type 'DONE'To end. > p sum // Prints variables"sum"// Run a + b >continue// Restore after the first hit > DONECopy the code
The result would be this:
p sum
(Int) $R0 = 6
p a + b
(Int) $R1 = 4
continue
Process 36863 resuming
Command #3 'continue' continued the target.
Copy the code
With thread and its subcommands, you have complete control over the execution flow: step-over, step-in, step-out, and continue. These commands are equivalent to the flow control buttons on the Xcode debugger toolbar.
The LLDB also has predefined shortcuts for these special commands:
(LLDB) Thread step-over (LLDB) next // and"thread step-over"The command has the same effect (LLDB) n // and"next"The command has the same effect. (LLDB) Thread step-in (LLDB) step // and"thread step-in"The command has the same effect (LLDB) s // and"step"Commands have the same effectCopy the code
To get more information about the current thread, we simply call the info subcommand:
(lldb) thread info
thread # 1: tid = 0x17de17, 0x0000000109429a90 LLDB-Debugger-Exploration`ViewController.sumOf(a=2, b=2, self=0x00007fe775507390) -> Int at ViewController.swift:90, queue = 'com.apple.main-thread', stop reason = step in
Copy the code
To see all active threads, use the list subcommand:
(lldb) thread list
Process 50693 stopped
* thread # 1: tid = 0x17de17, 0x0000000109429a90 LLDB-Debugger-Exploration`ViewController.sumOf(a=2, b=2, self=0x00007fe775507390) -> Int at ViewController.swift:90, queue = 'com.apple.main-thread', stop reason = step in
thread #2: tid = 0x17df4a, 0x000000010daa4dc6 libsystem_kernel.dylib`kevent_qos + 10, queue = 'com.apple.libdispatch-manager'
thread #3: tid = 0x17df4b, 0x000000010daa444e libsystem_kernel.dylib`__workq_kernreturn + 10
thread # 5: tid = 0x17df4e, 0x000000010da9c34a libsystem_kernel.dylib`mach_msg_trap + 10, name = 'com.apple.uikit.eventfetch-thread'
Copy the code
Honorable mention
command
, platform
, gui
In the LLDB you can find a command to manage other commands, which sounds strange, but is actually a very useful gadget. First, it allows you to execute LLDB commands from a file, so you can create a file with some useful commands that you can then immediately allow as if they were a single command. Here is a simple example of what is said about files:
Thread info // Displays information about the current thread br list // Displays all breakpointsCopy the code
Here’s what the actual command looks like:
(lldb) command source /Users/Ahmed/Desktop/lldb-test-script
Executing commands in '/Users/Ahmed/Desktop/lldb-test-script'.
thread info
thread # 1: tid = 0x17de17, 0x0000000109429a90 LLDB-Debugger-Exploration`ViewController.sumOf(a=2, b=2, self=0x00007fe775507390) -> Int at ViewController.swift:90, queue = 'com.apple.main-thread', stop reason = step in
br list
Current breakpoints:
1: file = '/Users/Ahmed/Desktop/Recent/LLDB-Debugger-Exploration/LLDB-Debugger-Exploration/ViewController.swift', line = 60, exact_match = 0, locations = 1, resolved = 1, Hit count = 0where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.viewDidLoad () -> () + 521 at ViewController.swift:60, address = 0x0000000109429609, resolved, hit count = 0
Copy the code
Unfortunately, you can’t pass any parameters to the source file (unless you create a valid variable in the script file itself).
If you need more advanced functionality, you can also use the script subcommand, which allows you to manage Python scripts (add, delete, import, and list) with custom scripts that are truly automated with script commands. Please read this excellent tutorial Python Scripting for LLDB. For demonstration purposes, let’s create a script file script.py and write a simple command print_hello() that prints “Hello Debugger! “:
import lldb
def print_hello(debugger, command, result, internal_dict):
print "Hello Debugger!"
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f script.print_hello print_hello'// Controls the initialization of the script and adds commands from this moduleprint 'The "print_hello" python command has been installed and is ready for use.'// Print to confirm that everything is okCopy the code
Next we need to import a Python module and start using our script commands normally:
(lldb) command import ~/Desktop/script.py
The "print_hello" python command has been installed and is ready for use.
(lldb) print_hello
Hello Debugger!
Copy the code
You can quickly check the current environment using the status subcommand, which tells you the SDK path, processor architecture, operating system version and even a list of devices supported by the SDK.
(LLDB) platform Status Platform: ios-simulator Triple: x86_64-apple-macosx OS Version: 10.12.5 (16F73) Kernel: Darwin Kernel Version 16.6.0: Fri Apr 14 16:21:16 PDT 2017; Root :xnu-3789.60.24~6/RELEASE_X86_64 Hostname: 127.0.0.1 WorkingDir: / SDK Path:"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk"
Available devices:
614F8701-3D93-4B43-AE86-46A42FEB905A: iPhone 4s
CD516CF7-2AE7-4127-92DF-F536FE56BA22: iPhone 5
0D76F30F-2332-4E0C-9F00-B86F009D59A3: iPhone 5s
3084003F-7626-462A-825B-193E6E5B9AA7: iPhone 6
...
Copy the code
You can’t use LLDB GUI mode in Xcode, but you can always use LLDB GUI mode from a terminal.
GUI // If you try to execute this GUI command in Xcode, you will see this error: the GUIcommandRequires an interactive terminal.Copy the code
This is what the LLDB GUI mode looks like.
Conclusion:
In this article, I have only scratched the surface of the LLDB. Even though the LLDB has been around for a few years, there are still many people who haven’t realized its full potential. I just gave an overview of the basic approach and talked about how LLDB automates debugging steps. I hope this will be helpful.
There are many LLDB methods that are not covered, and there are some view debugging techniques that I haven’t covered. If you’re interested in these topics, please leave a comment below and I’ll be more than happy to write about them.
I highly recommend that you open your terminal, start LLDB, and just type help to show you the complete document. You can spend hours reading, but I guarantee it will be a reasonable investment of time. Because knowing your tools is the only way engineers can really produce.
- LLDB official website – you will find all llDB-related material here. Documentation, guides, tutorials, source files and much more.
- LLDB Quick Start Guide by Apple — Again, Apple provides good documentation. This guide will help you get started with LLDB quickly, but of course they also describe how to debug LLDB without Using Xcode.
- How Debuggers Work: Part 1 — Basics — I really like this series of articles, it’s a good overview of How debuggers actually work. This article covered all the basic principles to follow when writing hand-written debugger code in C. I strongly encourage you to read all parts of this excellent series (Part 2, Part 3).
- WWDC14 Advanced Swift Debugging in LLDB — this is a great overview of Debugging in LLDB with Swift. It also shows how LLDB can help you become more efficient by implementing complete Debugging with built-in methods and features.
- LLDB Python Scripting – This guide To LLDB Python Scripting will get you started quickly.
- Dancing in the Debugger. A Waltz with LLDB– An introduction to some of the basics of LLDB, some of which are a bit out of date (e.g
(lldb) thread return
Command). Unfortunately, it cannot be used directly for Swift, as it carries some potential pitfalls with reference counting. Still, this is a good article to start your LLDB journey with.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.