Developer Tools • iOS, macOS, tvOS, watchOS • 59:51
Spend an hour with the team responsible for the technology behind Xcode's debugger: LLDB. Find out what's new, what's been there all along that you didn't know about, and how best to take advantage of it all. This session will cover a range of topics with tips for both newcomers and experienced developers.
Speakers: Kate Stone, Enrico Granata, Sean Callanan, Jim Ingham
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Good afternoon and welcome to debugging tips and tricks. It warms my heart to see this many people turn out for this topic. It's near and dear to my heart. My name is Kate Stone and I manage the core team that's responsible for debugging technology here at Apple. So again it's really exciting.
I hope we'll be able to bring up a number of engineers from my team to tell you things that might be novel if you're just starting but also tips that might be new to you even if you've been debugging on our platforms for the last ten years. It really is a deep area and we've got a lot of great content for you so let's get started.
Specifically we should note that LLDB is the technology that I'm talking about. If you've used Xcode for debugging, you may not be aware that behind the scenes there's this technology called LLDB, the low level debugger, providing all of the core technology, and it's not just in Xcode it's everywhere you need it.
But of course one of the more common places you'll encounter it is in Xcode in the debug console. Xcode hosts that console as part of your debugging area, and you'll see of course the variables viewed but also this LLDB console and that's a lot of what we're going to be talking about here today. That prompted and the wonderful things that you can do from that prompt.
Of course that area contains normally not just LLDB's prompt and allow to interact with the debugger but it also contains your application's output and allows you to type input to it for console based applications. That may not be the most convenient way in case your application takes advantage of other console features like moving the cursor or colors, other ANSI cursor manipulation features. So there's a new option in Xcode 8 that allows you to use a standalone terminal for your application while LLDB remains in Xcode and it's simple.
Bring up the scheme options and in the options tab at the very bottom you'll see this new console that allows you to switch from the default to use Xcode to use terminal instead. And then when you run you'll get a separate terminal for your application's input and output separate from Xcode.
[ Applause ]
For the remainder of the talk we'll be really focused on LLDB. So if you're interested in other Xcode specific features, definitely check out the two talks noted here. Of course they've past by now but the videos are there and waiting for you. You should also note that LLDB is not just part of Xcode but the Swift REPL is in fact LLDB.
When you bring up the Swift REPL and start interacting with it, you're interacting inside a debugging environment already so that the power of the debugger is there any time you need it. In fact every command that we tell you about here you can use directly from within the REPL just by prefixing it with a colon from the REPL prompt. So colon followed by command will issue the command directly to LLDB.
So let's say I'm at the Swift REPL prompt. At this point I'm interested in looking up some information on a type and of course I could bring up Xcode and look at the help, but right from the prompt here I could go ahead and simply say colon type lookup comparable. And I'll have looked up this protocol and found out that in fact it derives from another protocol and adds the following four functions.
If you're familiar with type lookup, it's a great feature you use it all the time but you may not be aware of the fact that despite the name type lookup it can actually be used to lookup a wider range of things. I can actually lookup functions to get the complete signature or even lookup entire modules, and of course this a lot of content as it will be the full declaration of every type in that module. We've abbreviated it here.
So the REPL is fantastic for these kinds of additional commands but it's also useful if you want to interact with the code you're writing in the REPL. Let's say I write a simple function here. I've written a function, I'd like to stop when I'm in the function and find out what's going on at a given line, so I can issue the breakpoint command by prefixing it with a colon, colon b 2, I set a breakpoint at line two and when I call this function, execution stops as I'd expect.
And because execution has stopped it switches immediately to the LLDB prompt and from the LLDB prompt I can issue other LLDB commands to interact with and explore my application. And the REPL will also do this automatically if you run into an overflow condition or other things that would normally terminate your application so that you can take advantage of the full power of the debugger directly from the Swift REPL.
You should also note that you can switch between the two prompts at any time. If you're at the REPL prompt a colon on its own followed by enter will bring up the LLDB prompt, and the command REPL will switch back to the REPL prompt so you can use the two interchangeably at any time. The REPL has slightly different characteristics and may even be desirable within a debugging session where you're debugging Swift code.
So that's great. It's a couple of the key ways a lot of people interact with LLDB but LLDB is also a standalone command line tool and that's fantastic if I'm a remote shell into a machine, I have a very slow connection, I really want to use typical benefits of a command line tool.
But it's also useful under other circumstances. You might want to use it for example if you're going to automate debugging tasks. I want to setup my debugger the same way every time I start it, so I might go ahead and provide a file containing a whole bunch of LLDB commands to configure things.
LLDB -- source followed by a file name will invoke LLDB and source all of those commands to setup my debug session just the way I want it. If you don't want to go through the trouble of setting up a file, you can invoke LLDB and provide commands directly on the command line; -- one-line will be followed by command and that will be issued as soon as I start LLDB, and if I want additional commands, I can just repeat the option here in its abbreviated form -o followed by another command as many times as I like. So it can be really trivial to setup LLDB just the way I want it in an automated script.
And of course this is especially valuable in a situation where I have an application that fails only one out of a hundred times. It's a race condition, I'd like to run the same debugging sequence over and over again and we have a special option for that as well. LLDB -- batch starts a batch mode. It will execute the instructions that I provided either from one line or sourced from file and presuming my application completes normally it will then exit.
If my application crashes, it will stop right at the LLDB prompt where I can investigate the problem. And of course just by wrapping this with a few other shell commands I can repeat that series of actions ad infinitum or at least until my application crashes and I'm ready to investigate the problem. LLDB has a wide range of options. If you haven't looked at LLDB -- help before, I'd encourage you to have a look. It describes these options and many more.
I'm going to introduce one really interesting concept here for us as a team that's actually probably the least important thing in this entire talk. So if you've going to forget one slide, please start with this one because it's largely transparent. You shouldn't really notice the effects but there are some subtle aspects of it that I want to introduce. Starting with Xcode 8, LLDB runs in a completely separate process from Xcode. It's totally transparent to you. You start debugging the way you normally did and what that allows us to do is support multiple different versions of the debugger.
It's selected for you automatically, so if I go to debug Swift 3 I get the latest debugger, I get all of the features that we're going to introduce in this session. And in fact if I'm using pure C++ or Objective C, the same thing is true. I get the latest debugger with all of the features we'll talk about here.
On the other hand, if I'm debugging Swift 2.3, I'm going to wind up with the debugger that's essentially what we shipped earlier this year with Xcode 7.3.1. That means some of the newer commands won't be available, but we have the full support of the debugger from the era of the Swift compiler.
But most importantly perhaps if you're part of our open source community, you'll be able to debug using the version of the debugger that matches the open source Swift that you're using. So if you download a snapshot of our work in progress or indeed if your start adding to it yourself, you will have a debugger that's immediately available and you can actually use that concurrently with debugging other programs written in Swift 3 or Swift 2.3 and everything is completely transparent.
There is one additional benefit here which is that if LLDB gets into a situation where it cannot proceed and it needs to shut down the debugging session, the debug session will be terminated, LLDB can exit gracefully, and Xcode will stay running. To talk about what you can do with LLDB a powerful tool that it is and how you can customize your experience, I'm going to invite up one of my engineers Enrico Granata.
[ Applause ]
Thank you, Kate. Hi, everyone. One of my favorite things about LLDB is how customizable of a debugger it is. It's not only great fun to work in the technology that enables this, but it's also an amazing way for you to be more productive in debugging your applications, and LLDB offers a great many entry points for you to customize it. You can start with command aliases and then work your way to custom commands or custom data formatters and in Xcode 8 we have one new extension point for you; stepping actions.
The way to think about stepping actions is do you like what the next command does? Do you like what the step command does? But do you find yourself wanting to tweak its behavior in just one or two little ways here and there? With stepping actions you can actually do that.
But the simplest way to get LLDB, to customize LLDB to suit your needs is to create a command alias which is a way to take a piece of debugger syntax for something you frequently do and attach a shorter piece of syntax to it. And now in Xcode 8 we also let you attach help text to it so that for your own purposes or for whoever you share those aliases with you can remember what the alias is up to. Let's see an example.
First of all, to create an alias you start with the command alias command. Then as I said you can pass help text to it, you provide us with a short syntax, and then you give us the full debugger command you want to replace. And now shell is just as normal a debugger command as any of the ones we built into it. You can for instance say help shell and it will so you help text including that which you provided to us.
And for those situations where debugging becomes a little gnarly and you tend to forget who you are as you pursue your bugs, you can ask the debugger to remind you your identity and it will tell you that it looks like I'm Enrico. And that's great but if you want to do something more advanced than simply attach a new name to an existing command, we also vend you a Python API.
It's a fairly extensive option model that lets you band the debugger to your will. Getting started is easier than ever. We've talked about this at great length in previous WWDC sessions which you can find online. We have a website with documentation and examples to look at and if you just search for it online, there's a community that's doing wonderful things around scripting LLDB. Let me give you a quick example. Let's say that I want a command that lets me retrieve the return value for the last function that I exited out of while debugging.
Couple caveats here. This command will work only if you finish your way out of a function and don't execute any stepping actions right after. You can type expressions, you can look at variables, just don't step around and this will work. Let's look at an example real quick. First of all, you import a file that contains that command and then as I said finish out of the function and the return values is right there for you to see. Okay, that's great. That happens by default, no need for customization here.
But what if I type backtrace for example and that's quite a verbose backtrace and now I don't remember where the return value is. Okay, I could go up in the terminal and try to dig it up but luckily enough we actually made a command that will tell us about it, and here that return value is again for us to see.
This is all it takes to make that happen. And by the way don't really worry about reading that slide now, it will just be online waiting for you later. On the other hand, you see that it is slide of text and now you can type your shell alias, you can type the text, and you'll find yourself typing this because they're awesome every time you start debugging and you'll type them again and again and again until you're a literal typing monkey and all you're actually doing is typing these debugger customizations.
Nope. I say no to that. I say save yourselves from repetitive typing. There's no need to do that. LLDB has an initialization file. It's called .llbdinit and it sits in your home folder. And if you need LLDB to do something special something different when launched under Xcode versus under the terminal, there's a .llbdinit -Xcode file that will be preferred when launched in the debugger under Xcode. One more trick. If you have Python commands that you need at debugger start up, don't try to type them in .llbdinit. Put them in a .py file and then source the .py file that in LLDB init with command script import.
Those of you that have used LLDB for a while are probably fairly familiar with the p and po commands. These are great commands, they're great ways to look at data because they are full expressions. They have the entire power of the language you're writing your application in available for you at the debugger console. On the other hand, with great power comes great responsibility. These commands run coding your target process. They have potential to cause side effects and also sometimes it's just not possible to run the code you want at the point where you're currently stopped.
And if p runs code once, po will actually run code twice because not only does it evaluate the expression you provide it also uses it also evaluates code to print the customization to show your type in a way that's customizable by type authors which is great if the type author customized the display for their type in exactly the way you want it. If you're not that lucky, the p command provides an alternative viewpoint of variables that may be closer to what you want. And also po is also a command that runs again twice coding your target process with all the potential for side effects.
If that scares you, we have another command to look at variables; frame variable. This is a very predictable command, it will not run any code. On the other hand, because it doesn't have that code running ability, the syntax it offers is also extremely limited. And that's quite a few ways to look at data already but spoiler alert, in Xcode 8 we have more.
Two new ones parray and poarray, and yes you're right. They sound a lot like p and po but they do something special for arrays. What do they do especially for arrays? Well, if you have used NS array in Objective C or Swift arrays you're used to the safe container that knows how many things it contains. C pointers don't do that. C pointers don't come with a kind of batteries included. They don't know how many elements they point to and so when we print a C pointer like this example in the debugger all that we're told is the pointer value.
But now we know that this points to a bunch of elements so we can start printing the first one, printing the second one and we keep going and now we're back to the little typing monkey situation which we don't like. Well, in Xcode 8 you can say parray, number of elements, pointer and it will expand for you that pointer as if it was an array of the element count you specified.
[ Applause ]
Thank you. That's already nice but why do I have to guess at the element count? It's right there. We have the count right there. What I really want is being able to type parray count dataset. I almost can. All I have to do is put count in backticks. That backtick is a general LLDB syntax facility which lets you take an expression, evaluate it and replace the value of that expression in the command before executing it. And now I've got my full array shown.
[ Applause ]
Thank you. Pretty much the same thing works for po. poarray, number of elements, pointer and I get po style descriptions of objects. On that same topic of po, I'm sure that those of you that could write Objective C code, quite a few I'm sure, have actually done something just like that probably without even thinking about it. You take po, you say po followed by a number that you happen to know is a pointer value and you get a pretty description back.
And you try doing the same thing in Swift and all you get back is a number. Why? What's going on? Well, I'm sure you've heard this quite a few times but I'll say it one more time. Swift is a type safer language than Objective C. We can't assume that numbers are arbitrarily objects because not all Swift objects have a pointer value connected to them. So when we say po a number we'll show you the number. Okay, that's great but come on I know there's an object there just show it to me already. There's a way to do that.
There it is. It looks like a lot of words, I know it looks like a lot of words but it's actually just follow me for a second. Expr -O just means po. If you're at the LLDB console and you say help po, what it will tell you is that it's an alias for expr -O.
So all we're actually saying here is po this thing as if we were in Objective C code and with that we actually get the pretty description we wanted. And that's great. On a topic related to actually inspecting memory addresses and trying to make sense of them, low level debugging. If you remember one thing and only one thing about low level debugging is to stay very far away from it. Don't do it.
[ Laughter ]
Unfortunately, sometimes you just really have to. Maybe you're debugging something that only happens in optimized code in the release build of your app. That happens to me sometimes. Or you're debugging third party code for which you have no debug information. If any of that applies to you, follow me as we sail past the Pillars of Hercules on this journey. But please know that on this journey you proceed at your own risk.
It starts just like this; I had a gentleman last year walk up to me in the lab with his laptop showing me Xcode just in that state, crashed in Objc msgSend of all places. And he tells me a story. I have my app and it's under store and it's great but then my framework vendor says, hey I have a new version of my frame, just update, it will be okay.
And I did update, I listened to him, and now my app crashes on launch. What do I do, please help me. And so we sat down and I told him well, we know pretty much nothing here but one thing we can do is let's start by reading machine registers.
LLDB offers a facility to do that and it lets you look at all the registers, only a few of the registers and it even lets you play custom formatting. What does it look like? You say register read and you get your register values, and that's a lot of registers. And why do I even care about all those weird numbers and the words on the screen? Well, you do care because often arguments are passed in registers.
Okay, that's fun, but that was a lot of registers. How do I know which ones actually matter to me? That is a question for your platform's application binary interface ABI, the colon convention gives you those rules. But LLDB also exposes to you convenient pseudo registers named $arg1, $arg2, and so on which in the case in which your arguments are actually of simple scaler or pointer types actually mapped one to one between the registers and the arguments. Similar convenience is available in the C family expression. So for example, if I have a function that takes these three arguments and I call it, those arguments will actually map one to one to $arg1, $arg2, and $arg3.
Okay, so that applies to our example. We're in Objc msgSend, we start by reading arguments. The first argument is the pointer 0 X 4 D 2, the second argument is the selector string by appending string. We happen to know that Objc msgSends first argument is the object we're trying to message and the second argument is the selector we're trying to send, and we can also use the memory read command to check what's up with the object that we're messaging. It turns out that's a bad object.
What is happening is that we're calling this selector on a bad object. How did we get there? Well, we're in Objc msgSend, we're crashed, something called Objc msgSend, something called the thing that called it and so on and so forth until we get all the way to the entry point of our application.
In LLDB we call the frames from frame 0 the youngest all the way to frame N the oldest and if you want to move around frames, you can use the up command to go back to an older frame on the stack and the down command to go back to a younger frame.
Another thing worth knowing is the disassemble command which lets you look at the disassembly for a function. You can do that for the current function, for an arbitrary frame, for an address, for a function by name, you can customize the way that disassembly shows and in some cases it makes debugging where you do have source code and debug info but you want to compare those instructions to machine instructions you can also ask LLDB to always show you always show you disassembly along with source code. So in our case, we crashed there and we can see that the thing that called our function is an application that did finish launching. So let's go there real quick and take a look at what that function is doing.
That function is calling this initializer that our framework vendor told us, yes totally code that initializer, getGlobalToken. It's moving some stuff around and then it's making the call to Objc msgSend that will crash us. So we can step around machine code and see what these calls are actually doing. First of all we step over the getGlobalToken call and then I'm going to cheat for a second here. I happen to know that the register called rax contains the return value of that function and if I read it, that's just the bad pointer value. Interesting.
Let's step around a couple more times. No, that isn't changing it, that isn't changing it. All we're doing is taking that pointer value as is and moving it into rdi and then calling into Objc msgSend. I wonder if that's connected? If I reg read $arg1 at this point right before entering Objc msgSend, rdi the bad pointer value.
What have we proven to ourselves? We've proven to ourselves that the getGlobalToken function that our framework vendor was so excited to get us calling actually returned to us a bad object and upon trying to send a message to that bad object, big surprise, our application ended up crashing. And on that note of patting ourselves on the back for conclusively proving our case, I want to hand it over to Sean Callanan to tell you all about the great new features in the expression parser. Thank you.
[ Applause ]
Isn't that magic? It feels like magic. Your program is just storing its data as numbers, arrays of numbers and yet you can use LLDB this powerful tool to represent that data in the way you think about it. Sometimes though it's not quite as easy as just looking at a number and figuring out what the data is.
Sometimes you need the expression parser. Now, Enrico has already shown you where the expression parser fits in in the general command syntax, but there's a lot that it can do. The expression parse's job is to work together with your program and the SDK and get from where you're currently stopped through some contortions to get at the data you want to the data that you're actually trying to inspect.
Now, I said we work with your program and we work with the SDK. Working with the SDK hasn't always been easy in LLDB if you remember from previous years. So for example if you were stopped in an Objective C program and you tried to get the program's undo manager, you probably at least once or twice got an annoying error.
It wasn't at all relevant to what you were trying to do and it was really puzzling. But last year we told you there's a way to get out of this. If you just manually import AppKit then you're expression works. All right, but why did I have to do that? It's already there. I hear you cry.
[ Laughter ]
You're not the only ones. So this year we thought what can we do to make this better, and it was pretty obvious. We looked at which modules the current source file imports and we import them for you automatically. No more of that manual importing business.
[ Applause ]
Cool. So we're getting out of your way more efficiently. That's great, but this is supposed to be a powerful tool. Let me tell you about some of the great things that you can do with it. Now, sometimes these conveniences might get in your way. You're actually trying to manually import the things you want.
There's a feature, a setting that you can use to disable this automatic importing and get back the feature the way it was last year. We think you're going to like it though so it's by default on. Great. Now let's talk more generally about using the expression parser effectively by reusing code.
Now, the most simple case of reusing code is reusing variables. Now remember I said you might need to do multistep expressions to get to the data that you actually want from the place where you currently are. In Swift you can do something as simple as defining a temporary variable and using it. This just works. It's as if you typed it in your own program.
Now what might be counterintuitive is what happens if you try to use it again. Then we say, what's that variable name? Well, actually we intended it to work this way. The reason is you might step around, you might stop in different places, maybe later you're in a place where you're program actually defined an A.
Do we want the A you used as a temporary valuable to get in your way? Probably not, but there is a way out of this. The affordance we setup to make sure that your variables don't escape in that way is we actually setup a local context. It's as if you actually put a set of braces in your program, put the let A in there and the print, but if you want the A to break out, all you have to do is give it a special name, a name with a dollar sign. That means that it will never collide with your own program's names and it means that it will live as long as your debug session does. Awesome. What else can you do with this tool? It turns out quite a lot.
Now in Swift ever since day one with LLDB and Swift you've been able to do the same trick with functions. Now when you did that you probably wanted to use multiline expression modes and in fact if you typed the expression command and pressed enter, you're immediately going to get a multiline editor where you can type in your function. If you define your function then you can simply reuse it again remembering the dollar sign. Now, those of you who tried this in Swift and said that's awesome might have tried it in Objective C and it wasn't so great.
Function definition isn't allowed here. Come on these LLDB guys always get in my way. All right. Well, turns out we like this feature too, we think it's awesome, we want it to be better. But we can't just make it magically work. The reason is remember we're stopped in your code. We want to act like we're inside your function.
If you're in Swift you can define nested functions, it's no big deal, the compiler will love it. It won't love the dollar sign you can take that out but the rest it's fine with. This is totally legal. In C, C++ and Objective C though trying to make a nested function like that, that's no good the compiler is going to yell at you.
Well, the way you get around that is using the top level expression mode. That's an extension of the expression command that makes it break completely out of the current function you're stopped in and just define global code whether it's functions or variables or what you will. Now you can define your function and use it just the way you would expect. All right.
Now the functions aren't the only reusable things you can define. I've already talked about variables. You can define closures too, they're kind of a merger of variables and code. In Swift you can define a closure and use it. New in Xcode this year you can do the same thing in Objective C. Blocks can be defined and reused and for those of you who are diehard C++ fans, you can do the exact same thing with lambdas.
Now, what can you do with these blocks? What's special about them. Well, you can pass them off to functions for example. Sometimes you might need to manually run something on a certain queue. That works. You can send stuff for example to a global queue and the block will simply run.
Now, sometimes it gets a little annoying because these complex expressions result in much more likelihood of typos. Now, what's the difference quickly without looking back to the previous slide between this expression and the one from the previous slide? You probably missed it. The compiler sure didn't. It will yell at you about the missing semicolon but there's a better way.
If you were to type this into the source editor, we would have told you, hey you probably missed a semicolon here. Did you mean to put it in? Well, it turns out LLDB can do the same thing, and we can do one better by just automatically putting in the semicolon that was missing, running your expression. This is called fixit. It's been in clang for a while and LLDB now applies the same thing. Swift has fixit too. In Swift you're less likely to run into semicolon problems but boy those exclamation points are annoying.
[ Laughter ]
Yeah. Well, they are as valuable as they are in your own code for you to understand it, when you're debugging you just want them the heck out of your way, and believe me we do too. So if you try to use something without unwrapping it, we just apply the fixit and unwrap it for you.
Now, there may be one or two people who say I don't want debugger touching my code. Now, for those people I have had those evenings too, and we have settings that will turn off the entire autoapply fixit feature and if you just don't like the debugger acting smug and pointing out every little thing in your code that it's fixing for you, then you can turn just that part off too. All right.
[ Applause ]
Thanks. All right. Great. That's a nice convenience feature but let me finish up with just one more thing that you can define that's reusable. You can define your own types. In Swift for example you can enter a multiline expression that defines a class and when you try to instantiate that class indeed it shows up just the way as if you had defined the class in your own program. In the same way in C++, you can define a class and reuse it. Now let me show you an example of taking all these concepts and using them in your own program.
Often especially in your programs that interact with web APIs you get a lot of data back and you want to filter it especially when you're debugging. The way you filter data especially say in NS array is by defining a predicate. Now in the expression parser you can define custom predicates. In this case we're taking writing a block that takes the result strings from the web server and filters them to find strings that have the text error in them. Probably useful for debugging.
Now if you simply take an array full of data from your web server and you apply the predicate to it, you can get right down to the message that you actually cared about. All right. Now you've hopefully seen how powerful the expression parser can be for you, I'd like to turn things over to Jim Ingham to show you more powerful features of LLDB.
[ Applause ]
Thank you, Sean. So, so far we've sort of inverted the natural order of things and told you how to look at the state of your program when you're stopped but we haven't yet told you how you would actually get to such interesting points. So that's with what I'm going to tell you a little bit about.
Of course breakpoints are the natural way that you would stop your program. So I want to talk a little bit about how you might think about breakpoints naively as the place where I stop my program but that's actually not how they're implemented or how LLDB thinks about them. To LLDB a breakpoint is really is search through your program space for interesting places to stop or many different kinds of searches as it turns out.
So breakpoints are really search criteria and what the individual locations where your stop, what you thought of naively a breakpoint, we call breakpoint locations. So to make this a little more concrete, let me tie this to Xcode's breakpoints because after all Xcode under the covers when it debugs is LLDB so all the Xcode breakpoints must be LLDB breakpoints. So for instance when you click in the source gutter in Xcode, what you're really doing is running this command in LLDB, some breakpoint setting command.
Similarly when you make a symbolic breakpoint you're running a by name breakpoint setting. So I want to give you a little sense that these are really searches by showing you cases in which you would end up naturally with multiple results from what you thought of as a simple unitary breakpoint setting, so the first example that I'll show you is symbolic breakpoints.
So here's an example where you want to just set a breakpoint on main. That should be a simple thing to do, right? But then it says, no I have 19 locations. Well, while did you end up with 19? Do the break list command to see the results of your breakpoint setting and what you see is that the breakpoint name search is actually a loose name matching search, so for instance it picked up the selector names within a class and that's actually convenient in many cases because like if you're debugging in C++ and you have a name space, and a name space inside, and a class, and a method, you don't want to have to type all of the full path to that but on the other hand it does mean that the search is perhaps wider than you intended it to be. We provide many different kinds of searches so of course we provide a slightly more strict search which is the full name search that forces the name to match the entire name of the symbol that you're specifying.
We tried that but even that didn't work right. Well, for some reason somebody decided their library has to have a function called main in it, no idea why but it does. So you can even specify further by limiting your search to a particular shared library with the shlib option. So then finally then you get the breakpoint you want.
I'm going to give you one other instance not because I don't think you believe me but because this one actually comes up in Swift fairly often with file and line breakpoints because Swift has this nice feature that you can call a function that uses a closure and define the closure simply by continuing with a curly bracket and then on with the body of the closure.
But then if you try to set a breakpoint on that line, what you're going to find out is you have two stop points. Why do you have that? When you look it's actually fairly straightforward, right? That source line actually contributed a little bit of code to the closure function and you see we have a breakpoint location on the closure function, but it also was the invoking site for that function and so you also have a location for that invoking site.
So anyway that's for that so now having given you a few examples, let me give you the general form of the command and then go on to some more interesting uses of it. So the breakpoint set command works as follows: You say break set and then there are some options which specify the type and that's really specifying the kind of search that you're doing.
Is it a file and line search, a symbol name search or so on and so forth? And the values for that type option will be the data for the search and then there are other kinds of options like ignore count, condition and so on. The way to think about those options is they don't specify where to break, they specify whether to break.
So that's the whether can be modified after the fact but the where can't because we've already done the search and you would just set a new breakpoint if you wanted to do that. So let's talk a little bit about these breakpoint location things which are the places where you're going to stop. They are the individual search results, they always have some address which is the address at which your program is going to halt.
When you look at them they're specified by the generating breakpoint's number and a location number written separated by a dot. So if you actually notice when you're debugging in Xcode and you stop at one of your breakpoints and you look at the little PC ribbon, the PC ribbon will have the stop reason on the far right and it will say breakpoint, but it always says 2.1. It never said breakpoint 2 because you only ever really stop at locations so 1.1, 1.2 or whatever.
By the way, the locations and breakpoints are sort of symmetrical with respect to all of these other options that I talked about. They all take the same sort of generic options like commands and conditions and so forth, and you can specify a command condition whatever on a breakpoint and then it will work for all the locations but you can also override for a particular location one of the commands or conditions just by setting it on the location instead.
One other little convenience, oftentimes if you have a breakpoint that's generated a bunch of locations, you want five of them, you don't want five of them so you disable them which you can do independently, but then if you you don't want any of them to hit you want to be able to disable the whole breakpoint which you can do by disabling the breakpoint. But it turns out that doesn't change the enable disable state of it locations so then you can just turn it back on again and all of the location state will be as you would expect.
So that's just a little thing. So now that you've seen a little bit, the notion of how breakpoints are thought of in LLDB, let me show you a couple of more powerful types of breakpoints that LLDB provides. So again these are searches for places to stop. What kinds of searches are we going to do? It's just what name spaces in your program might be interesting. It turns out all of the name spaces are name spaces of stringy things because they're all like names of functions or whatever, so we always use regular expressions as the way to express the search patterns.
So if you know regular expressions, this will make you feel lovely, and if you don't regular expressions, I would have said a couple years ago look for somebody in your office that has books with animals on them although nowadays if you're looking for somebody old you probably should just look for somebody who has books at all.
[ Laughter ]
[ Applause ]
So anyway, so we provide two kinds of searches, one is fairly obvious which is a search over the names of functions in your program so this is the option for that, and one that might be slightly less obvious but I'll convince you is interesting I hope as we go along, are sourced text search breakpoints. And this is the option for those.
Okay, so let's give you the first one this is function name pattern matching breakpoints and I'll just show you some examples. So suppose somebody has given you a new class and you don't know what it does, you want to see how it works, so what you want to do really is break on all the methods implemented by that class which you could do by going through the source file in Xcode and clicking on the beginning of all of them but that would quickly get tedious.
And by the way, you don't want to stop on the parent or subclass whatever. What would be better would be to try to cons up a regular expression which matches all the functions in a given class. So in Swift this is an appropriate regular expression or in Objective C this is an appropriate regular expression.
So then you would have breakpoints on all of those then you could run your program and then you could go through and see what's going on. And remember because you can disable individual locations, when you do this kind of experiment, you find that you hit one of them, you know what it does, you're uninterested in that one so you just disable that location then you keep going. The second one you figure out disable so on and so forth.
So that's kind of a nice way to explore new code. An even more radical version of the same thing is somebody is giving you a whole shared library that does some stuff. You want to just see what stuff it does when it's running then set a regular expression breakpoint, I'm using the short - r form here and the regular expression matches everything, that's what .star does, and then limit it to the library you're interested in.
Combining this with breakpoint commands can often be a really nice way just to sort of get a quick and dirty trace of execution through this library, you could backtrace and maybe print the locals and then continue and you'll just run your program and get a tape output of the execution through that library.
Of course it slows down execution but you know whatever sometimes you pay. And then the other trick again is as you find ones that you don't care about, you can disable them. So let me talk about the other kind of pattern matching in source. So the point here is that there are some constructs that are really obvious when you're looking at your source text but figuring out how to get to them in the generated code is really not obvious.
So an example of this is macros which generally just get substituted as text into your program and then sort of vanish, but you know where they're inserted in your code because they're the things with capital letters. So you want to do a search maybe for all capital letters or for the particular macros you care about.
But you can even be more creative than that so for instance I want to know anywhere that a particular field is gotten from a pointer which is something that in source text I can see obviously because it's going to look like that but in generated code finding those places would be quite difficult. So that's another instance where using the pattern matching in your source can allow you to find constructs that you might not be able to find otherwise.
Then another use of this is to sort of make topic groupings that you can set breakpoints on just by inserting patterns artificially into your source code like as comments saying break here or break here when you're interested in inspecting this particular subsection of my program's functionality and then using these source regular expression breakpoints to catch it.
So here's how the source breakpoint matching command works. There's source regular expression is the option, the data you're providing for this search is the pattern, and then you can limit it to one file, you can limit it to multiple files by just providing the -f option multiple times and there's also a flag to search all your source files. So let me just give you an example to whet your appetite.
Suppose I had like a complex function like a state machine which is computing stuff and then it's running from many different places in some horrible huge case statement or something like that, and I'm interested in finding out when it's going to return null but I'd like to know what was the case in this particular run through at which it returned null, and that's a hard thing to figure out to do because you can stop after the function returns and check whether it's null. You can go click on all the places where it returns null but you might miss one or you can just look for the pattern.
There's one other convenience that we offer you in the source regular expression breakpoint type which is you can specify not only a file but you can limit it to a particular function so in that case I'd do something like this. I'd break my pattern would be return and then I'm doing showing that I know regular expressions because I'm showing off, space star is any number of spaces and then my null pointer I limit it to a function and I limit it to a file I'm interested in and then I can find out as I run through exactly where I've returned null in this particular usage.
So it's worthwhile talking about a couple of extra breakpoint options that you might not have heard about. One of them is along the lines of the where or the filter kind that's useful now that we have Swift and Objective C together in programs which is the ability to specify a language for a breakpoint.
So for instance, there are a lot of count methods everywhere in the world and if you set a breakpoint on the name count you're going to set it on a bunch of Swift code but you're also going to set it on a bunch of Objective C methods and you don't care about the Objective C methods, you only want the Swift ones, then you can just specify the language Swift and it won't set a breakpoint on any of the Objective C names that happen to match. So that's just a useful little convenience and yeah, right. And one other option that's sometimes useful is being able to narrow your search to a particular thread.
So you've got some code that's being called and a bunch of different threads, it's like a kernel or something like that but you've starting working on the execution in one thread and you don't want your breakpoints that you're using for the investigation to take you off onto other threads, it's fairly simple.
There's a thread ID option and there's one that you can do by thread name which you set with this pthread set name np call. That one is convenient because if you name a thread then that persists over many debugging sessions where of course the thread ID is going to change every time, and you can even restrict it to code that's being serviced on a particular queue by name.
One other thing that you might note is that you can add all these options to existing breakpoints and particularly that's useful if you've set file and line breakpoint in Xcode in the gutter but then you decide you want to like for instance limit that one to a particular thread and you can change all these after the fact, the command is break modify, and the other useful thing in this slide is showing you how you specify them because you can specify either by breakpoint, by breakpoint location number and there's also a little syntax to specify ranges.
That's that. So now you've come up with all these clever breakpoints that you want but you run into a little road block which is that it turns out Xcode at present only persists breakpoints that it knows you set and so the ones that you've managed to write in by hand it won't know about.
So how do you make them persist? The first way is what Enrico told you about if you want it to hold for all projects, you just put it in your LLDB init file and then you're done. But if you want to make project specific ones, here's a cute little trick that you can use to get the breakpoints loaded every time you debug that particular project.
What you do is make an Xcode stored breakpoint, preferably something that's going to get hit early on in your program execution, and then you put your breakpoints as commands in that one. So you know if you're a main executable for instance, main is a very convenient place so you would make a symbolic breakpoint and then you would put main in and then you would remember the slide a while ago now where I told you about our little trouble with main, so you would specify the shared library, then you would add an action which is a debugger command action and then don't type in all of the breakpoints in one in one in one here because that's just going to get tedious. It's much easier to put the commands in a file and then use LLDB's command source command to load those commands in, load those breakpoints in and then finally if you autocontinue then just every time you run you will automatically have all of those breakpoints set for you.
So I want to show you one other little convenience we've added to overcome one particularly annoying problem that you get in modern languages which is when you're trying to step you're trying to step into something but the problem is that in most modern languages most of the variable access is now done either as properties or through accessor functions or whatever so that and they're generally that's not the code you're trying to debug.
So you end up in a scenario like this you know I'm here I'm trying to get into this function, do something. I want to step in there. So I try that I step and I don't end up there because one of the arguments that I was passing was an accessor function so I ended up in the accessor. I don't want to be there because it's not very interesting so what I'm going end up doing is finishing out and stepping back.
So is there any way that we can make that slightly more easy? And it turns out that we've added something called targeted stepping so the option is the step in target that you would say by saying step and what I want to do is I want you to step but I only want you to stop in this particular place, that's what you're expressing with this.
So let's try that in this case and what we'll find is that it almost but doesn't quite work and the reason it doesn't quite work is because though we didn't end up in the accessor, we ended up on the next source line instead of in our function. And that makes sense when you think about it because actually stepping is source line by source line and that was the multiline call. So we've also added the ability to specify the end range of the stepping operation by saying what the end line number is or even more conveniently you can say just step through this block and get me in to do something I don't care.
And there's even an alias for that which is sif step in function. So then what you would do is you would be sitting here and you would say step in function and then you'd land in the right place or if you didn't I wouldn't have put it on the slide.
So that I want to conclude with a couple little bits of troubleshooting information. One piece of information you often need to know is what is actually in my running program. For instance maybe I built the release and debug versions and I want to know which one I'm actually using or somebody gave me a library with a dSYM. Did the dSYM get read in? So the command that inquiries about that information is the image list command.
You can either give it a module name in which case it will tell you information about just one module loaded into your program or for amusement's sake you can give no options you'll show all of them which is sometimes eye opening. So here's an example just to see how it's used.
I say image list example and I see here is the path to the binary so for instance if I wanted to check whether I was using the debug build, yeah, okay it does look like I'm using the debug build and if there is a dSYM available, it will always be listed after the binary so in this case I see I did get my binary.
I want to tell you one thing about Swift debug information. I put on this slide the why but I'm not actually going to tell you the why because we're running a little short of time, but I'll tell you the TLDR because I'm going through too fast for you to read.
The TLDR is that because of the way Swift and LLDB work with one another all the Swift code that you have that has debug information has to have been built locally so copying binaries from other people doesn't currently work and stuff like that. You have to have made sure that everything has been built locally and with the compiler that goes along with the debugger that you're currently using.
I want to say this is one little convenience we've offered and so Enrico's rule of optimized code debugging as we saw earlier is don't do it if you don't have to and then since most software developers are rational actors you can write a corollary to that which is that most people who debug optimized code actually do it by accident.
So now LLDB will tell you that a file was compiled optimized when you stop in it. It will only tell you once per library and you'll get a little message like this. And then you run quickly to your build settings and change them back. One other new feature that was added to clang a while ago was this notion of modules. So modules are a way to allow the compiler to look at all the headers that are the header environment for your program, compile them, parse them up once and then reuse that for all the compiles.
So then we thought well why can't we also do the same thing for the debug information? Why don't we allow that parsed form of the type information to be also done once and then shared amongst all the .o files that you have debug information? So that's called clang module debug feature. We can also use PCH files by the way. The setting in Xcode is clang module debugging and here's the flag for some reason I put that in.
This is great because again like with the compiler this speeds up the generation of the debug information, it will speed up your compile times but it has one caveat, and that caveat is that or actually one major one. So that caveat is that now your debugging is depending on your .o files but also on something that's sitting in some cache somewhere.
So normally that's not a problem everything is in place but when you go to ship your library or application to somebody else, how is that going to work? Well, if it's an application or a framework then you just run dsymutil like you already do and it does the right job, it gathers everything together and that all works.
But remember that dsymutil only works on linked products, it doesn't work on .o files. So if you are shipping static archives with debug information, then you must turn off this G module's feature or you will ship broken debug information to your clients. And also by the way, if you're running out of disk space and you delete your module cache, now you're not going to be able to debug anymore. So that's the only down side to that feature.
So with that let me tell you what you've seen. We hope that you see that LLDB is an extremely customizable debugger providing you many ways to look at your data that expressions actually give you much more power for investigation and I thought Sean's example was great of how you actually live go through and find what you're interested in in a complex array, that we have more breakpoint types than you know of in your Xcode and that you can actually get yourself into super deep trouble with more than source level debugging, and in general we hope we provide you a rich set of tools for exploring your code.
And here are a couple of the previous sessions that might have interesting information. There were a couple of sessions earlier on which you didn't see or did see but anyway they are available on slides. And with that thank you and I hope that you enjoy the little tiny bit that remains of your WWDC.
[ Applause ]