 I'm at the back. All right, great. Let's start. Fast. Okay, so today's talk is actually gonna be about LLDV. LLDV is something that most of you who are, I have two other partners have probably used, hands up if anyone has not used LLDV before. Like, cool, everyone has, great. That's the last thing I'm asking questions that are negative from an audience, because everyone by default will say yes. Okay. So today's talk is gonna be about LLDV, and usually most people use LLDV, right? Most people will tend to use PO in LLDV, and that's all they'll use LLDV for, which is pretty good, but actually LLDV can do a lot more things, and we're gonna explore some of the things that LLDV can help us do. All right. It's gonna be quite a few demos, so I'll try to be as fast as I can. All right, so let's dig in. So this is today's agenda. First I'm gonna talk about Paul, our good old friend, how it actually works, and some other associated cousins of Paul that LLDV offers. We're gonna then talk about how LLDV switches between Objective C and Swift Context, and we're gonna look into a detailed grasp of breakpoints, what different kind of breakpoints are there, and how you can use them to do more interesting stuff other than just being a breakpoint. And then we're gonna dive into a bit on some internal stuff, like what's actually going on at runtime for Objective C and Swift. In particular, we're gonna talk about the topics, we're gonna talk about calling conventions, and this is quite an interesting thing, personally, I find. And then I'm gonna show off some LLDV pro tips, which you can, some LLDV plugins that you can use that will make your debugging quite cool. Okay, so let's just dig right in. Okay, so everyone loves Paul, Paul the Panda, everyone loves him, right? Okay, so what does Paul do, actually? Paul, if you use it, it's just, you pass it an expression, it'll evaluate the expression in your debugger, and that's it. So that's why we normally will use this as a kind of print statement in our debugger. If we're debugging something, and you wanna print the value, but this is Paul, and it works, right? Okay, actually I'm at the hood, right? It'll evaluate an expression, and the debug output that it shows, they'll call this method on whatever object you pass Paul. There's a method called debug description. Debug description is implemented in Objective C, it's a base, it's actually a base method of NSObject, or I thought it was a method, you can override it, and you can provide your implementation of debug description. If you are using Swift, you can confirm to this custom debug string convertible protocol, and that's what it's called, and you will get, you need to implement this, you can implement this method as well, you also get it for free, there's a default implementation, or you can override and provide your implementation. So this is great, the worst by far for most cases, except until you need to modify the source code. Because let's say now you are debugging some SDK, you've got this black box SDK from someone in Apple, that you need to use, and you need to now add your own debug formatter to it, but you can't modify the SDK, how do you do it? Any ideas, how would you add your own custom debug statements to it? Like one way would be you can swizzle the debug description method in the objects they provide or something, do some runtime hackery, but actually there is a much easier way, and that is using LDB, so LDB allows you to provide, so LDB has like two more ways to for you to print objects. One is P, which is actually just stands for expression, it will just evaluate whatever expression you give it, the only difference is that when it prints it, it will use the LDB formatter to print it, not, it won't actually go to runtime and call debug description. LDB itself has a stone formatter to format objects, and I will show you later on how it looks like. And there's another thing about frame variable, and you can pass it a variable name, which will also, which will not evaluate an expression, but it will just read the value from memory and it will output the LDB formatter description. So the cool thing about this is that the default implementation of this actually shows you all the instant variables of any object, so you can actually take a look at how internal objects, how objects implement certain stuff. But the other cool thing about this is that you can add your own formatter, so actually you can define your own formatter for any particular time, and that's why this is what, if you do this then you don't need to edit the source code. So let's say for example for UILabel, right? For UILabel, if you use UILabel, if you pull it, you'll see a large string which will contain its layer, contain its text, contain a lot of information, and let's say you have a lot of labels and you want to contain some information instead, so you can add a type summary in this way. This is like the one how to write it, and basically when you run P on any label, it will just output this label and pass it the actual label instance. So I'll show a demo of this in a bit, but, and there is actually a whole wealth of documentation on how you can do custom formatting on this thing. So this funny thing I showed you, a label, dollar, bar, it's how you can take, it'll basically pass you the object that you call the formatter on, and there's a million different ways for you to format this, but good luck reading this job, it's actually quite complicated. I tried doing some funky stuff with it, but actually swear how to use it, so I ended up not using it much. So, but nevertheless, let me, before I jump to the demo and show it to you guys, one problematic thing that we often face in every debug with LLDB is, you are running, you are valid in Swift code, and suddenly you pause the debugger, and you type something in Swift, and then it gives you some expression error of says this thing doesn't compile, and you wonder why. And this is because LLDB supports both Swift and Objective-C contexts. What that means is LLDB when you're writing LLDB, you're gonna write expression in both Swift and Objective-C, and whatever the current context is, it will use that context to validate that expression. So by default, let's say you're editing a Swift file, and you put a breakpoint in a Swift file, it will use a Swift context. But if you don't have any file, you just put a random breakpoint, let's say for example, it will actually default to Objective-C. So if you just pause implementation, or let's say you pause when you're doing view debugging, it will actually use Objective-C. So you have to be aware of this thing, about what context you are using. And, yeah, like I mentioned, it will default to Objective-C by default. And if you have a breakpoint, yeah, it will use the Swift file. And actually under the hood, if you know how to help PO and PR implement it, actually under the hood, they are just, they are just, they are just aliases for expression. So expression is the unifying command in LLDB that basically evaluates any expression. And with expression, actually, you can pass it a lot more flags, do a lot more stuff. So for expression, you can specify the language, for example. So if I pass dash L Swift, then I can evaluate any Swift expression inside here to do anything. So for example, I can declare this new variable even. So you can declare new variables in your Swift expressions. You just need to prepend them with dollar for some reason, because I think they were missing basic too much. But it will work. So you can evaluate this variable later on. Just one thing to keep in mind is that the variables are not shared between contexts. So if you declare a variable in Swift, it won't be available in Objective-C and vice versa. And yeah, we can evaluate the, we can use the layout, this L flag to make it a language. And just one last thing to remove the demo, working with addresses. So if you might have been bitten by this before, but in LLDB, if you do this, like let's say this address, right? This is a memory address. You were using new debugging, you found some instance address somehow. And you wanna, you know, actually it's a label. So you just wanna find the text. Then you run this, and if it's Objective-C, this was fine. You can do this, because Objective-C, anything can be an address, anything can be anything. Objective-C is beautiful that way, or not beautiful, depending on your point of view. So Objective-C will have no issue using this address directly, but if you Swift, this will not work. Swift will just treat this as a hex value, then it will complain what the hell we're trying to do. Because Swift is strongly typed. So for Swift, if you want to do the same thing, you have to go through this. You have to pass the address to this function called unsafe-vidcast, which will, you need to pass the unsafe-vidcast at type, and then it will create a label for you, because the compiler believes in that the world is structured, please. It doesn't believe in the Wild West, dynamic world of Objective-C. Okay, with this out of the way, I'm gonna show you a little demo app that I've been working on to de-stress, and there's some bugs, so we're gonna look at these bugs, we're gonna clear these bugs, and our aim over here is to clear these bugs without recompiling the app, okay? So let me show you the app first. There's my Xcode, let's run this, and okay, so is this clear? Anyone can see this? Here I have an app that's very simple. It does something that I really like. I like cats, I love cats, I have two cats at home, okay? So every time I click refresh, it will fetch me a new hidden picture after some time. That's it, that's all the app does, actually. Nothing much, but I like this because it makes me feel better when I'm stressed, when deadlines are nearby, nothing makes the heart feel better than looking at that, like you look at that and you're like, well, the world is a good place now, okay? Okay, but sadly, the world is not a good place. As you can see in my rush to build this, I have a few bugs here, right? You can see I didn't take care of iPhone X, so I need to take care of the safe area. You guys might notice. I'm a bad developer, so I've been in. So this thing is over, this thing is bad, and I need to move this UI somewhere. Then another problem is for some reason, my loading indicator keeps spinning. I need to make a stop spinning after I fetch the image. So let's try to fix these two bugs, okay? So let's try some light debugging, my favorite kind. Okay, so if you look at the implementation, let me just zoom out a bit. Can everyone see the source code? If anyone cannot see, just give me a shout. You can't see? All right, let's zoom in more. All right, can you see now? Okay, great, thanks guys. Okay, so I basically, in my view, I have some constraints. So I have this kitten view, which has the image view, I have the status label, activity label, and the time label, and this total label. This total total label tells me how many kitten pictures I attached. All right, and you can see the bug is over here. Right, here's my bug. I put the bottom equal to super view, which is wrong because you should use the safe area layout guide, et cetera, right? Okay, the bug itself is quite easy to fix. All I need to do is you do this, you to insert this instead, sorry, over here, and then it will be okay. So what I do instead is I make the bottom equal to safe area layout guide at the bottom, and fix some offset, okay? This is quite easy, it's not that hard. But I want to inject this change in without, because let's assume that this app actually takes 10 minutes to compile, because some of our apps do take more than 10 minutes to compile. And sometimes the expert model caching doesn't work as expected, so sometimes it'll stick super long. If you're running an anti-virus, on fact, then it can take even longer than 10 minutes, we had this funny case today where it in turn took 40 minutes for one of our builds. So it happens. So let's say I don't want to recompile this, so how do I do that? So one thing I can try is, and let me show this to you, when I do a fetch, I can create a break point. So whenever I click my refresh button, right, this method is triggered, at this point I can, let me try to inject some code so I can update this thing dynamically, okay? And one way to inject the code is I can create a break point and with a break point, I can add an action. So in this action actually, I can evaluate code. So over here, what I'll do is I will write some code to update my constraint and then I will just copy over what I changed here. This one is a bit hard to read, but it's okay, that's all. And I will take this thing, this little flag, automatically continue after evaluating actions. There's a little flag here. Let me zoom in a bit. Can you see it? Yes. Okay, this, this flag, okay? I can't see it. Oh, okay, okay. So this flag, I will set this to true. So what this means is this evaluating is a continuing, an auto-continuing break point, this break point. That means it won't stop the execution of my app. My app will continue executing after it has used this bunch of code here, okay? So this means, this allows me to inject code. So I can inject code and I don't need to recompile the app for this. So let's, let's put this in, let me, okay. So let's see if this works. Okay, where's my, let me see there. Okay, and it didn't work. There's a problem. Ah, oops, sorry. I mistyped something. My bad. Why did I mistype here now? Shit, sorry, can't do new lines. This is one problem, this is one problem in this approach. You can't have new lines. So my bad, let me give you one second, sorry. No, I should, it should compile it dynamically. All right, sorry, my bad. You're right. Thanks so much for that. We know this. Yes. All right, yeah, silly, I can do more than this. I was planning to showcase this a bit later, but I will just showcase this. Let's say, okay, so I moved this up a bit, right? Let's say I'm not, my designer, let's say is not happy with this UI. My designer says, this looks like crap. I want you to move this around a bit, okay? So please change this, he says, please change this UI and show it to me right now. So in order to fulfill that request, I will, hold on, let me try to move this. Okay, so what I can do is actually I can pause the debugger here. Oh, sorry, I can pause the debugger. So I pause, hold on, hold on a second, sorry. So I don't need this anymore. So I pause the debugger here on a real breakpoint. And let's say I want to, I want to move this around. I want to move this variable around, okay? So one thing I can do, and this is a command that I already played around with, is, what's this label called? It's called tuto label. Let's move it up by, let's say 100 pixels. Oh, sorry, I mistook the arguments, hold on. All right, so let's see that word. Oh, it disappeared, oops, sorry. I moved it down 100 pixels. So they move it up again, because it's at the bottom. This command, this will basically nudge my view up, nudge my view in another direction, sorry. There you go, so I can move this around, like whenever, however I want. So I can show the designer, hey, I move it here. So I move it, so I can sort of do this live by moving it around multiple times and stuff. And if you notice, I'm still on the breakpoint. I actually didn't execute the code. I'm still on the breakpoint, I actually technically still paused, but the view actually re-rendered itself a few times. So I can use this to do some kind of live debugging slash show the user what, show the designer, for example, hey, I'm moving this thing around, you wanna see how, like you can take a look at this. So the way this works, I will dig into this a bit later, how this actually does, how this actually works. But that's not the only thing I can do. And while we're at it, let me fix the other problem as well, sorry, first, the other problem was that our activity indicator never stopped. And that's because I forgot to stop it after I'm done fetching the picture. So over here I need one more stop animating in my success case. So here I have a callback, I have a success case and I have a failure case. In my success case, I forgot to call stop animating. It's a common mistake. So I can inject code here the same way I did just now. So I can, I did this break point. I can make it a non-continuing break point. This one's gonna be easier. So I just need to call e.activityindicator.stopanimating. And this will automatically fix that problem. So let's just continue. And there we go. So, let me remove this break point. There, wait, not yet. Not yet. Sorry, not, why did this stop animating? Yeah, it works, sorry. So now the bug is fixed. So I can, so my point is you can inject code dynamically using non-continuing break points, okay? So one more thing I wanna showcase, another kind of break point that we normally don't deal with often is called a symbolic break point. A symbolic break point allows you to add break points to code that you don't have. Let's say, for example, that I wanna put a break point on UI level set text, okay? I don't have the source code for UI level set text. It's defined UI kick. But I wanna put a break point because I wanna debug something that's happening in some internal system label. I don't know, I don't have much information about it. So one way I can do that is I can create a symbolic break point. So to create a symbolic break point, you go here, I can, hold on. Zoom again. All right, can you see? Yes. All right. So I did a symbolic break point. Then I can create a, I can write a break point for any symbol over here. So let's, let me do UI label, okay. And after I type UI level set text, the break point will appear over here. Okay, it already appeared. Okay? You can see the break point is over here. In, over here. Sorry. You can see this break point, right? There's two symbols here. One symbol is the query I entered. I added, and this is the symbol name it resolved to, saying that it found the symbol name, okay? And as you can see, my query was successful. My break point was successful and I already got a break point over here. Okay? So you can see, I broke, broke point, I put a break point on set text using a symbolic break point, okay? Now, but all I see is a symbol here. This does not make much sense, right? It's hard to understand what's going on here. So our next job is to understand what's going on here. Okay? So actually it's understandable. It's pretty readable if you, oh no, hold on. One second. So sorry. Yeah, I know, right? I managed. Okay, now I'm right. I'm sorry. Dave, break that one. All right. Just now I showcased symbolic break points and yeah. Okay, so let's debug this. This is what I showed you just now, right? So let's try to understand what's going on here, okay? So there's a few, just to get a few easy hack out of the way, one easy way to find out what's going on here is to use this thing. So the LDV will define some custom registers, pseudo-registersies at all, $1, $1, $2, et cetera. These will give you the arguments in the current function column. Okay? So I can use this to sort of get an idea of what's going on. So let's show you what happens if I do this in my current break point. All right, so for example, if I do dollar arc one. Oh, okay. I get something that looks, what exactly is this view? I get some view. I don't know exactly what view this is. Let's see what exactly this is. It's probably a UI view. Yeah, it's a UI level, sorry, it's a UI level. I put a break point on UI level set text. There's some mysterious UI series watch stream view inside there that is calling a set text, that is calling set text. I don't know why, but it's cool. I can find it again. I found a new thing that this thing exists. I didn't know this thing existed before. And I know that, well I know what it is. So obviously arc one is the argument that this method is being invoked on. So in this case arc one is self. Like when you call a selector, when you call object to see method, you need to pass an object, right? To call a method on. Arc one is the object you're calling the method on. So let's try to see what arc two arc two is some weird thing. I don't know what this is. This looks like it, this is weird. But usually in LDP when you see this, usually this means you miss the type cost if you were expecting something here. So because I've read up a bit ahead, I know that this is a selector. So if I cost it as a selector, oh sorry. Okay, there we go. So this is a selector set text. Okay, so this tells me the second argument into this method right now is set text selector. And the third argument makes sense, would probably be the text itself that I want to set. So let's see the third argument dollar arc three. It's this time. Oh, so this is the status bar time setting thing. So this is the status bar view that sets the time. It just so happened that I managed to catch it as the minute was decreasing, so cool. I did not plan this by the way. Okay, so, but what exactly are these values referring to? Let's dig a bit deeper into that. So in order to dig that, dig deeper, we need to look at calling convention, okay? So what is calling convention? Before I go to calling convention, you need to understand how CPUs work. So to give you a very brief summary of how CPUs work, CPUs have this thing called registers, all right? And in x8664, which is the architecture on primary running, Intel 64 bit architecture, there are 16 general purpose registers that are used by the compiler. All these guys, rax, vx, blah, blah, blah, I don't care. Okay? So the idea is that when you make a function call, you need to use these registers in a specific way. The reason for that is if my compiler uses these registers in a specific way, any other compiler can also understand the same binary. Because when, let's say I'm doing a function call in something that's compiled by some other compiler, if the conventions are the same, then the meaning of what the CPU is gonna do is gonna be consistent across both binaries, okay? So that's why the idea behind here is that we should use these registers in a specific way. So let me clarify that idea by looking at C, everyone's favorite programming language, okay? The reason I look at C is because it's very simple. At least Collin can mention that C is simple, C is not simple, okay? So in C functions like, the register ordering for a function argument is fixed. What that means is when I provide arguments to a function and I do a function call, I need to put the argument somewhere, I need to put the arguments in some registers. And the order of arguments has a mapping to which register I put it in, okay? So for example, in C Collin can mention, the first argument will go to this register called RDI. The second argument will go in RSI, third argument will go to RDX. It doesn't matter for you. But my point is, it will go in this order. C will always follow this order, okay? And so on. And if you have more than six arguments, then it will be pushed on the stack, okay? For those of you who don't know what a stack is, I'm sorry. Yeah, I'm not gonna talk about this. Okay, so, so that's C. And like, if I was to look at some C code, right? Let's say I define this C function, ignore this, I'm sorry. If I, let's say I define a C function called foo, okay, because I couldn't find a better name. And it digs three arguments, R1, R2, R3. All right, and it does something. I don't care what it does in the body. Okay? The, and let's say I call this function with these three strings, who ran it down? I just came up with this late last night. Please forgive me. So, the assembly, right? If you look at the general assembly code, if you convince yourself, if you look at the code, RDI will contain foo, the first argument to this. So okay, but this doesn't look at two things. Over here. The first thing is this, call q means call a function. This is the assembly for doing a function call at this address. And this address is the address of this foo function. And you can see by this comment there, okay? So I'm calling a function called foo, define, and this address in my code segment, okay? And the first argument foo is defined in RDI. And if you trace back, it will first load this foo string from memory into this register, RDX. Then it will do a register move of RDX to RDI. So RDI can be foo in the end of the day, okay? This convinces that this is true. And you can see the proof of my point that, hey, this actually is calling in this convention. This convention is fixed, okay? So if you understand C calling convention, you understand everything, okay? Because objective C calling convention is just C calling convention on step, but with an important caveat. So in objective C, every time you do a method call, actually what's happening is it's a C function being called. This C function is called object to C message send, okay? So every time you do a method call, you are calling this function, and this function takes a bunch of arguments. The first argument will be the object that you pass that you need to call the method on. The second argument is the selector. Her argument is whatever other arguments you have. So for example, let's say I set text, this rather controversial statement, and I call this set text on label. What this translates to at the lower level, at the SMD level, actually, is object to C message send on label with this selector set text and this string, okay? And because this is a C function, the calling convention is the same as C function. So first argument is the receiver. Second argument is selector, then the rest of the arguments follow. So you can imagine the first argument, RDI, will be the selector, and this is arg1. So going back to my original thing that I showed you, arg1 is label, arg2 is the selector, arg3 is the other third argument. That's why I demoed this thing. In practice, you don't need to care about the registers because we have those pseudo registers and the calling conventions on different architectures are different, so no, it doesn't make sense to memorize the registers. In ARM, there'll be different registers, but my point is just to make you understand what's going on at a low level, okay? And now we come to Swift, which is a beast. So for Swift, if you found object to C and C boring, Swift will kill you. So Swift, when it uses static dispatch, that means the function is in a fixed place in a fixed-side particle segment, the calling convention will be similar-ish to C. I won't say it's exactly the same because it's fucking complex, okay? And the reason is, I'll explain the reason why, but first thing you need to keep in mind is, remember, I don't know if you guys follow this, but who's heard of ABI stability? Anyone heard of ABI stability? Okay, great. So this is what I mean by ABI stability. ABI stability, part of ABI stability is calling convention. So how your binary, what your function looks like in binary, how it, what's the convention it uses for putting the arguments inside the registers, that is part of ABI stability. And Swift, as of now, is not ABI stable yet. That means you can expect the ABI, the application binary interface, to change with this version, every version onwards. So that's also why when you distribute, and it's a Swift app, it ships with the Swift runtime libraries. Because it's not ABI stable yet, so it doesn't, it can't use a fixed runtime library in the system, okay? So there's a disclaimer here for Swift, like I mentioned, because it's very complex. There's a lot more stuff aside from just registers that I did not talk about. So there's things like memory management ownership rules, because Swift uses ARC under the hood, so there's a little bit to see actually. So who owns the memory ownership rule? Memory ownership rules when you call a function, all that is also defined in the calling convention. Also, whether you need to pass by reference or pass by value, how you lay out binary types. So far, we've just been dealing with more primitive types, but we deal with structs and stuff, it becomes a lot more complex on how you add up your registers, what goes where. This is, there's a whole document on this. There's this very complex document you wanna read and I'll get more information about this, okay? Okay, cool. So that covered. So with that, since we already covered that, I'm also gonna talk about another thing you can use LOD before, which is if you want to reverse engineer stuff. Let's say I want to look up and see what methods are implemented in this library or what private methods are there so I can play around with them and see if they can allow me to do certain stuff that I can't otherwise do, all right? Obviously not submitted to App Store. Okay, so LODB, right, has this thing called image. So image, come on, allows us to inspect symbols and their addresses, so it will help us look at the binary and see everything that's in the binary. Okay? So here's a very simple command. Image, look up, it's an RN, dash RN just means regular expression. So whatever regular expression you pass it, it will pass the regular, because they find everything in the binary that matches this regular expression. So like for example, let's say I look at my Xcode and I'm at a debugger, which is great. So I do an image, look up, dash, RN and let's look at UI label. Let's see all the methods on UI label. And, whoa, that's a lot. So you can see, there's a lot of output here, but my point is you can see all these methods that are there on UI label. This is everything that's defined inside UI label and if I wanna do some funky stuff where I wanna play around with some private frameworks, this is how I would do it. I can find the private framework name here, or private method here, I can play around with it, then I can scribe because I can't use it in App Store, but it's fine. At least we can explore and see how Apple does stuff. And if you wanna reverse engineer some other third party app, or your competitors app, yeah, then yeah, you feel free to do this. Okay, so that's about image. One small thing I will do, or I will move on to the next demo is, so if because we can do this right, we can also look at how objective C works, how the runtime works. For example, one question I've had, I've had ever since I use objective C, was when I came across blocks, right? You think about block, it's a lambda, but where is the code stored? Like when you prepare block, the code has to be somewhere in the code segment, right? But where exactly is it and how do I put a break point at it on the code that's inside the lambda? Like one, let's show you a quick demo on how to, on doing that. So, actually I skipped the demo, I'll just show you the results, I'll do the hack. Okay, so, okay, never mind that. So let's say, assume that I already did the demo, okay? So here's a break point where I have a block, okay? This is interesting, because I wanna show you something very cool that happens internally. If when you look at the internals of a block, right, a block actually is an object, okay? It's an object that's of this type, NS, underscore, underscore, NS, global block. This is one kind of, this is one kind of block object. And it has a reference to a function pointer, and this function pointer is the actual body of the function. So when you call block invoke, actually it will call this function pointer, and this function pointer has a symbol in it. So if you ever see this underscore block invoke thing, this is the name of this block. So you can put a symbolic break point on the block by using this thing basically, and it will work, okay? This is how it identifies blocks. So let's say you have more blocks within this method, let's increment this number here. That's how it basically finds blocks, okay? And yeah, like I mentioned, blocks is subclasses of NS block, under the hood it's a function pointer, and if you notice one cool thing, the function pointer's type will match the type of the block, okay? So I don't have time to go and show you this in live, but I just believe this take my word. Okay, I'll quickly wrap it up. Okay, one last thing I wanna show you before I end, is you can define your own aliases in LDB. So let's say you use a command quite often, you can alias the command, and it's very simple to alias, you can just, so let's say I wanna alias this expression of, you see, you just define all the aliases this way. So that's how I did the nudge just now. And you can use command regs to do some more powerful stuff as well that I can't go into right now, I'm sure it died. How do you wanna persist these commands? LDB, when you stop a session, all the commands will be gone, all the aliases will be gone. Just hook it inside here. This file got LDB in it, just put your commands there, it will work. And finally the coolest thing, LDB integrates with Python. So you can actually do a lot of cool things here, if you have a proper programming language in LDB, which you do with Python. So this is very cool project called Chisel, where Facebook implemented a lot of very cool plugins for LDB. So let me just show you a quick one, then I end, I swear. So for example, I'm at a break point, right? Let's say I want to see the view, I wanna print all the views in my hierarchy, all the views right now, even though I do previews, I can see all the views over here, okay? Then let's say I wanna find an image view. So let's say I wanna find an image view, I can do F view, oh sorry, F v. This will give me all the image views. And let's say I wanna look at the image inside the image view, I can do this new other command called visualize. So I need to get the image out first. So let's look at this. This is the image view I can see. And I wanna visualize the image inside here, which is, I should have pulled the image over. So here's the image, I wanna visualize this image, I just do this, visualize, and it will open the image over here, okay? So there's a lot of different commands. This is the refresh button. If you're curious how the refresh button is implemented, the system refresh, this is what it looks like. It's actually a bit map, and there is, it's actually black in color, it'll use the pink color to change its color. Like I can do all these things because of the LLDB plugins. The checkout chisel, it allows you to do a lot of cool things like this and really empower your debugging, okay? So if you wanna do further reading, there's this very good LLDB section, this time at WDC. I recommend giving that a go. It was inspired this thing and there's a much better demo than I did. Then there's this book that I read, which is damn good as well. It gives you a lot more detail about all the stuff that I mentioned. And other than that, yeah, you win it. Anyone has any questions? It's good for your ask. Yeah, but that is the end of this talk. Yeah, you get that. I think we're gonna take a short break like for five minutes. In the meanwhile, if you have any questions, like feel free to ask as well. Feel free to walk around, grab a snack if you want before we start the next talk. We'll reduce the break time to five minutes maybe to keep the time.