Enhancements to LLDB simplify life for Swift and Objective-C developers alike. Experienced developers will find new powers within the debugging console, and learn more about the connection between the debugger and Swift REPL. Newcomers will get insight into the range of possibilities hidden behind the LLDB prompt.
KATE STONE: Good afternoon and welcome to What's New In LLDB. My name is Kate.
KATE STONE: And I manage the runtime analysis tools in Xcode, including LLDB. I will be getting us started here but I'll be bringing up two of my engineers, Sean and Enrico, to dive into the details in just a little bit.
But to get us started, I would like to talk a little bit about the year's highlights, some of the changes, big and small, since the last time we were at WWDC.
Most notably we shipped our first Swift debugger. It is a step forward, obviously, in being able to write and develop the Swift code that you have come to know and love.
But more importantly it wasn't our last step. We continued to improve that debugger and we hope your continued feedback will help us make it even better still. At the same time we shipped the Swift REPL; it's a little bit different than your standard request response REPL environment, in that it is really LLDB in disguise.
The REPL is not just a way that you can ask questions in Swift. It is a way that you can debug those questions, you can actually set breakpoints in the REPL and do everything you'd expect from a debugging environment because it is LLDB. If you haven't dug deep into that, I'd recommend you take a look at the blog posts on the subject.
But, of course, that's not the end. There are numerous improvements that we have made since then. Since we first shipped, we've made over 100 improvements in the Swift debugging experience, and we continue to enhance the debugging experience for Objective-C as well. We're going to cover some of them here, but just to recap some of the changes that have been made since last year, Swift types initially did not show inherited Objective-C fields. We've since corrected that.
Help now includes command aliases. And it is important to understand these, because while LLDB has what may look like a wordy syntax, we might ask you to type 'help, in practice there are a number of short cuts. And throughout this presentation we'll use the following notation to describe them.
Help can actually be abbreviated to the first unique sequence. So in this case H is enough to bring up help.
More importantly, for more elaborate examples like expression-O-- , which means 'evaluate an expression, tell me the results as if it were an object by sending it a method to describe itself, and then no more options followed by the expression, but you can just type PO. PO is a handy alias that is shorthand for everything to the left.
So, as you get used to the help, you should learn to use the aliases, because they are going to be your fastest way to get to some of the powerful functionality in LLDB. We've also improved data formatting, because telling you about your data is absolutely critical to your debugging experience. So types like set and NS Index Path now have default formatting to help you understand your data instead of just showing you the raw dump of the underlying representation.
Printf prototype for expressions, if you've ever tried to evaluate an expression involving printf, you may have seen some slightly suspect results. That's because the debugger tries to make a lot of assumptions about your expression for any declaration that it doesn't know, and it didn't know printf, in most cases. So it didn't know that it was a variatic function, that is, one that can take a variable number of arguments, and didn't know especially on 64-bit devices how to pass those arguments correctly. That has been corrected so that all of your expressions involving printf for C and Objective-C should just work right out of the box. And then lastly, and truly lastly, this time, we have improved the disassembly format to make it easier than ever to read. Digging into some of the more interesting enhancements, breakpoint enhancements were made this spring that you may not be aware of. Specifically you can now name breakpoints. And it may not seem obvious why a named breakpoint would be easier to use than any other breakpoint, until you realize that those names don't have to be unique and that you can apply multiple names to a single breakpoint. You can think of them a lot like tags. And all breakpoint commands take those names. So I can set a breakpoint by providing breakpoint set and dash N, capital N followed by a name, optionally followed by another dash capital N and additional name, and so forth.
But more importantly, once I've given a breakpoint or multiple breakpoints a name, I can then operate on them with all the other commands. Breakpoint enable name will enable all breakpoints that share that name or, again, think of it as that tag. That is made even more interesting by the fact that we now allow you to set breakpoints in LLDBinit.
If you're not familiar with LLDBinit, this is a filename starting with a prefix with a period that goes in your home directory that tells LLDB, here are a bunch of commands to execute every time I start an LLDB session.
So if you use these, you can create a set of default breakpoints when LLDB starts, and all breakpoints you set before you actually create a target are inherited by every target you create.
So combine these two, and I can write an LLDBinit along these lines. A set of breakpoints, -n everything named malloc, a breakpoint -n everything named free, name all of those breakpoints memory, and then disable them. And what this gives me is a handy way to get all of my memory handling breakpoints just by typing 'breakpoint enable memory' in any session that I use thereafter. So, you'll have your own set, I'm sure, of handy breakpoints that you always want to have access to and now you can give them easy to remember names. But, of course, you are here not just to learn about things that we have done and already shipped, but the things we are just starting to preview. Xcode 7, really important release, a lot of improvements here. An expression evaluation, notably. And Sean is going to talk to you a lot more in depth about this. Swift 2 support, because, of course, the language continues to evolve, as well as Objective-C support in terms of advanced handling for modules.
Some of this we'll cover in depth, but some of it is really just behind the scenes. If you go digging, you may notice, for example, that in Xcode 7, because we know about modules, we can actually build debug information for the module once and then not replicate it everywhere else. This allows us to drastically decrease the size of debug information and improve the performance of compilation. Once you get into actual .dSYM file, that .dSYM file will then contain absolutely everything you need. But it also contains some handy other optimizations, like not duplicating information for C++ types, thanks to the one definition rule, whereas before we may have had multiple copies. In fact, we have seen debug information winding up a sixth the size that it was in Xcode 6, especially for C++ projects. We have also improved the data formatting in a variety of ways. Vector types now get unique automatic data formatting both for Objective-C and Swift. And, perhaps most importantly, if you have custom types in Swift, you can now customize the way they are presented just by writing Swift code. And Enrico will talk about this in depth. We have integrated support for address sanitizer into LLDB. So not only will address sanitizer tell you when you reference memory that is invalid, but you can ask questions about memory. You can ask, for example, for the history of memory to see where it was allocated or when it was deallocated, right from the LLDB console, so the memory family of commands are ones you might want to dig into and look at the help for. We've also added type lookup commands. The type lookup command will allow you to get information about any type in the system. Basically it's a header-like representation that you can get right there in the debugger to remind you about the contents of the type.
So, from the LLDB prompt, all I need to do is type 'type lookup' followed by a type name and I'll get a quick description of that type. In this case the new error type that's used by Swift's error handling mechanism. I see that behind the scenes we have a pair of properties normally implemented for you so long as you're using a EDAM type, but nonetheless you can see the details right from the console. Similarly if I'm interested in a type like 'Comparable,' a protocol. It will tell me that this protocol actually derives from two others, so that you will see that Equatable is where we get the equality operator. The underscore Comparable is where we get the basic less-than and then the derived operators are part of the Comparable protocol. So there's a lot of convenient information you can get here. And like everything else, remind you that you don't have to type the full command 'type lookup.' TY space L is sufficient to use this command. But to dive into more depth, especially to talk to us about how we evaluate expressions, I would like to invite up Sean Callanan .
SEAN CALLANAN: Hello.
I want to tell you about compilers in LLDB. Now you may be saying to yourself there are lots of compiler sessions. Why do I care about compilers? Compilers are a critical part of LLDB.
They are what makes LLDB powerful, and they are what makes LLDB easy to use. It is really powerful because compilers have a unique understanding of the way your program works, the way it lays out its data, and what you mean when you say I want to look at this variable or call this function. Another reason the compilers are so important is because they make it easy to work with the debugger.
If you want to print something you just use the code you are used to typing, pass it to the expression command, and the compiler takes over from there.
So today I want to tell you about the improvements we've made in two important areas related to our compiler integration.
First of all, I've got some great news for you long-time Objective-C developers.
And then I'm going to tell all you Swift developers, whom I hope is all of you at this point, about the improvements we've made there, too. Let's get started on Objective-C. Now, LLDB contains two separate compilers.
Clang, a powerful Objective-C compiler and, of course, since last year, the Swift compiler. Our Objective-C compiler support has been improving over the years and we have been adding great new features.
For example, the Objective-C runtime integration.
So if there's some information in the Objective-C runtime that isn't present in your debug information, we know to feed that to the compiler so that you can use lots of classes without having to do anything special. Last year, of course, we introduced the Swift compiler into LLDB. And the Swift compiler already is a very powerful tool. And we've used a lot of lessons that we learned from integrating the Swift compiler, and we are improving them both together.
Let's talk about how the expression parser works with Swift, with a view of Objective-C. Let's look at a simple print command.
Now I say print here because I'm using the p shortcut.
What that actually means is expression.
Notice the dash dash.
That means everything after the p command has to be code. You can't pass extra options to the command this way. There are other ways, as Enrico will tell you, and as Kate showed you before.
Now here is some simple code that runs through a loop and prints the loop counter each time.
And, indeed, if you run it you get the numbers out that you would expect.
Why is this cool? Well, LLDB and your program are separate processes.
LLDB has a swift compiler inside it.
Your program is already running, but with LLDB's help, the Swift compiler can inject the code that you just typed into your program to run. That's kind of cool if you like printing loop counters.
But there's a little bit more to debugging. Swift also works with your variables. You type in some code. And the contents of an array are printed.
Now, this array here is just a piece of data that is in your program.
And LLDB arranges to show that data to the compiler so that it can generate the code that you expect.
There is another thing I want to talk about really quickly. And that is how Swift works with the SDK.
When you type an expression like NSApplication.sharedApplication, what happens is, first of all you see the NSApplication.sharedApplication, the way you would expect.
But what LLDB is doing is going out and finding the SDK module that contains that, giving the compiler access to it, then the compiler finds NSApplication and figures out how to use sharedApplication.
That's all great and it is automatic in Swift.
In Objective-C it doesn't always work.
So let's try all these things in Objective-C and some of you may be getting a little bit apprehensive at this moment, because you know that trying to NSLog something isn't as easy as it sounds in the debugger. Well, in the past when you've typed in NSLog, you've seen errors like this: NS log has unknown return type.
That's because in the SDK, sure enough, there's a definition of NSLog. But what LLDB sees is only what's in the debug information in the symbols. In this case, all it sees is a symbol.
That symbol, we don't know what its return type is and we don't even know that it takes a format string.
Well, the good news is, we fixed that .
SEAN CALLANAN: NSLog works the way you would expect.
Now, let's try something a little bit more underhanded.
Wait a second! Why is this underhanded? It's right there in the frameworks. Unfortunately it looks like this identifier doesn't even exist.
Now, you may know that if you use NSLog and you cast the result, yeah, you can use that.
But with NSMakeRect you can't even do that.
That's because NSMakeRect is defined NS-Inline. There is no symbol for it. What LLDB sees is nothing.
No problem anymore.
SEAN CALLANAN: Just one more pain point left.
Now let's look at the old NSApplication sharedApplication case. Sure, if you ran that expression by yourself, yes, it would work in Objective-C. I mean, we have been working on the Objective-C runtime integration.
But if you try to get out the undo.Manager you quickly see that the runtime doesn't tell you everything.
In particular you get this weird error about undo.Manager not being found on an object of type id.
What is going on there? In the SDK, sure enough, sharedApplication returns an NSApplication star. But if you look at the runtime, what it returns is id, a generic Objective-C object. Great news. That is no longer a problem. In fact we see information that we can only find out from the SDK such as that that pointer is nullable. This is some of the great new SDK support for Swift that just bubbles down into Objective-C as well.
But the information is right there. And that's the philosophy that we've applied here.
Read straight from the SDK.
Your code has always worked in LLDB. We knew about local variables, functions, your own classes.
SDK functions, on the other hand, we had a little bit more issues with, as you see.
SDK classes like NSView and NSApplication, yeah, we saw them but, as you see, because of the runtime integration, there was a little bit of an asterisk there.
Now, SDK constants, if you ever tried using NSASCIIStringEncoding in an expression, you know that never flew.
And if you tried using macros like int-max, and, you know, max, to take the largest of two numbers, that wasn't going anywhere either.
Well, all of that is fixed in the latest LLDB.
SEAN CALLANAN: And we got rid of the annoying asterisk, too . SEAN CALLANAN: Now, you may say, how many easy monthly payments of 39.95 do I need to make to get this functionality? Well, good news, it is all free and the way you do it is @import AppKit.
You just run an expression that says import the frameworks I care about, if you pull in AppKit, or for the two or three iOS programmers among you, import UIKit, this works. Now, we haven't been standing still with Swift either. Swift 2.0 has great error handling support and LLDB is right off at the bat supporting that just the way you would expect. We can handle Swift errors and you don't need to call 'try' when you are calling functions that could throw errors in expressions.
The reason is because we catch stuff for you. If you type this function 'throws' and pass that to an expression, notice this is the same as the p command.
You will get an error variable created for you that contains the error that that function threw. You can also do this in the REPL.
If from the REPL prompt, you do the same thing, you get an error variable.
Now, let's look at a little bit more detail of LLDB support. And that is, you don't always want to see what the resulting error was.
You want to know what was the code that threw that error.
Well, the way you've done this in Objective-C is you used breakpoints.
Specifically you set breakpoints on Objective-C exceptions.
The way you did this was you used the breakpoint set command. You specify that you want to set the breakpoint for the Objective-C exception, and we set that breakpoint for you. Now whenever your Objective-C code tries to throw an exception, we'll stop.
You can do the same thing with Swift errors.
Just replace the Objective-C with Swift and we'll stop whenever your program wants to throw a Swift error.
But there's another cool thing that you can do. And that is you can stop on specific types of errors.
This is something we support in Swift.
And the way you do it is very simple, it's very similar to the way you would set an expression breakpoint anyway.
You use the dash O parameter, which specifies the type name of the error you want to catch. Now if you do that, you will stop only when the type of error you're interested in would be thrown. Now, finally, of course you can still catch errors the way you would in normal code. After all, the REPL is meant partially as a way to learn and explore the way the language works.
If you import Foundation to get an NSError and then you write some code that throws an NSError, you can catch that NSError and print it.
If you do that, the output will be exactly the same as if you had caught it in your own program. So I hope you go out of this remembering two things. First of all, add import your modules, and second of all, try playing around with error handing in LLDB. It's a great place to do it. Now, for much more detail, not about how to tell your program what to do, but how to print the information your program produces after it's done it, I would like to call up Enrico Granata .
ENRICO GRANATA: Hi! I was in the Labs this morning. And we were trying to look through a problem and one thing that came up is, why can I not see this variable? What's up with that? And in order to help us figure that out, we tried several commands. We tried expression, we tried PO, we tried frame variable. And people usually at this point ask, why do you have so many commands to do pretty much the same thing? Look at my data, see what is happening in my program. Well, all of you here get the insider scoop now. Let's look at the commands that LLDB has to let you look at data. There's three of them. Frame variable, the expression command, which is the p command, the expression dash uppercase O, which as you've been told before, you know as the po command.
First one, frame variable command.
The frame variable command which you can shorten as frv when you type it, is pretty much the Xcode variables view. It lets you look at all your local variables.
It lets you look at only a few of your local variables, and optionally you can also apply formatting with the dash dash format flag. One thing I want to highlight, because I will get back to it, is when you see that Tuple in the first output, that is an aggregate, that is an object that contains other objects. The things that are within that aggregate, we call them children. The expression command is the command you've seen a lot in this session. I'll be really brief. Of course, with the expression command, you can do simple arithmetic, as you'd guess.
It is totally possible to use previous results and actually do more stuff with them. And of course, the expression command also knows how to do custom formatting of your results.
One thing I want to point out, once again, children.
Third command, the po command.
This is probably the command that all of you who are Objective-C developers, I'm guessing quite a few prior to Objective-C code, and you know the po command. You can create objects and get their description printed out.
You can just create an NSArray or print the existing NSArray and you'll see the contents of it.
Or, guess what, it simply works for a string.
So, three commands. They don't all do exactly the same thing, as you probably guessed by now.
Actually, they are somewhat similar and not at all identical. For instance, the expression command and the po command are 'run my code' commands. Whatever code you type, these commands will run it.
But then the frame variable command and expression command do step 2 differently. When they have to show you your result, they use the LLDB formatter system.
We talked quite a bit about the LLDB formatter system in past WWDC sessions. And you should totally go check them out. But really briefly, LLDB has knowledge of some built-in system types and automatically formats them. NSArray, NSDictionaries with strings. And it is possible for you to provide your own format as written in Python.
On the other hand, the po command will not use LLDB formatters. The po command runs some more extra code behind the covers, behind the scenes, to actually produce the result it has to show you. You probably have written at least one description method in one of your Objective-C classes and then you realized, oh, that's what po prints.
Now I want you to think for a second about these two models of actually taking your object and producing data for it. The LLDB formatter's model is what we call the 'out of process' formatting model. Why? Because the formatter sits outside of your process. It is either built knowledge into the debugger or you brought some Python script to represent your object. It's different languages, different files that live in different scopes.
On the other hand, that external formatter lives in the dugger. It is really easy for that guy to get access to all the knowledge about your program that the debugger has. It's kind of like a bird's eye view of your process. Because of that, it's also really easy for this kind of formatter to make sure that your program state is not changed. You don't want to change the state of your program as a result of looking at data in the debugger. The debugger is kind of like the stage inspector. It looks at things and tries not to change them.
The other model, the po model, the 'write a description method' model is an in-process formatting model. You write your data and your formatter together, you write them in the same language. You probably even write them in the same file.
And because your formatter is just code that runs in your application, it gets easy and full access to the object model of your application.
But with great power, comes great responsibility.
You want to make sure that your formatter does not change the state of your program. You want to make sure you don't do anything in your formatter that will alter the object you're trying to represent.
Well, you say, okay, Objective-C has an in-process formatting model. I can write a description method, the debugger will pick it up.
What about Swift? Turns out, and once again, those of you that are in this room get the insider scoop, that Swift has had an in-process formatting model since the very beginning.
But, where is it? How do I use it? I hope all of you have used the Swift playground. If you have used the Swift playground, you have used the Swift in-process formatting model. It has been there since the beginning. So what is new? Now in Xcode 7, we take that very same model, we make it public API.
It is available for you to use. And it still powers the playgrounds, but now it also powers the LLDB po command. Now you've got the right Swift formatters for your Swift objects.
How? Let's look a little bit under the cover. The model is based upon four protocols. These are their names.
Yes, I said four protocols. And their names are also quite lengthy.
But I wouldn't worry too much about that. It's possible to only opt-in partially to this model. The four protocols doesn't mean you have to conform to all of them. You can choose a subset to get the result you want done. ENRICO: Conform to those, do that work, and profit.
Let's look at the protocols.
CustomStringConvertible is the protocol that tells, that tells us, how would I print my object as a string? It doesn't only tell LLDB. It also tells Swift.
How? The Swift print function, as well as the Swift string interpolation feature, both use the CustomStringConvertible protocol.
That's a lot of benefit. How hard can it be to get it? It's pretty easy. I have a data structure that represents the bottles of beer song, because I'm preparing myself for the bash. And I want to print the lyrics.
I create an instance of that, and I say, how many bottles of beer are on the wall. But that's for when everything in my app works fine. If I'm debugging it, maybe I need a little bit more information. That's where Custom Debug String Convertible kicks in.
It's a debugger specific representation of the object.
What debugger-specific means is really up to your app. It's really up to the semantics of your object model.
But, as a hint, the debugPrint function will default to choosing this protocol.
Of course, both print and debugPrint will fall back to the other conformance if their favorite one isn't there.
How to get this to work? Pretty simple.
Let's extend our bottles of beer. Because we are debugging, we want to know a little bit more about the bottles of beer on the wall, so we can just check that they are all correctly stout, and it is going to be a great bash because they all are.
Third protocol is Custom Playground Quick Lookable.
As the name implies, this protocol is specifically for playgrounds. It is meant to provide rich graphical representations of your object in a playground.
You want an example? Sure thing.
I can write a data structure that represents a person, and then I can get a depiction of a person to show up in my playground sidebar as a result of creating an object of that type.
I'm quite sorry to disappoint you guys, I really wanted to get this to happen. But, unfortunately the t-shirts with my person will not be sold at the conference. Sorry. But I have something to make up for that. The last protocol in the lot, the Custom Reflectable protocol. It allows us to invent a entirely custom children hierarchy. Yes, I said I would get back to the word 'children' and I did.
When I say an entirely custom children hierarchy, what I mean is that I can make a new -- I can make a new structure for my object. I can tell the language, I can tell the debugger, I can tell the playgrounds, this is what my object is actually made of. This is how you should see it. And the way you do this, the currency that you transact in, when you're trying to describe the structure of objects to Swift, is called a Mirror, reflectable mirror.
Let's see an example, without further ado. I have an application whose job it is to collect temperature samples. It has got a couple data structures. One that describes a moment in time, and one says, for a given moment in time, this was the temperature information I got.
And then we have got temperature samples, of course.
Now I'm debugging this app. I'm trying to see what is going on, how it's processing the samples. So I say po temperatures. And that's what I got.
Honestly, you know, I look at it and I say, it is not bad for a default.
But I immediately see a couple things I want to change about this.
Why stack time on two lines? I really wish it was all in one line. And it would be great if it was in a.m. /p.m. format, too. And that temperature, I look at it and I'm like, what scale is that in? I don't even know. Is it Kelvin, is it Reaumur, is it Rankine? We are in America and we want our Fahrenheit degrees . ENRICO GRANATA: Well, the good news is that we can fix all of these things, in two steps.
Let's go. Step one, let's get the time printed on one line.
Here is what I did to get that to happen for me. I used an NSDate formatter.
Because I got to run within my application, because the code I'm running to format this object is actually just normal Swift code, it is just code I would normally write as part of my app, whatever framework, whatever library, whatever technology my app would normally use to get this job done, I can just use it in my formatters. In this case NSDate formatter.
Step two, let's get those Fahrenheit degrees going.
How do we do it? We create a mirror. Here we are.
Now we are saying that our temperature data object is structured as a container of three things. A time, a temperature in Centigrade, and a temperature in Fahrenheit. The time is the string interpolation of the actual time data stored inside the object.
And because it is string interpolated and because we provided a custom string convertible conformance, that will be automatically picked up.
One more thing worth noticing here is that when I get both scales, when I get both Centigrade and Fahrenheit for my result printing, I don't change the value stored inside the object. Of course, you say, you don't. Well, it is actually quite a bit important to remember not to change the state of your program by writing in-process formatters.
And now that we've done all this work, now we've got all this, now how do we benefit? Well, we could try po-ing that again. And here we go. And now I look at it again. I see my object. And I can look and I see now that at 6:30 p.m. it was 93.2 degrees.
Yup, in case you are wondering, it was really hot in Cupertino yesterday.
And now we did this work but we did it because we were smart and we did the work ahead of time.
Sometimes I'm trying to debug something and I got my program just where I want it. I have got this one hard-to-reproduce bug to finally happen. But now it is really hard to look at the data. The data is confusing, it's complicated. And I wish I had done this work before, so that now I could actually look at my data in much simpler ways.
But alas I haven't. All hope is lost.
Nope! No, it isn't! You can add conformances and runtime, too. Through the expression parser you can add these conformances and run while debugging your app.
On the other hand, you cannot change them. Existing conformances stay. You are in the REPL, you're experimenting.
You really wish you could po something. Oh, how I wish I could add a conformance.
You can do that in the REPL as well. And, of course, but I'm sure you all expect that, guess what? In playgrounds, too.
Look at that! It seems to me that a lot of this work that we've done over the last year in debugger land that you've heard from Kate, from Sean, and from me, has to do with making sure that it is really easy to access as much of your information as possible while you are debugging.
Access to the Objective-C runtime gives you more insight into fields that before were unavailable to you. The SDK modules offer an unprecedented level of access to the operating environment that your application is running in, more types, more functions, even macros.
And in-process formatting, in-process formatting is a great way for you to create compelling representations of your types, that apply across the board. They apply in the playgrounds, they apply in the REPL, they apply in the debugger.
For more information, of course, do not hesitate to visit our website, Swift language docs, or Developer Forums, of course, the Labs, and Stefan, our Evangelist, is but an email away.
Thank you very much, and have a great WWDC! [ Applause ]
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.