Developer Tools • iOS, OS X • 54:06
Xcode provides a powerful debugging interface and the new state-of-the-art LLDB debugger. Discover how you can better track the value of your variables, better debug multithreaded applications, and find and fix issues faster than ever. Join the Xcode engineering team for an in-depth look at the Xcode debugger and LLDB.
Speakers: Troy Koelling, Han-Ming Ong, Ken Orr, Alex Raftis
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good afternoon. I'm Ken Orr. I'm the manager of the Xcode debugger UI team. I saw a very interesting talk the other day. It was a TED talk. Those are the technology, entertainment, and design talks. And it was a talk about how to use a paper towel by a guy named Joe Smith. And he was showing how we could vastly reduce the amount of paper waste we produce each year by doing just a couple things differently when we go to dry our hands.
Now, what intrigued me so much about Joe's talk was that he was presenting in such a prominent venue about such an ordinary task. You know, and that got me thinking, that is exactly what I'm here to do today with you, to talk about debugging, something that's second nature probably to most of us, something that seems so ordinary, but it's still a task that has many simple things that we can do to eliminate waste and become more effective. And so when you leave here today, I want you to be able to just tweak a few things about how you work, and that small amount of change will add up to a large amount of saved time over the long run.
Now, what is it that we're doing when we're debugging? Well, probably a number of things, but two of the most important are we're trying to analyze the control flow of our app, so what lines of code are being hit and when. And then we're also trying to evaluate correctness.
Are we showing the right information to our users? And Xcode provides us with some fantastic and very powerful tools to do this, namely breakpoints and variables in the variables view. So today, I want to spend some time talking about these and show you how you can be more effective in using these tools that Xcode provides you.
So we'll start by talking about breakpoints and breakpoint actions. Now, before we do that, let me ask, how many people are not using breakpoints? Or perhaps you don't know what they are. I know there's got to be somebody, but in fact, my own brother, who's in academia and also does some development, was not using breakpoints until I gave him a private demo of these and showed him how it would change his life. But that meant he was doing a lot of this. And this is extremely inefficient. So you have to first find the line of code where you're having a problem. You have to stop running.
You have to insert the log statement. You have to build. You have to run. You have to get back to that line of code. You have to evaluate what's going on. Then you're not done. You need to stop. And then you need to take this out, because if you don't, you're going to leave that debugging cruft in there, and we don't want that. So this is an extremely inefficient way to debug. So we do not want you to do this. And in fact, if you use breakpoints and breakpoint actions, you'll never have to put another NS log in your code again.
So let's start back at the very beginning and talk about how to create breakpoints. Well, that's very easy in Xcode 4. Of course, you can just simply click in the source code editor margin there, the gutter. And alternatively, if you're a keyboard person, you can press Command-backslash. Now, deleting a breakpoint is just as easy. You can drag that out. A lot of people don't know that, but that's a very easy way to delete breakpoints.
And now, once you've created these breakpoints, you've taken the time to set these, you're probably gonna want to manage them. So you're gonna want to see them all in one place. And the place you do that is the breakpoint navigator. So you get to the breakpoint navigator by clicking this little breakpoint icon here. Or you can also press Command + 6.
And let's take a closer look at the breakpoint navigator. So if you're using a workspace, then by default, all of your breakpoints, they live with the workspace. So they're attached to the workspace. But they don't have to stay that way. So workspaces are they're composed of projects, one or more projects. And, in fact, you can move breakpoints to live with the project instead of the workspace. So here we can move the breakpoint. There's only one project here. But I'm going to move it to the sketch project.
Now, why would you want to do this? Well, there's a couple reasons. So first, you may want to open that project up outside of the workspace. So if you want to do that and you want the breakpoints to come along for the ride, then you're going to want to associate the breakpoint with the project rather than the workspace. And the second reason is that oftentimes the project will be associated with many workspaces. And if that's the case and you want the breakpoint to be associated with the project, this is how you achieve that.
Now, we can also share breakpoints. A lot of people don't know this, but sharing breakpoints means that anybody can have access to the breakpoint. So anybody that opens the project or workspace will see that breakpoint. And this is also useful for a couple reasons. You can, if your team, for example, has a number of breakpoints that apply to everybody, and in fact some of the teams at Apple work this way, then you can share that set of breakpoints and then all your team members can see them.
Also, the way I use these is if I'm providing reproduction steps for a bug, I will write up the steps, then set a shared breakpoint, zip that all up, send it to the person that's going to reproduce the bug, and now they can see exactly where in the code they need to land in order to see that issue. So a great tool to do that. So here we've created our shared breakpoint.
And then there's one last place that we can stash our breakpoints, and that's in this special user group. So the user group is a special group that lives on your machine. So it's associated with your user account, and now any breakpoints you put in here will be available from any projects and any workspaces that you open up. And we'll see in just a minute why this is useful.
So here we've moved that to the user group. Okay, also in the breakpoint navigator, we can create a couple special kinds of breakpoints. So, namely, the exception breakpoint and the symbolic breakpoint. So an exception breakpoint is one that's hit any time an exception is thrown or caught. Now, if you'll remember in Objective-C and in Cocoa, we don't use exceptions for control flow, like you might in .NET or Java, but in fact, they're used only for exceptional cases. So they're really cases that you want to debug.
That's when we'll use those. Now, when you create one of these exception breakpoints, you'll see this little popover, and there's a couple of knobs that you can twist on here. And the first knob that you can twist is you can decide when or the type of exception breakpoint. Do you want to break on all exceptions? Do you want to break just on Objective-C or just C++ exceptions? Typically, I just leave that on all. That's the easier setting, and that's the default.
Then you can also decide when you want to break. So do you want to break when the exception is thrown, or do you want to break when it's caught? Now, the default is thrown, and that's where I leave this most of the time, because that will-- then you'll stop at the line of code that actually caused the problem. So that's probably where you want to be, so I would just leave that on throw.
We're going to skip those two options for now, but we'll come back to them. Now, when we create a symbolic breakpoint, we see this little popover. And let's talk about what a symbolic breakpoint is. So a symbolic breakpoint lets you specify a symbol name. So that would be something like a method or a function. And when that method or function is called, we'll pause execution and we'll stop you on the line of code that's making that call.
Now, this is useful in cases where you want to set a breakpoint, but you don't have access to the source code. So if you want to stop on malloc underscore debug, you don't have the source code for that. So you can't open it up in the source code editor and set a file in line breakpoint. So you want to create a symbolic breakpoint with that function name.
Now, you can also have a symbolic breakpoint that resolves to multiple places in your code. So if you wanted to stop any time draw rect was called across any class, you could do that by putting a symbol name of draw rect. Now, that would stop you in a lot of places, but you can start to think about when you can use this type of breakpoint.
Now, you can also specify the shared library that you care about. So if you know exactly what shared library your symbol name is in or the only shared library that you care about to stop in, you can specify that here. Now, if you leave it blank, we'll stop any time the symbol is hit and we'll resolve it across all shared libraries.
But you can whittle it down if you'd like. Okay, so we've seen how to create symbolic and exception breakpoints, which are both very, very useful. But let's take a look at some things that we can tweak about just simple file breakpoints. So the first thing we can do, well, is edit it.
And this is a little-known keyboard shortcut, well, keyboard mouse shortcut. So if you hold down Option and Command, and then you click that little blue breakpoint icon, you'll get that nice little popover. And you can do that either from the breakpoint navigator, or you can do that from the source code editor. That will shave at least a few seconds off your day.
So the first thing we can tweak here is the condition. So if you have a line of code that's hit in a lot of different scenarios and there's only one particular scenario that you care about, well, you can articulate that by entering in an expression. And that expression will be, of course, evaluated either to true or to false. If it's evaluated to true, we'll stop at that line of code. False, we'll just pretend that it wasn't even there and continue going. So the expression is, it's a line of code just like you'd put in your source code.
Now, you can also specify an ignore count, and this is useful if you're debugging drag-and-drop code or drawing code where you just don't care about the first 50 times that a line of code is hit. So you're dragging over, and you only care when you get to the drop target, and it takes 50 times to hit that line before you care, so you just enter the number 50, and then we'll skip the breakpoint 50 times, and time 51 will stop.
Now, the last option here gives you the ability to automatically continue. So, if you check that box after we evaluate the breakpoint, then we'll actually automatically continue. Now, by itself, that isn't particularly useful to just keep going, but when we combine that with the next thing we're gonna talk about, which is breakpoint actions, we'll see how that can become much more useful.
Now, breakpoint actions are-- they're operations that are-- that are run when a breakpoint is hit. You can add one simply by pressing that little guy there, the Add Action button, and subsequent actions you can-- you can add. So you can have any number of actions on a breakpoint. Typically, I have one to two. That's about as many as I find useful usually, but you can have as many as you want.
And today we're going to talk about five of the standard breakpoint actions in Xcode, starting with the debugger command breakpoint action. Now, this action is probably the most useful. You'll probably find this one the most useful, and it's also probably the most used. And the way I use this is I use this to get rid of that NSLog statement in my code, and I do that by something like this.
So instead of an NSLog, I create an action that does a PO, so that's the LLDB command to print an object. And then I just put my variable there, and then oftentimes I'll check automatically continue so that I won't even stop. Because usually I don't really want to stop, I just want to see the value. So now I've added a logging statement to my code without adding a logging statement.
Now, you can actually get much fancier, and in fact, you could really add an NSLog to your code if you wanted without adding it to your code. And you could do that by running an expression. So here I'm doing an expr. That's the LLDB command to run an expression. And then I'm calling the NSLog function. And then I'm passing it my variable, just like I would do for my source code. And now I've added a real NSLog without even touching my code. And I can do this all while I'm running.
And you can really get much more sophisticated if you want. So if you had a conditional breakpoint and you met the condition, then as one of the actions, you could set subsequent breakpoints. So you could chain breakpoints together. And you could do that by running the breakpoint set command and then passing in the file and line. So you can see here, you have all the power of the console in Xcode, and you have that right here from this simple little breakpoint action. So you have a lot of power there.
Now, the next action is the log message breakpoint action. This is a very simple action, and I typically use this to just print out a simple message, something like this. Anything between at signs is an expression, and that will be evaluated for you. If you use the %H macro, we will replace that with the hit count.
There's also the %B macro, which will print out the breakpoint name. So I like to use this when I care to find out how often a line of code is being hit. I'll just add one of these actions, put the %H in there, and then I can see a quick count in my console.
So then we have the shell command breakpoint action. Now, this one gives you a lot, a lot of flexibility because you can do anything that you can do from terminal, you can do using this action because it just runs a shell script. So one usage I found for this is to actually take a screen capture.
So I'll set a breakpoint on some drawing code where I'm having a problem, and then I'll kick off a screen capture, and then I'll check the wait until done box so that we finish the screen capture before we continue evaluating. And then now I can go and I can inspect that screen capture after I've continued evaluating, and this is just a really, really powerful, powerful way to debug.
So then we have the sound breakpoint action, which is probably my favorite action. So, as you might guess, this one plays a sound. I like to use this when I want to determine how often a method is being hit. So if I have some timer code and that's calling a method and it should be called every second, well, I can quickly evaluate whether that's working by setting a sound breakpoint action, and then I can just listen to it. Now, you could, of course, add logging to do the same thing, but what I find is that there's so much logging going on in our apps that oftentimes it gets lost.
That message gets lost in there. And so the sound breakpoint action, it gives you another channel of sensory information that you can use to analyze the control flow, and that's what we're trying to do here. We're trying to better analyze and more quickly analyze control flow, so we have to use all the tools that we have access to. Now, as an added bonus, any sounds that you put in tilde library sounds will be available to you from Xcode. So that's your home directory. And so you can have a lot of fun with this one.
And then our last action is the AppleScript breakpoint action. Now, like the shell command action, this one gives us a lot of power and flexibility to do things outside of Xcode itself. So one usage I found for this is, so say you have a long-running test. It takes like three, four hours, and there's a failure in there. You know the line of code, but you don't know how you're getting into this case. So you set a breakpoint, and you add an AppleScript action, and then you set it up to email you, when that action is hit.
So now I can leave. I can go do something else, and then when I get an email two hours later on my iPhone, I can come back to work, and then I can analyze the problem. So you can get really creative, and I encourage you to try all these out and make yourself more effective when you're debugging. Now here to give us a quick demo of breakpoints and breakpoint actions is Alex.
Hi, my name's Alex Raftiis. I'm an engineer on the UI debugger team. And I'm here to show you some examples of what Ken was just talking about. So let me switch over to... Our machine. And first, I want to give you a quick idea of what we wrote to demo this.
And we chose to write a client-server application because we also wanted to show you that you can actually debug two processes running simultaneously in Xcode. So I'll give you a quick look at our project. We have a workspace, and here we see the three projects we have in it.
First, we have a HIKES framework. This framework contains our common object model code and code that needs to be shared between the client and server. We have a HIKES iOS target here, which is our application. And then we have a HIKES server, which is a Mac OS X application that is responsible for vending the hiking information.
Now, before I show you this running, I wanted to give you an example of Ken's favorite breakpoint. So I'm going to use open quickly, command-shift-O, and bring up hikeserver.m. And I'm going to go to line 207 here and set a breakpoint. Now I'm going to use option-command-click to edit that.
And I'm going to add an action here. I'm going to choose the sound breakpoint and have it play a ping sound. I'm also going to have it automatically continue evaluating after I'm done. Now, I've done this on the line of code where when the client connects to the server, this is the method that is called and this is going to give me a ping.
In this case, this is useful because frequently when you're debugging a client and a server, you'll find that you have the client code actually displaying in the console, not the server's console, so you could very easily miss a log message that something had happened. So this will just let us know that in fact the client is connected to the server. So let me go ahead and launch our server. And let me go ahead and launch the client.
Sorry, it sounded like I didn't have all the internal speakers, but my Mac Pro up here just pinged for me. So let me switch over to our iPad. And here you can see our application running. I know that it connected because I heard the ping, so if I click on our hikes, I expect to see data show up, and I do. I can select the central coast here, and I can select to see a hike.
There's the hike. And this is showing me a hike on the Montana de Oro Bluff Trail. Now let me show you one more thing to set up the next, which is I'm going to go look at the Cerro San Luis Trail, and this time it gave me a world map, so something seems to be going wrong. So what can we do to try to figure out what's going wrong with this? So to diagnose this, I'm going to set another breakpoint. Again, it's going to be in HK server.m, but this time at line 382.
Actually, it's gonna be at 383. I'm gonna set a breakpoint here and edit this breakpoint again. This time, I'm gonna add an action. And this action is going to be... Oops, I zoomed out too far. A debugger command, which I'm going to drop in here. And this debugger command, as Ken showed, is going to run a log statement. And it's just going to log the map ID that the client's requesting from the server.
I'm going to click to continue after evaluating again and click done. Zoom out, come down here into the debugger area and select the hike servers console. I'm gonna go to the iPad again and select the trail. And you'll see here that, sure enough, I asked for a map ID, but I don't see anything.
So I want a little bit more information about what's going on here. And so certainly I could add some more log statements and recompile and all of that, but I can do more. We've actually, into our server, built in a logging mechanism, and that's where you're seeing these info logs come from.
But we have a higher level of logging, a debug level, that'll give us a whole bunch of information that we don't normally want to run with because it would put so much information in the console we'd just lose any relevant information we wanted. But I can come in here with a debug command and an action, but this time I'm going to set a condition.
And in that condition, as you can see here, I'm going to do map ID is equal to string, the CXPR unique identifier that that map has. That means that I'm only going to do this action when that map ID request comes in. I'm also going to set an expression on this one. Oops. All right. I'm going to add an action again.
Make sure the field's selected and set an expression. And this time, I'm actually going to use the LLDB expression command to assign a new value into our log level. I can do this directly to an instance variable here because we're in the scope of the HK server object, so I have access to its instance variables. And log level four, of course, is our debug level.
And click to automatically continue after evaluating. But I need to take one more step here, which is also to come down here and set another breakpoint because I want to turn this log level off. The whole point is not to have a ton of log messages going to the console. I only want the log messages I care about. So again, I need to set a condition.
In this condition, we'll be the same as the one we did before. I'm going to add an action. And this action is going to be simpler to what we did before, but this time we're in the scope of a block, not actually in the scope of our HK server object. And because of that, I don't have direct access to the instance variables anymore.
But I can still change the log level because I know our connection object, which is in the scope of the block, has a server delegate, which is my server, and I can just call the setLogLevel method on the server object and set the log level back to 3. I'll set to automatically continue again. And now I'm just going to click on the map.
And you'll see here that, yeah, we wrote some data, so obviously the server thinks it's sending something back to the client, but I'm looking at this and I can see the number of bytes written, and it's not very much. It's certainly not enough to actually represent an entire hike. So that makes me think that there may be something wrong with my file.
So I'm going to come into my maps here and select the map, and sure enough, for some reason, I have an empty KML document here. So I'm just going to delete that. Drop the correct KML data in here, and this obviously looks like a much better data file. Save that. Request the map again.
And this time you're going to see that we actually wrote a whole bunch more data out to the client. So probably something's going right this time. Unfortunately, I'm seeing something else wrong on the iPad, so I'm not going to be able to show you that trail. But with that, I've been able to show you that without having to stop your client, stopping your server, add a log command, recompile your server, relaunch your server, relaunch your client, and get you back to where you wanted to be, you've been able to see that we've been able to get all this kind of diagnostics information out of the debugger. So with that, I'd like to turn it back over to Ken. Thank you.
Thank you, Alex. So we've seen how we can better analyze control flow using breakpoints and breakpoint actions. Let's take a look at inspecting our data in the variables view. So the variables view, it gives us a window onto our application state at a given point in time. Now, there are a number of different viewing modes that we can use when we're in the variables view. So we have the auto mode. Now, that's the default. And in this mode, we'll try to show you only the relevant variables at the line of code that you're stopped at.
So we'll look at the context, try to filter out the noise, and show you only the most useful stuff. Then we have the local mode. So that will show you all the variables in local scope. And then we have all. So this will show you locals, registers, globals. It'll be a lot of information, but it's there if you need to access it.
Then we have the variable kind. So this is a nice little icon badge which at a glance will tell you what it is that you're looking at. So am I looking at a local, an argument, a static, a global, a regular register, an instance variable, or an expression? So a quick glance, you can tell what you're looking at. Then we have the name. So this is the name of your variable as it appears inside your code.
And then we have the dynamic type. So the dynamic type is the runtime type of your object. So you may use an NSString in your code. That's the static type. But at runtime, you can see here, like the title object is actually an __nscsString. You didn't put that in your code. That's the runtime or dynamic type. So another example of that is maybe you're passing around a shape object in your code.
That's the static type. The runtime type would be something like a circle or a rectangle, the actual implementation. Then we have the variables value. So here it's a pointer, but it could be something like one or 5.3 if it's a primitive. And then finally, but certainly not least, and we'll talk about this more in a minute, is the variable summary.
Before we talk about that, I want to show you a new feature in Xcode 4.4 in the variables view. Have you ever found yourself in a situation like this? So here we have a method, current process name string. And that method, it's building up this string. So it's concatenating something together. And then it's returning that. And it's doing this all in a single line.
And then the calling method, update label, is just passing that right along. And it's passing it to another method. And at no point now do we have a way that we can actually see the value of current process name string. Now, you may say, okay, well, yeah, we can pull it out. We can capture that in the variable. But you shouldn't have to change the way you're working in order to get the tools to work for you. So this is something that Xcode in the variables view can do for you. And now in Xcode 4.4, it can.
So here we can see that if we set a breakpoint, we hit this line of code, we press the step out button, We land in the calling line of code, and now in the variables view, you can see that we show you this return value. So... We're giving you access to information that you never had before and hopefully we're shaving off one cycle on that debugging iteration that you're doing.
So now let's go back to summaries. So summaries show you the most important piece of information about an object. Now for an NSString, that's the character data. For an NSBundle, it's the path to that bundle. So it's--we're surfacing information that we think that you're probably going to need, and we're doing that in the variables view.
Now, it turns out in GDB, we didn't actually have summaries built into the debugger itself. We provided them from Xcode, but in order to do that, we actually had to run code like this many times. Now, running code like this is not great, and it's not great for a couple of different reasons. So, first, Running any code can change the state of your application. So we're running this code sort of behind your back, and that might have side effects. And the debugger should never have side effects, especially when you don't know about them.
The second problem here is that it may not always work. So we're running code, which means we may be allocating objects. Now, if we're allocating objects, then we need the malloc lock. If the malloc lock is taken by another thread, then we're not going to be able to get that lock. Fortunately, the debugger was smart enough to notice this, and it wouldn't deadlock, but you would end up with no summary or summary unavailable. So that's not good. That's not useful for you.
Well, in LLDB, they've built summaries directly into the debugger, and they've done so in a very extensible way. So the summary generators actually have access to the LLDB APIs. So they can do things like read memory directly. And in fact, that's what the summary for an NSString does. It knows how the class is laid out, and then it goes in and it reads out the character data directly. And this is great for a few reasons.
One, we're not running any code, so we're not going to change the state of your program. Two, we're not running any code, so we're not going to need to take any locks, so it's probably going to work all the time. And three, we're not running any code, and reading memory is way faster, so it's just an all-around win for you.
Now, we have these great summaries built in, and there's lots of built-in summaries. But like I mentioned, it's actually a very extensible system. So now you have the ability to write your own custom summaries, which will show up right in the variables view. And so you can help yourself reduce that debugging iteration cycle. And I want to show you quickly how to do that.
So the first thing you can do is you can add a Python script. So here I've added a Python script named custom summaries. Doesn't matter what you name it. And then you actually need to write this script. Now, I'm not gonna show you the whole thing, but I do want to point out a few key pieces.
So the first thing you need to do in this Python script is you need to import LLDB. So you need access to the LLDB APIs. Now, then you need to define a function. You can call the function whatever you want, but it's important that the function has-- it takes two parameters.
So the first parameter is this value object. That's an SB value. So that is an LLDB API object. That represents your object at the point where you're gonna be inspecting it. And then there's the second parameter, which is-- that's an internal use-only LLDB parameter, so you don't want to use that one.
And now we actually need to implement that function. So here I'm writing a summary for an SKT circle. And I'm not going to show you the whole thing, but I'll show you the most important parts, which is the beginning and then the end. So what I've done is my SKT circle, it has an IVAR. And that IVAR is underscore bounds. So I want to reach into this value object, which represents my SKT circle. And I'm going to reach in and I'm going to grab that underscore bounds IVAR.
Then I'm going to-- that's just an NSRect. So that NSRect, of course, has a size. So I'm going to reach in and grab the size. And then in the middle here, I'm just doing some calculations. So I'm figuring out the major radius, the minor radius, and the area. And then I'm building that all into a string.
And then I'm returning that. And that string that I return is what we show you in the variables field. So it's very easy to write your own summary without running any code and then provide that right to the variables field. So here to give us a demo of this is Troy.
Thank you, Ken. My name is Troy. I'm an engineer on the Xcode team. The purpose for me of using a debugger is getting information out of my application. And really the best way to get that information is through the variables view. So I'd like to give a demonstration of how that works. So I'm going to return to the same application Alex was showing us. As you recall, that's a hike application. I'm going to be mostly dealing with the client side of that. So let me get that running. Run the server.
and I will run the client in that. Let's run it on the iPad. How about that? All right. So as you remember, we click into one of these hikes and we get a hike. I would like to know in any given map whether or not these mile marker points are being created successfully and how many mile markers there are. So I'm going to set a break point in Xcode. I happen to know that map is drawn on the detail view controller, so let's use open quickly to get to that.
and I'm going to use this new type ahead filtering for the method selector to get to the configure view method and quickly jump to that where I happen to know the points are being called. The way the points are created is we have this parser for the KML files that Alex is showing us. And so the first thing this method does is check for the parser. And then down here, it queries the parser for the points. So I'm going to set a breakpoint there and refresh the data.
and hit that breakpoint. So typically when you're debugging, you don't know exactly where you want to go, so you set your breakpoint a step higher and then step into the method. So this is the method I want. I'm going to step into that method. All right. First thing it does is creates a mutable array, it gets the place marks out of our own place marks and gets a point out of that place mark.
If that point is valid, we're going to put it into the array, which it is, so that was the first thing I wanted to know. So now I'm going to jump out of that method. Oh, but I forgot to know -- I forgot to find out how many points were being created.
This has typically been a problem because the object we have here is a method call, so we don't have an instance to query in the -- to query directly. So what could you do here? You could -- You could iterate over that loop, but that's going to be tedious, so we're not going to do that. I could also copy this method call, open up the console, and PO that value.
But as Ken said, that might be changing the outcome of the program running because this is a parser. It might be reparsing things. It might be changing the number of points. So we're not going to do that either. Instead, I'd like to draw your attention to this new return value, which shows us that it's an NSRA, and it shows us exactly how many objects are in there. Also, we can control-click on this and send the description of that return value into the console so we can inspect the individual elements right there, which can be really useful.
Now I'd like to debug a real problem in this application, a real bug. As you can see, the master view controller on this has a distance label, but those distances look completely wrong. I don't know exactly how far it is to some of these central coast places, but I know 18 miles is not far enough.
So what's going on here? I guess I got to go set another breakpoint, dive in, and see how I can fix that. So that is in the master view controller, but I know even better that cell is being created in a delegate method, which I can jump to directly in this open quickly dialog. So that's in the table view, cell for row. There it is. That jumps me right to the right one. So it goes through this thing and down here, after it gets the location, it calculates the distance. So let's set a breakpoint there. And I'll reload this data.
Okay, so we have this object which is being asked for the distance from current location. But this object is an HK map, and as you can see down here, it's just kind of an opaque object. The only value it can show us is the pointer. So we would really like to add a custom summary to that to make that more useful.
I happen to have a custom Python summary over here in the project, so let's take a look at how that's put together. First, we import the LLDB APIs, which is important. These APIs provide us things like this get child member with name kind of stuff. I'm not going to go through everything, but also recognize that there's these two important parameters to this function. And down here at the bottom, we have a special method which LLDB will call for us when we import this Python file into LLDB.
And what we want to do in this function is add a type summary for this HKMap object. And that will tell LLDB that whenever there's this HKMap object, use this custom summary, and Xcode knows how to use that in the variables view. And you'll also note that it specifies which function to call, And that's the same name as the one up here.
So let's get back to our running code. All I need to do is a command. and Alex Farley. Hi, everyone. I'm Troy Koelling. I'm the head of the Xcode engineering team at the LLDB. I'm going to show you how to do this. I'm going to go ahead and start with the script import. You'll notice the auto-completion, which helps with demo mistakes. And I'm going to drag and drop that custom hiked summary Python script in here so we get the full path to this.
Now, of course, you could drop this line into your tilde slash dot LLDB init file, and that will initialize every time you run, which is really helpful so you don't have to do this manually every time. But for the sake of this demo, we're just going to do it here on the command line. Now when I open up the variables view, the custom summary is right here, giving us latitude, longitude, and distance. That distance doesn't look completely right, so let me try a different map real quick.
Okay, the distance here is 315 kilometers. That's nothing like the 18 miles we were seeing. In fact, if I step over this line, the distance, the core location distance that this map returns is also about 315,000, this time it's in meters. So those two numbers agree, whereas our text label was not agreeing at all.
Well, the text label comes in this next line where it creates a string from this function, this helper function here. So let's dive into that and see what could be going wrong.
[Transcript missing]
and take a look at this map data. I'll delete that one. Here we're getting closer to 200 miles, which is much more reasonable for the amount of distance it is from here to the Central Coast. So all because of data formatters, we were able to find, track down, and fix a bug that would have taken a lot longer in any other case. Now I'd like to return it to Ken.
Thanks, Troy. All right, so now I want to show you a few advanced debugging features. And the first is one that is new in Xcode 4.4, and that's the ability to debug as root. So you may need to debug as root if you need to launch your process as root because you need access to privileged ports, say, so ports below 1024, the whole class of things you may need access to as root.
And now you can do that by bringing down the scheme sheet, so you press Command-Less-Than. And then you'll notice this new option here. So we're in the Run Action Info tab. We see this option to launch and run as root rather than the user that I'm currently logged in as.
Now, running as root is also useful when you're debugging XPC services, because XPC services often need privileges. That's sometimes the point of using XPC services. And in fact, I just want to briefly talk about them, because I know they're new, so maybe we're not all familiar with those. So an XPC service is a way to offload logic from your app, from your process, and run that in another process.
So in this example here, I have my app, and now I'm calling this third-party API, which is a little bit crashy, and then I also have this privilege stuff that I want to do. Well, it's actually much better if I can pull that out into XPC services, because now what I can do is my crashy bits there that I'm calling will no longer take down my app. I can then respond to the crash of the XPC service rather than have the user see Crash Tracer.
So I can react to the crash of the XPC service, either show a message to the user or just call the method again, try it again. Now, also, if I want to do some privileged operations, and I don't want to escalate my entire app to have those privileges, I can pull that all out into an XPC service and just have that one little bit that gets escalated privileges. Now, these, by design, of course, are running, these XPC services are running in a separate process.
So you may be able to do that. You may be thinking, okay, how am I going to debug these from Xcode? Because these processes are, in fact, launched not by Xcode, but they're launched automatically behind the scenes for you by LaunchD. So how is Xcode going to know about this? Well, it turns out that there's actually a very easy workflow for this, and here to give us a demo of that workflow and debugging as root in general is Hanming.
and James Han-Minng Ong. And I'm here to show you the two things that Ken mentioned about. We're a little bit short on time, so let's dive right into it. So first thing I want to show you is debugging as root. I have the map server chosen in my scheme editor, scheme chooser. Let's go ahead and run it.
So immediately you will see that you actually have a-- you don't have permissions to run the root server, the map server, because you don't have access to some of the privileged assets, in this case, a port below 1024. Now, this is fairly typical of a server that you run, because, you know, they need access to privileged access.
The reason why you're getting the error is because-- in this case, with Xcode, it's because Xcode set it up such that you're running as yourself, which is the right thing to do for the majority of the time. So to fix that... we'll bring down the Scheme Editor... for the--for this scheme, and we'll just switch to root.
So you'll be asked to authenticate as the main user. And voila, in this case, you're running as root now. So let me switch gears a little bit and talk about debugging as XPC. Now, the way we wrote the map server is that we compressed the data on the server side and sent it to the client for efficiency reasons. So imagine this scenario that you are given this super-duper compression library that compresses 99.99% of the data. But you're not sure about its stability, so you don't want to do it in such a way that if your compression service goes down, your server goes down.
Because, you know, your server is earning you these billions of dollars that I've read about on the internet and needs to stay up because you've got a mortgage to pay and stuff. This is a good scenario in which, can't say you partition the XPC service out. If it crashes, your server deals with it and, you know, use the old way.
So I'm not going to go through all the details of how we wrote it, but just want to point out a couple of interesting things. We did it by adding a target, XPC target, service target, to our frameworks. And you can see that Xcode by default gives you a pretty nice XPC service bundle. We chose that.
Give us a main.m file, which is mainly boilerplates. The section you have to do is to handle the message. In our case, we compress it up and send it back to the caller. The color in our case is the map server. So it gets hold of the XPC servers by its well-known name. Sensor the data and expects the compressed bytes to come back. So I have my server running, so let's go ahead and run the client.
All right, nothing came back. So the only thing I added was the XPC service, so I must have screwed up, right? So let's debug that. To debug the XPC service, We choose the scheme, bring down the scheme editor. Let's choose the binary of the XPC service. So bear in mind that the map server is the one that's asking launchd to launch the XPC service, and because the map server is running as root, you need to debug as root. So Xcode in this case is not launching, so Xcode has to wait for it. Let's go ahead and wait for it. You can see an activity viewer. That's what we're doing.
Let's run the server. And let's put a break point here where I want to debug the why is it not compressing. are sending us data back. Server is running. Let's run the client. I'm not going to switch back to the iPad. I'm just going to trigger the breakpoint.
All right, so as you can see, I've hit the breakpoint. Let's step through it. So here I'm getting the data over, and I'm getting zero bytes. So let's see what I-- let's look at the color. Here's how I send it over. So there's a typo error. When I'm sending the bytes over from the caller, I'm using the keyword bytes. And on the receiver, I'm using my bytes. So if you were smarter than me, you would have to abstract this out into a .h file so that it's shared between the caller and the colleague. Let's fix that. Recompile.
[Transcript missing]
Okay, let's step through. All right, this time I'm getting, I think the right number of bytes. Continue. Switch you back to the iPad to prove that it actually works. There you go. Here's my map. With that, I'm going to hand you back to Ken. Thank you very much.
Thank you, Han-Ming. Okay, so you can find more information out about especially the LLDB custom summaries, and I encourage you to check these out. This is a great way to help yourself debug your own code. So LLDB.LVM.org, you can find out about custom summaries, scripting, LLDB in general. There are a few related sessions, and whether you're watching at home or whether you're here with us at the conference, I encourage you to check these out. There are, in fact, a couple more Xcode labs and LLDB labs. In fact, there's one right after this.
So today we've seen how we can eliminate some of the waste in our everyday, ordinary debugging. We've seen how we can more quickly get access to the information it is that we're looking for. And I hope you can take just a couple of the things back that we showed you and incorporate those into your everyday workflow, and that will save you time. Thank you very much.