Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LLDB fails to examine variables (in Xcode)

Tags:

xcode

swift

lldb

In particular the print command typically (80-90% failure rate) does not work

I've verified already: https://developer.apple.com/library/content/qa/qa1947/_index.html

Example 1

(lldb) p prevMsg

error: Couldn't materialize: couldn't get the value of runOnce: extracting data from value failed error: errored out in DoExecute, couldn't PrepareToExecuteJITExpression

Example 2 A more typical example that puts you in a stone age of computing:

(lldb) p activeNetworkRequests

error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x1700530). The process has been returned to the state before expression evaluation.

This seems to have gotten progressively worse since Xcode 7.

Printing variables scoped from the enclosing function of a closure are particularly hopeless.

The code base is not small, about 15K lines. It would not be practical to isolate and reproduce all the code here.

Surely others are experiencing this?

UPDATE: I'm told about the merits of expression --unwind-on-error=0 -- variable-in-question, presumably for example2

UPDATE 2:

Code:

Util.log("Returning \(key) from file cache", [.Caches])

Output:

08:03:11.201 v2.0.64d other TwoStageCache.swift objectForKey(_:completion:)[95]: Returning https://example.server.com/Storage/Retrieve?FileName=accounts/[email protected]/resource/47a58660-26d1-11e7-8e7f-c9f4cd679b03.html from file cache

(So the value of key is fine)

(lldb) fr var key
(URL) key = unable to read data
(lldb) print key
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x1d787583).
The process has been returned to the state before expression evaluation.

If we look at the crash:

(lldb) expression --unwind-on-error=0 -- key

libobjc.A.dylib`objc_retain:
    0x22562b0 <+0>:  pushl  %ebp
    0x22562b1 <+1>:  movl   %esp, %ebp
    0x22562b3 <+3>:  subl   $0x8, %esp
    0x22562b6 <+6>:  calll  0x22562bb                 ; <+11>
    0x22562bb <+11>: popl   %ecx
    0x22562bc <+12>: movl   0x8(%ebp), %eax
    0x22562bf <+15>: testl  %eax, %eax
    0x22562c1 <+17>: je     0x22562e1                 ; <+49>
    0x22562c3 <+19>: movl   (%eax), %edx
->  0x22562c5 <+21>: testb  $0x2, 0x10(%edx)

From:

1 $__lldb_expr(UnsafeMutablePointer<Any>) -> ()
2 Beta Viewer`@objc AppDelegate.init() -> AppDelegate:
3 sharedEnchantment`partial apply for TwoStageCache.(objectForKey(URL, completion : (imgData : Data?, err : BBError?) -> ()) -> ()).(closure #1)
4 sharedEnchantment`thunk:
like image 314
Anton Tropashko Avatar asked May 04 '17 13:05

Anton Tropashko


People also ask

Does Xcode use LLDB?

Xcode uses the LLDB as the default debugging tool. The full form of LLDB is Low-level debugger. Breakpoints help a developer to stop the execution of the program at any point.

How do I enable debug mode in Xcode?

When you run an application in Xcode, the debugger is automatically started and attached to the process of the application. Click the Run button in the top left or press Command + R. From the moment the application is up and running, we can start inspecting the process and, if necessary, debug it.

How do you set a breakpoint in LLDB?

In lldb you can set breakpoints by typing either break or b followed by information on where you want the program to pause. After the b command, you can put either: a function name (e.g., b my_subroutine ) a line number (e.g., b 12 )

What is LLDB in Swift?

LLDB is a high-performance debugger and next generation of GNU Debugger. It contains a set of libraries, such as Clang expression parser and LLVM disassembler. LLDB is the system debugger on macOS, iPadOS, iOS, tvOS, and watchOS. It can also be used for Objective-C and Swift development for architectures: x86_64.


1 Answers

Sorry in advance for the essay, but hopefully the info will be worth the read...

lldb has two ways of looking at variables(*): print and frame variable.

print isn't really meant primarily for printing variables - that's just a side effect of what it really does. print is an alias for expression which gives you a little more sense of what it is: a full expression evaluator which runs the expression you pass at the point where you are stopped in your code.

It builds a context that emulates the code at the current pc (including the Class/Protocol context) and then takes the snippet you pass it, compiles it in that context, JIT's the result, inserts the JIT'ed code into the process you are debugging and runs it. That's quite powerful - you can change values, call functions in your program, introduce new functions, new types, etc. But there is also a lot of machinery just to get it going, and with swift some of this machinery is tricky to get right.

frame variable can only print locals and arguments in the current frame (with the -g flag it can also print globals & statics). It can't call functions, or any of the other fancy things print can do. It does understand a limited subset of the variable access syntax, so:

(lldb) frame variable foo.bar.baz

will work. But under the covers, all it needs to do is read the debug information to find the variable, its type, and where it is in memory, and then it can extract the value from that information. So it is faster and more robust for what it does do - which is a large percentage of the work people generally ask print to do.

Note, you can get "object printing" for variables you access with frame variable by using the -O flag, and it supports the same formatting options for results as print. For context, the Xcode "Locals" view is roughly equivalent to calling frame variable.

I tend to use frame variable for simple locals printing, but even if you like to use one command for all your needs - which will be print - it's good to know that there's a fallback if print is failing for some reason.

Back to your examples...

Example 1: one of the things print does in Swift is introduce all the visible local variables into the context of the expression, so they are available to your code. The error in Example 1 is because one of the local variables couldn't be realized - maybe it was a only specified by a protocol conformance and we couldn't figure out what it really was - so we failed building the context, which means the parse or JIT steps failed. The print code does a pre-scan for this sort of failure and omits failing locals, but you've found a case this scan misses.

frame variable would have probably also failed to print runOnce but since it doesn't depend on the current context, the inability to do that wouldn't have affected your ability to print other variables.

If you can reproduce this issue, even if you can't make the project available to us we can often figure out what's going on from lldb's debugging log. So drive the debug session to the point where the print is going to fail, and do:

(lldb) log enable -f /tmp/lldb-log.txt lldb expr types

then run the failing expression. Then grab that log, and file a bug as described here:

https://swift.org/contributing/#reporting-bugs

Example 2: Was activeNetworkRequests a property? Those require us to call the "get" method to access them, and I have seen a few cases where lldb doesn't emit the code to call the property getters correctly. The log above will show us the code that was emitted, and we might be able to tell from there what went wrong. Of course, if you can make a test case you can send with the bug that is always best, but that's often not possible...

(*)For gdb users this is pretty close to the info locals vrs. print...

like image 85
Jim Ingham Avatar answered Oct 13 '22 00:10

Jim Ingham