Developer Tools • iOS, OS X • 55:25
LLDB is the next-generation debugger for Mac OS X and iOS. Discover why you'll want to start using LLDB in your own development, get expert tips from the team that created LLDB, and see how it will help you track down bugs more efficiently than ever before.
Speakers: Sean Callanan, Jim Ingham, Caroline Tice
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.
Hi, everybody. My name is Jim Ingham. I've been working on GDB for, God, a decade here at Apple. And for the past year and a half or so, we've cooked up this new LLDB, which is our attempt to make a modern version of something that will do all the tasks that GDB has done for you in the past. And so we're here to tell you kind of how you can start moving over to using LLDB from using GDB if you were a person. So here's what we'll talk about.
I'm going to talk a little bit about the LLDB command line and just its basic structure, how you can get used to using it. And then a couple of the other members of the debugger team will come up to talk about some power user features. So Sean Callanan will come up and talk about how to make good use of the expression parser for programmatic data introspection.
And then Caroline Tice will come up and talk about how to make use of the LLDB Python bindings, which can do many things. But, you know, kind of like automate some complex debugging tasks for you. So first of all, there's this LLDB project. So what is that? That's our modern replacement for GDB. It's actually an open source project. It's part of the LLVM project, thus the LL. It's supposed to stand for low level, but it's not a low level debugger. Anyway, whatever. It's an open source project. It's most of the work has so far been done by us.
But anybody who is interested in debuggers or wants to poke around at the guts of the system is welcome to take a look at it. And give a hand if they want to. It's hosted at the LLVM site, ldb.lvm.org, as you see. So we do take advantage of being a part of the LLVM project.
And we use the Clang type system for our type system and for our expression evaluation, which is nice because then they get to do work for us. We also designed it from the start to have very efficient handling of debug information. So it has an incremental dwarf part, whatever that means.
Anyway, what it means is that it's a very efficient handling of debug information. And what it means is that hopefully it will be faster startup times and lower memory usage, particularly as your projects get larger and have more and more debug information. Threads are a first class citizen, as opposed to GDB, where they're kind of tacked onto the side. So that's becoming important as threads become more ubiquitous in your programs. And finally, we designed it from the start to have a first class powerful scripting component.
In this case, we implemented it with Python, but it actually uses this SWIG interface generator. So I mean, if you said, I hate Python, I must have blah, blah, blah. And then it's an open source project, we'll tell you it's an open source project, why don't you go fit it to whatever blah, blah, blah is, and then you would say, No, I'll use Python instead.
[Transcript missing]
We tried to make a few little convenience things when we print out the registers, which ends up actually being surprisingly useful.
So if you see here's a register dump, but in this case, one of the register values pointed into a string section, so why not look up the string for you, which we do. And one of them pointed into text, so we tell you the function name in the text. That's convenient.
So the main thing I want to tell you about now is this alias facility, because I think that as you get used to the program, you'll find that you can mold it to your working patterns using this facility. So as I say, having this kind of turgid object actions, dash, dash, something, something, it makes it really easy to find and document, but it makes it hard to type.
So you have to start having accelerator commands, obviously, and by default, we ship with a GDB-like set, which should cover most of the common operations that you'll need to do. They're all listed in the help, so if you do just help itself, you'll see the list of built-in commands and then the list of aliases that we've set up for you.
But again, as you work, you may do something all the time, which is not one of the things that we expected you would do all the time. And so we want to allow you to be able to write a shortcut. So there are two kinds of shortcuts. There's a simple one, which is positional aliases, and then there's one that if you have learned regular expression and you go hunting the world looking for uses of regular expression, because you learned that aren't thing and you've got to use it for something, then there's one that you can use that for. So the positional aliases. These ones are just trivial to write. The command that creates them is this command alias. You say, oh, I just clicked too much. Anyway, never mind. And then the alias name.
What you are going to type. And then the substitute command line, which is the command that will get run. So in the simplest case, this is just straight substitution. So you say command alias step is going to be thread step in. So then when you type step, then what's actually going to get run is thread step in.
If you were keeping a sharp eye, you would have noticed in the case when I set a breakpoint, it actually, I used in the first slide, I said be something. And it actually printed out the command that it actually ran and then did the command. So you can. Learn how that works for yourself.
And if you've made an alias, then any additional arguments typed after the name of your alias will just get appended to the substitute command line. In this case, I'm doing dash avoid no debug false, and it's just getting appended to the end. The reason they're called positional is the other little bit of sophistication is that you can route arguments from the substitution from the command that the user types into the substitution string, which is particularly useful if you have two.
Option value pairs and you want to jam stuff into the two values. So the way you do that is you put percent and some number in the command line, the substitution command string, and then that will get filled with the argument of that number in the command that actually gets run.
So here, for instance, I've made an alias which has a count in percent one and a start address percent two. And then when I type it, then it gets substituted in, but in the wrong order because I didn't do the slides right. Um. And then, of course, any additional arguments are appended to the end, just like you would expect.
In this case, though, all the arguments are required. So if I said there was a percent 1 and a percent 2, there's got to be a percent 1 and a percent 2.
[Transcript missing]
So altogether, the command looks like this then. I would say command regx, I'm going to call it dfancy or whatever.
And I gave myself help because I'm going to forget how it works. And then you hit a return, it will very nicely tell you what you have to do. So then you'll type them all in, whatever, whatever. I'm going to go really fast so you can't see my bug. And then this is just like I didn't lie, right? We could do it in a demo, but the slides I could have just typed in. So I might be out lying, I don't know. But the help works, and then the command works.
Okay, so summarizing this little subsection of the talk, what I hope you come away with is that, you know, to get started, what you have to do is, you know, figure out how the top-level objects are laid out, and then remember that help will always tell you what you need, and tab will always tell you what you need.
And then once you get more familiar with what's going on, you can use the shortcut aliases that we've provided for you. Actually, we'll probably use those first, and then start typing the more verbose ones as you get further in. And then finally, as you watch your behavior, you can start to construct these shortcuts for yourself. So with that, I'd like to turn it over to Sean, who's going to tell you about the Expression Parser. Thank you. Thank you, Jim.
I'm Sean Callanan. I worked on the expression parser for LLDB, and I'm very excited to show it to you today. Now, there's going to be some excitement, there's going to be some drama, but first, let's start with the basics. You've got a program, and you'd like to use the expression parser to examine it a little bit. Now, Jim just sold you on it earlier, telling you you could do all sorts of low-level introspection. Now, let's see if that's actually true. Now, you're sitting at the LLDB prompt, and the first thing you do is you've got to run your program.
So you set a breakpoint at a particular stop, or place where you want to stop, and then you type run. You may be familiar with the syntax from GDB if you've used GDB in the past. And just like you would expect, the program will run and hit your breakpoint.
Now you can use the expression command. Now I'm going to type it out here just so you can see the full name of it, but you can also shorten it, and I'm going to be shortening it later in the slides. So here we're going to just type a very simple arithmetic expression, nothing fancy.
And what you have happen in the program is it's like a set of curly braces is inserted into your program, creating a new scope, and your arithmetic expression is put right in there and evaluated. Now, what happens after the evaluation is you get the result back as a new result variable. Now, your result variables are kind of cool. They're stored inside your program's memory so you can access them like pointers. And they stay around for as long as you're debugging your program.
Then, once you've done this, your code gets taken back out because obviously you don't want to continue and keep running 3 plus 5. And when you continue, your program just runs as normal. All right, well, that's the simple arithmetic expressions, but I doubt you need LLDB to compute 3 plus 2. So let's do something a little fancier. So I've inserted a new expression. So notice something, first of all. I've shortened expression down to expert because I need space on my slides.
So You still get your set of braces inserted, but you can do cool things, like you can access your program local variables. Now, that's not too fancy. I mean, it's kind of cool. But let's go and try something even neater. So, you've got a -- you just accessed a variable that already existed in the program, right? Well, maybe you need a new variable.
Now we get to see the power of LLDB's expressions starting to peak out. LLDB's expressions are actually using LLVM under the hood to compile your expressions for you. When I said there was a pair of curly braces in there, that's actually because you're getting a scope to execute some code.
So if you declare an integer variable and you actually use that variable, Also note that I have a multi-line expression here. I pressed enter after the expert command so you can actually enter multiple lines. You get these multiple lines inserted into your new scope, and now you can use your temporary variable.
That expression local variable, though, goes away after your expression completes. Now, obviously, that's not all you might want to do. You might want to keep your expression local variables around. Now, you know the result variable that I showed you earlier, the little $0. What if you could create your own user variables? Now, the GDB gurus among you may know that you use the set command for that. Well, we've got the same kind of concept, except you do this all from inside the expression command.
Inside the expression command now, you can actually create a $ variable that stores your value for you in the program's memory for as long as you're debugging the program. So I'm going to say $i equals 3 and then compute $i plus 2. The $i variable gets inserted as a new global.
The instructions referencing it get inserted into your program, and they run. Now, I've showed you how to access simple variables, how to create your own ones. In C++, things are going to act like you expect. Your code is going to get inserted into the method you're stopped in, and you can access the C++ member variables. And if you're in an Objective-C object, it gets inserted into the Objective-C method, and you get to access your instance variables. You're not going to get any nasty surprises.
All right. Well, I showed you a bunch of stuff. Let's just go through the summary and then get on to the fun. The drama. I promised you drama, right? So you've got in-scope variables that you can access just using the expression command. You can kind of think of it like GDB's print command for this kind of purpose. You can also access your globals and variables and your functions for which you already have debug information.
Now, there's a little bit of a wrinkle here, just as there was with GDB, by the way, that if you don't have debug information, for example, if it's a function that you got from the standard library, then you have to cast the return value for it. And if it's a variable you got from the standard library, you have to cast the variable to the type you're expecting it to be.
You can declare your own expression local variables that live as long as the expression does. And you can create user variables, declaring them once using the special dollar sign. and then referring to them in later expressions just whenever you need them. Alright, now let's do something a little bit more fun. Let's debug an RPN calculator.
Now RPN is reverse perlic notation, and what you do is you type in numbers, and then you type in operations to, that operate on the numbers you've already put in. So in this case we put in seven and five and we said plus, it pulls in the last two numbers and adds them. Now the way this is implemented is usually as a stack.
So when I type seven, seven gets pushed onto the stack, and when I type five, then five gets pushed onto the stack after it. And then when I do the plus operation, I get a 12 at the end, which is kind of what you'd expect. I'm not gonna debug that kind of simple bug for you. Alright, now let's do something a little bit more fun. First I'm gonna plus seven, and now I'm gonna try using the add command. Oh! I'm sure you've seen this error before.
So you're stuck at a segmentation fault. What do you do? Well, if you've used GDB in the past or you use Xcode, you know we opened the debugger. So let's open the debugger and let's think about how we're going to deal with this. Well, the first thing you do is you run LLDB on your RPN calculator. Now, you type run and your RPN calculator is running inside LLDB. Then we reproduce the bug and the program crashes. LLDB catches you. Now let's try running a backtrace to see what we can do about this.
Now this is the moment where some of you are going to start saying, "Uh, because we have no debug information." This is all assembly offsets. So you're at add plus 33, which means 33 bytes into the assembly code for add. What the heck are you going to do here? This is where LLDB is going to come to your rescue. So the first thing we need to do, because we want to be at the beginning of add, where the arguments are kind of in registers where you'd like them to be, so we're going to set a breakpoint on the add function.
Now we run again. LLDB asks you, oh, wait, do you want to restart your program? Fine, it's an RPM calculator. It's not exactly doing a lot of Internet interactions and stuff like that. So we're going to say 7 and plus and reproduce your bug. So now when you do a backtrace, you notice you're right at the beginning of the add function. This is where you can get your arguments out of registers, and that's what we're going to do.
I'm going to first of all pull out the first argument for the add function, and you're going to have to believe me that that's a pointer to the stack. So when you do that, you get the hexadecimal output of that. Notice I used the --format command here. The --format option tells LLDB what format you'd like your output to be in from your expressions. So X means hexadecimal. You don't have to remember this. There's help on it.
Now, let's try doing something fancy with this. First of all, I'm going to enter a multiline expression, again, by pressing Enter after I type EXPR. Now, I'm going to redefine the type that was missing because we didn't have debug information. I redefine my type and create a new user variable of that type. It's actually a double pointer. And I'm going to assign the $arg1 argument register to that new user variable. This user variable now has the type. I can actually access it. This is something that wasn't really possible in GDB.
You had to use weird hacks, examine memory directly. Why do you want to do that? That's like, then you have to remember all these obscure commands. You really want to do that. You really want to just be using types. And because we've got Clang under the hood working for you, you can do that.
So, now let's look at the stack entry that we've got on the top of our stack. Well, the value is 7, which makes sense because we typed 7 in at the prompt. But the next pointer is null, and we're in the add function, so it's looking at a null pointer to try to get out the next element. Well, that probably doesn't come as a surprise to all of you, but we've got a stack with one element, and we're trying to add two elements. Well, all right, fine. So let's try to fix this.
You're going to use the push function from your program to push a new element on the stack. Remember that we don't have debug information, so you have to cast. And then, if you now look at the next element of the top of the stack, you see, aha, there's something there.
So now it's safe to continue and let add do its work. And now we've fixed the problem. We've got 10 on the top of the stack. And hopefully, you see how we've done that. Now the next thing I'd like to show you is something a little bit cleverer and something that showcases even more for you just the power that's under the hood here. Now let's do stop and add again after putting some numbers onto the stack.
We are going to enter a multiline expression again. And now we're going to have to enter the struct that we entered before. Now, why is this? It's because, remember, we had a pair of curly braces that your expression is being executed inside. That means that there's a scope there. And things like the types that you temporarily defined here are actually going to go out of scope for you.
So you've got to remember to redefine your types if you actually create new variables of that type again. So here we're going to create a depth counter. And then we're going to use a for loop. We're going to use this iterator called current to walk across our stack. And at each time we walk across an element, we're going to increment our depth counter.
Finally, we tell it to report depth. We press enter, leave a blank line, which I didn't show you here, but you have to do that at the end of a multi-line expression. And then you get the result out, which is two. Now, this is kind of cool. This is something you really couldn't do. There's this for loop here, where you're using pointers and iterating across a data structure, which incidentally wasn't even in the debug information. This is really where you're starting to see that Clang is letting LLDB do things that you just couldn't do before. All right.
And the real power comes when you start to use this every day and realize, oh, wow, I don't have to worry. So let's summarize. So you can use the expression parser to interact directly with your code. You can use registers, you can use variables, you can use functions just the way you would expect.
You can create your own user variables remembering the dollar sign, just the way you would declare a normal C variable. And you can reconstruct the program state with the help of these type definitions without debug information. You can use full objective C++ in your expressions. Remember that for loop that we wrote? These kinds of features are what's making LLDB the best debugger you've ever used.
So let me show-- so the help expression command will give you more information on the expression command's options. Most of them center around the kind of output formatting I showed you earlier. and in any case, you should just try to explore it. Try it out on your own. See what you can do. Thank you very much for your time. I'm going to pass you on to Caroline, who's going to tell you about Python integration.
Hi there. My name is Caroline Tice, and I'm an engineer in the debugger group. And in this part of the talk, we're going to be going over scripting and Python in LLDB. Now, I know that a lot of you have been using GDB for years, and you've never written a script, you've never wanted to write a script, you can't imagine wanting to write a script. And you're probably asking yourself if now wouldn't be a good time to go get a cup of coffee. Well, the short answer is no. If you miss this part of the talk, you're really going to end up kicking yourself.
We've made scripting in LLDB extremely easy to use, extremely easy to access. We've fully integrated it with Python, and we've added a lot of extensions to make it more powerful, and to make it better for accomplishing some of your debugging tasks. In short, scripting in LLDB is actually going to allow you to do a lot of things you've only dreamed of being able to do in GDB. So let's dive right in and see what I'm talking about.
What can you do with scripting in LLDB? Well, to begin with, it's going to allow you to set some really useful conditional breakpoints. So remember in LLDB, you actually have access to the call stack as an object. This means you can query the call stack and get useful information out of it. So therefore, you can set conditions on your breakpoints based on things like the name of the calling function.
This is especially useful if you're programming with Apple frameworks, which have lots of little functions that are called everywhere, and you really don't care about 90% of the places where those breakpoints are hit. You just want to see the breakpoints where they're hit in your code. Well, now you can actually say, I only want to stop on this breakpoint.
When it is hit, you can stop on this breakpoint. And the calling function has a particular name, or when the calling function's arguments have particular values. If you're doing multi-threaded debugging, you can say, I want to collect information about which thread hit this breakpoint, and I only want to stop at this breakpoint if it's hit by a particular thread. You can trace the execution of a particular thread.
You can even record information about which thread hit it last time and say, I only want to stop there if the same thread hits it the next time, or if a different thread hits it the next time. Anyway, you have lots and lots of options for setting breakpoint conditions on your breakpoints that you just didn't have before.
In addition to setting breakpoint commands, you can also use scripting to help you traverse your dynamic data structures. So you've got a huge tree or a huge heap or a linked list. There's a piece of data that's missing or it's wrong or you want to see if it's been inserted multiple times.
You want to find the path to your data. And you can actually write useful Python scripts to go through your data structures and help you figure out what's going on in your code now more easily. In addition, you can actually record information at breakpoints about your registers, about your local variables.
You can write this to Python variables. You can write it to files using Python system calls. You can do this every time you hit your breakpoint so you're starting to build up traces. You can do this across multiple runs of your program. So this helps you collect a lot of useful information when you're debugging that's going to let you get your tasks done more easily. Finally, you can do this with -- use Python scripts to help you do this.
You can also use Python scripts to help you do automated testing in QA. So especially if you have some of these really evil bugs that you only hit once in a blue moon, you can write scripts to help you collect data around where you think the problem is. And then you can run your Python -- write Python scripts to run your program over and over again, collecting data every time you run through it, and eventually it will hit the problem and stop and you'll have this great set of data to help you do your debugging.
This is just a little idea of some of the things that you can do with scripting in LLDB. I want to talk a little bit about what you actually have where. You've probably already figured out by now that you have Python embedded within LLDB. What you probably have not figured out is that you also can have LLDB embedded in Python.
So you can actually run Python from the Unix prompt, not use Xcode, not use the LLDB debugger, and load the LLDB Python module and be off and running LLDB inside Python. So I'm going to talk about this for just a second because it's useful for some automated QA and testing, and then we'll get back to the main program, which is Python in LLDB.
So if you're going to run LLDB directly from Python, the first thing you have to do is you have to tell Python where to find your LLDB Python module. So that's going to be located in your resources Python subdirectory below your LLDB framework, which is going to be on your, wherever you installed Xcode, usually in your slash developer directory.
Once you've told Python where to find your LLDB module, you start Python at the Unix prompt, you import your LLDB module, and there you go, you're off and running. You can now create a debugger, you can create a target, you can set breakpoints, you can run your process, you can do all of your debugging tasks directly straight from Python. You've never touched Xcode, you've never touched the LLDB debugger. Thank you.
For more examples on how to do this, you can look at the LLDB test suite. We do this a lot in the LLDB test suite, so there are some great examples there that you can look at. These functions that I've highlighted in yellow here, these are part of the API functions that come with your LLDB Python module, and I'll be talking about them a lot more later on in this talk.
So moving back to our main program, which is Python inside LLDB. LLDB contains a full and complete Python interpreter. This means you get all the syntax checking, all the parsing, all the exception handling, all the error handling, the system calls, the modules, the whole nine yards. We have all of Python available to you inside LLDB. We've also made it very easy to access. There are many different ways you can access Python from inside LLDB.
To begin with, you can use the one-line script command. So this is what you do. If you have just a little bit of Python, you want to execute it, but you want to stay in your main LLDB prompt environment. You don't want to drop into Python in particularly. So in this case, I've said to Python, I have this decimal number. I want you to convert it to hex, give me the answer, and let me just stay here in my LLDB prompt.
And that's exactly what it's done. If you want to do more complicated or interactive Python stuff, you can drop into a Python interactive interpreter from inside LLDB. This is going to be just like typing Python at the Unix prompt, except we've already preloaded it. So we've already put the LLDB Python module in there for you, and we've set up some additional extensions to make it easy for you to be off and running doing your debugging tasks. Thank you. And finally, as I said, you can also attach Python scripts to your breakpoint commands, so this is what it would look like to do that.
[Transcript missing]
Once we do that, we're going to initialize our other parameter, which starts out the current path as an empty string because we're starting at the root of the tree. And then we're going to call our depth-first search function. Our depth-first search function, we have to tell Python that it's in the tree utils file that we imported, so that's what that's all about.
And again, we pass it our binary search tree and our search word and our current path. We then print out the path to see did it find the node, and sure enough, it found the node. We then print out the path to see if it found the word "Romeo" in our tree, and the path from the root of the tree to the node containing Romeo is left, left, right, right, left.
At this point, we are halfway there. We have found our word in the binary search tree, so the next question is, why didn't our program find it? And how are we going to figure out what the problem is? And the answer is, we're going to use scripted breakpoint commands. So the idea is something like this. We know that our word is here. We know that a binary search algorithm has two decision points. There's the decision to go right and the decision to go left.
We're going to set breakpoints at each of these two decision points, and then we're going to attach a breakpoint script command to each decision point. The script is going to compare the decision with the path. As long as the decision matches what the path says we should be doing, we're going to continue executing.
As soon as we come to a decision where our decision differs from the path, we're going to halt execution and say, "Here is our problem." So this is what our Python breakpoint command would look like. Before I go over this in great detail, there's something that I need to tell you about.
Whenever you write a breakpoint command in LLDB, a Python breakpoint command, it's going to take the code that you wrote, and it's going to wrap it up in a Python script function. So it actually wraps it up in a function, and it's going to pass in two more convenience variables for you. So it's going to pass in the frame where the breakpoint was hit and the breakpoint location object for the breakpoint that was hit.
Now, there are two important things you have to remember from all of this. The first one is that these two convenience variables are there for you whenever you write a breakpoint script command, so you can just assume that they're there and you can use them. The other thing that you have to remember is if you want to use a Python variable that was defined outside of your breakpoint script command, you have to tell Python it's a global variable.
Otherwise, Python's going to think it's local to the script, and you're going to get unexpected behavior. When you actually hit your breakpoint, when you're executing, Python is then going to call this function. It's going to pass in the frame where the breakpoint location object was hit, and it's going to wrap it up in a Python script. So it's going to wrap it up in a Python script. the correct frame and the correct breakpoint location object for you.
So, going over the code in a little more detail now, the first thing I've done is I've told Python that we're going to use the global path variable that we already got from our depth-first search function. So we're using this variable that we got in one point, and we're using it in another.
We're going to compare our current decision to go right with the path, with the beginning of the path. If the path says, yes, you should be going right, then we're going to strip the first character off the path, and then we're going to resume execution. We're going to resume execution using this frame variable that we know LLDB is going to have there for us.
We're going to call these API functions to first get our thread and then get our process, and once we have our process, we're going to resume execution. So what this looks like for the user is, as long as the decision matches the path, execution just keeps running. The user never even sees this breakpoint being hit.
However, if the path differs from what our decision point is, then we're going to stay halted, and we're going to print this error message for the user. So let's see what this looks like in execution. So we set our breakpoint command on our breakpoint to go left. We've already seen what breakpoint to go right. We've seen what that looks like. We add our breakpoint script command to the breakpoint to go left.
That looks just like the one to go right, except you say left. We continue execution. It runs for a little bit, and then it actually halts and prints out one of these error messages. So we did find a problem. So now let's examine what's going on at this place where we found the problem in our program.
We look at the word at the current root, and the current word is dramatis. We say, okay, well, what are we searching for again? We're searching for Romeo. We say, since the tree is sorted alphabetically, Romeo should come after dramatis, so going right seems like it ought to be the correct decision. But we were going to go right, and our path apparently says we should go left. Let's ask Python one more time. Show us the path variable from the current node to where you found Romeo.
So we use one of these one-line commands to say print the path variable. It prints the path variable. It's still left, left, right, right, left. So surprisingly, we actually hit the problem in the very first node of our tree. We say, okay, what is left, left, right, right, left from here? And we print out the word, and it says Romeo. But, aha, we have an uppercase and a lowercase. There is a case conversion problem in our program, and this is the bug.
So this is an example of just some of the stuff that you can do using Python and scripting in LLDB to help you accomplish your tasks. Hopefully, I've shown you that LLDB makes scripting very easy, very useful, and very powerful. I've shown you that you can use the convenience variables in the API functions. They really help you accomplish a lot of great things.
I've demonstrated if you want to do automated testing in QA, you can actually run LLDB from inside Python at the Unix prompt, and you don't have to touch Xcode, you don't have to touch the LLDB debugger. If you want more examples of that, as I said, you can look in the LLDB test suite, and there's just lots and lots of more cool stuff you can do with scripting. I've just shown you the tip of the iceberg.
Going over what we've covered in this entire session, we've shown you the LLDB command line with its object, act and syntax. We've shown you that you can use help and apropos and the tab key to find all the commands you need to use in LLDB. We've shown you how to use aliases to create simple shortcuts to do what you want to do easily.
We've shown you, you can use the expression parser to execute your code in your own program, and you can even use it to help you debug when you don't actually have debug information there, which is going to be very useful. I've shown you some of the great cool stuff you can do with scripting in Python and LLDB.
For more information, you can go to the LLDB website. The LLDB website has great information about LLDB in general. There's this tutorial for how to do stuff in LLDB that you used to be able to do in GDB. The entire scripting example with all the code for the depth-first search function, all the code for the dictionary program, more explanations than I had time for. It's all there on the website if you want to go look at that.
If you want more information about the API functions or about running LLDB directly from Python, I recommend that you actually download the source code from LLDB. Again, it's an open-source project. And then you look at the header files in the source tree for the API functions, or you look at the test suite for how to run LLDB from within Python directly. And of course, if you want information about Python, Python has its own website. So, thank you very much.