Examining the Call Stack
While a program is running, it stores information about what it’s doing in a data structure known as a call stack. Each time a method is called, the program pushes a new stack frame on top of the call stack, which contains the following: the arguments passed to the method, if any, the local variables of the method, if any, and the address to return to after the method call finishes.
When the program stops at a breakpoint, you can interact with the debugger to examine the state of the current stack frame. This allows you to reason about the behavior of a method and how it interacts with other parts of a program.
In addition to getting information about the current stack frame, you can interact with the debugger to examine the entire call stack of the current thread, and of other threads used by the program.
Getting Information About the Current Frame
By entering the frame info
command, you get the location of the current frame in code, including the source file and line number.
(lldb) frame info |
frame #0: 0x0000000100001277 Validation`(string="Sw0rdf!sh") -> Bool).(containsSymbol #1)(String) -> Bool + 23 at Validation.swift:8 |
Inspecting Variables
You use the frame variable
(f v
) command to get a list of all the variables in the stack frame.
(lldb) frame variable |
(String) name = "Anton" |
(Greeter.Greeter) self = 0x0000000100502920 { |
acquaintances = ([0] = "Anton") |
} |
To get information about an individual variable, you use the frame variable
command, passing the variable name as an argument.
(lldb) frame variable name |
(String) name = "Anton" |
Evaluating Expressions
One of the most powerful features of LLDB is the ability to evaluate expressions from within a debug session.
In the example used in the Quick Tour of LLDB chapter, the expression
(e
) command is used to modify the state of a stored property to change the final output of the program.
(lldb) expression -- acquaintances.insert("Mei") |
(lldb) expression -- acquaintances.remove("Anton") |
(String?) $R1 = "Anton" |
The expression
(e
) command evaluates the passed argument as an expression in the target language. For example, when debugging a Swift program, you can evaluate Swift code as if it were a read-eval-print (REPL) loop in the context of the current stack frame. This is a powerful way to introspect variables at different points during execution.
Printing Modes
When inspecting values during a debug session, it is important to understand the difference between the frame variable
and expression
commands.
|
|
|
---|---|---|
Does not run code | Runs your code | Runs your code |
Uses LLDB formatters | Uses LLDB formatters | Adds code to format objects |
Using the frame variable
(f v
) command doesn’t run any code, and therefore does not produce any side effects. Accessing a property or evaluating the result of calling a method can often change the state of a program in a way that obscures the problem that you are attempting to debug, so using the frame variable
(f v
) command is the safest option for cursory investigation.
The expression
command is aliased to both p
and po
, which are frequently used operations when debugging. The difference between the two is that p
uses the built-in LLDB data formatters, whereas po
calls code provided by the developer that creates a representation of that object, such as the debugDescription
method in Swift. If no custom representation is available, the po
command falls back on the representation provided by the p
command.
Getting a Backtrace
A backtrace is a list of the currently active function calls. By using the thread backtrace
(bt
) command, you can reason more clearly about the chain of events that caused the program to be in its current state.
Calling the thread backtrace
command with no arguments produces a backtrace of the current thread.
(lldb) thread backtrace |
* thread #1: tid = 0x1288be3, 0x0000000100001a98 Greeter`Greeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5, queue = 'com.apple.main-thread', stop reason = step in |
frame #0: 0x0000000100001a98 Greeter`Greeter.hasMet(name="Anton", self=0x0000000101200190) -> Bool + 24 at Greeter.swift:5 |
* frame #1: 0x0000000100001be4 Greeter`Greeter.greet(name="Anton", self=0x0000000101200190) -> () + 84 at Greeter.swift:9 |
frame #2: 0x00000001000019eb Greeter`main + 155 at Greeter.swift:20 |
frame #3: 0x00007fff949d05ad libdyld.dylib`start + 1 |
frame #4: 0x00007fff949d05ad libdyld.dylib`start + 1 |
You can pass an integer as an argument to the thread backtrace
command to limit the number of frames that are displayed.
Alternatively, calling the thread backtrace
command with all
as the argument produces a full backtrace of all threads.
Listing Threads
Programs often execute code across multiple threads. To get a list of all of the current threads in a process, you use the thread list
command.
(lldb) thread list |
Process 96461 stopped |
* thread #1: tid = 0x1384af1, 0x000000010000111f main`sayHello() -> () + 15 at main.swift:2, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13