Apple Developer Tools • 48:09
This session is for developers new to Mac OS X and those who wish to learn the latest concepts and improvements to debugging in Mac OS X. Learn how to configure, run, and debug applications using source-level debugging within Apple's IDE. Advanced features of the GDB debugger are demonstrated, along with useful techniques for getting the most out of these powerful tools.
Speakers: Jeff Glasson, Scott Tooker, Klee Dienes
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it may have transcription errors.
For those of you that are at the DevTools Feedback Forum, this time I will try to stay on the stage. Some people were there. That's me. So we're just going to do a little bit of an overview of debugging, and then we'll get into a little bit of detail. Some of the stuff you would have seen before if you've seen the other Xcode sessions.
But I want to go into how some of the features work a little bit. So we'll go over how to get started. I'm going to go over a little bit of tools to show you how Crash Reporter works in Panther, and then we'll go through some UI with Scott Tooker. we'll come back. We'll do some advanced GDB command line work and then we'll have some demos. Thank you.
So what you guys probably-- if you have to use debuggers a lot, maybe you shouldn't be doing what you're doing. So this is kind of-- I kind of look at debuggers as, you know, a crutch for my friend's program has a problem. How can I help my friend fix my problem? So what is debugging? You know, why does it crash? Why does it hang? What happened? box is green instead of blue, or the number is five, or my fractal is weird. But it's all your friend's program, so don't worry about it, don't feel bad.
So, what do we give you as part of Xcode? So, in addition to what you think of as debugging, there's actually a lot of tools on the system that we've kind of talked about in other sessions as performance tools like Sampler and malloc-debug and even the Chud tools, but those are all actually very useful for debugging too. And we will go into a little bit of detail of how to use malloc-debug later. No demos on that though, we're just going to get real nerdy and talk about it. But, you know, if your program is hanging or crashing, sampler and malloc debug can be very useful before you start dropping down into GDB or using the debugger UI inside Xcode.
So, crash logs. If you've got a program that crashes, you probably want to find out what happened and where it crashed. So there's a--most of you guys should know this, but the console application under applications utilities has a preference to enable crash reporting. And that actually gets set on by default, I believe, when you install the developer tools. But there's also another preference you can set that lets you have the crash thing pop up as soon as your application crashes, you'll get a window up here. This has changed a little bit from Jaguar to Panther. So now the logs actually appear in your home directory library instead of the system library. And instead of getting separate windows for each of the crash logs, you actually have a drawer on the side. If you played with the Panther preview, you'll be pretty obvious as to what's changed. So what's in the log? The log has a stack trace for all your threads. It's got the register state for the thread that crashed.
And if it crashed in an area that has debug information, it also has the file and line number. Some of the things to be careful about is if you're writing a program that crashes a lot during your development cycle, these files get bigger and bigger as each crash gets appended to the end of the log and they stay until you delete them. They don't go away after you reboot the system. They are not considered temporary files. So crash logs if you're crashing a lot.
So when I say debugging, I generally mean GDB and debugging C-based programs. And the way that works inside Xcode is there's actually three separate distinct unit processes involved. There's the Xcode UI, there's the GDB debugger, and then there's your target application. And they all talk to each other via different IPC mechanisms to communicate. Xcode and GDB actually are very tightly coupled for a lot of the features as you'll see in a little bit. And then GDB, of course, is going to issue low-level calls down to your application, mock-level calls actually to control and single-step and collect exceptions from your program.
So Xcode is really just the GDB user interface when regard to debugging. Of course, it's a lot more from other sessions, but we're talking about debugging here. So what does it let you do? It let you view threads and stacks and local variables and you can type in expressions and it'll all be displayed. You can set breakpoints. You can basically not type GDB commands and debug your program and see results and interactively iterate your program.
The important thing is all these cool new features that we've been talking about all week like fix and continue, zero link which really isn't a debugging feature, but fix and continue mostly and these new data formatters, these are things that are only accessible via the Xcode UI. Do not try to access them from the GDB command line because they will not work. Xcode also has support for debugging non-C based languages like Apple JavaScript and Java, but we're not going to talk about that at all today.
So what do you need to do to make sure your program can be debugable? If you have a new project that you're creating with one of the Xcode templates, or if you are upgrading a project builder project, the development build style should set the correct preferences for you in your project. And when you click Build, it should just work. If you have an older or legacy project, such as you're bringing in an open source project that uses Makefiles or some other build system that you want to integrate into Xcode. You need to make sure that you pass the correct flags to the compiler to enable debug information generation and on GCC that's -g. And also, you need to make sure you turn off optimization. We do not deal with optimized code right now very well. It may work but you may see some weird things as you're debugging your program. You can see lines stepping back and forth as you say step. So you could get confused.
So one other thing is if you're debugging a framework, you need to make sure that you set the path to your debug framework and not the--if it's a system framework that's installed in system library frameworks, you want to make sure that you've set the path correctly to the debug library and that's in the inspector for the framework--or for the framework in the UI. And please use an absolute path there because otherwise it will try to be a little too smart and you may get the wrong thing.
Oops, that didn't work. So what's new? So I kind of briefly touched this. Custom data formatters you've seen and heard about in previous sessions, if you've seen any of the tool sessions. Fix and continue we've been talking about all week. We've also added support for stopping on catching and throwing the C++ exceptions. And we've cleaned up the debugging UI in Xcode. And hopefully we are minimizing the amount of time you have to type at the GDB console prompt.
So data formatters, why are they good? What they do is they allow you to summarize data. If you've been using Project Builder and debugging and you have a complex data type, you've got all these turn downs and it can take up a lot of screen real estate to show your either class hierarchy or substructures or elements in your structure. So you can actually create a formatter to display that what you care about on one line. We also support custom plug-ins for the data formatters. The way it's architected right now is you need to have a function that returns a value, which is great for Cocoa. But Carbon APIs tend to pass things by reference, return values by reference, and return an error code as the return value of the function, which you really don't want to display the error code. You want to return display the actual data. And so we have a plug-in architecture that you can write your own for custom things, or we actually supply a bunch of system types. And I will kind of encourage you to attend tomorrow morning's 9:00 a.m. session bright and early after the party tonight, where Chris Espinosa will go over that in detail.
You can also annotate things with text. So if you had--I think you saw this in Ted's session earlier in the week if you were there where you can actually say, you know, this is my x coordinate, y coordinate, et cetera. So it's very easy to see at a glance what's going on. There's also some expression evaluation that can happen.
So, important caveats or things to be careful or cognizant about is the formatter itself is actually bound to the type of the object that you're setting the formatter for. It's not just that instance of the type. So if you have a local variable that's an NSString or an integer or whatever and you set a format string on it, that format will be applied to all displays of integers or NSStrings in your entire debug session. Maybe sometime we will expand that, but right now for this release it's going to be single formatted string for each type.
And we actually have 35 formatters for Carbon system types right now. That list can grow. That covers most of them. We're also shipping a bunch of base Cocoa foundation formatters. So out of the box, you will actually get a fairly good experience of these opaque types that Carbon tends to pass around. Oh, not working. So I wanted to bring up Scott Tooker and he's going to give you a little overview of the UI and how it's changed and why you want to use it.
So what we're going to be looking at today is Sketch. It's a common, very well-known Objective-C example we ship on the system. And just to start, let me just bring up the breakpoints. I've actually set a breakpoint in SDK graphics so that when we go to draw a rectangle, it'll break. And so let me just go ahead and show you. One thing that we've had, we had in the old project builder, we continue to have an Xcode, but people ask from time to time is, how do I set up a symbolic breakpoint? I want to break on rays, I want to break on a function name where I don't necessarily have the code. Well, if you go to the debug menu, show breakpoints, you can actually just click new breakpoint and then type in, And then that's actually just passed directly down to GDB when they set a breakpoint. And it's easy as that. You can obviously just hit New Breakpoint, get additional ones. We also, in the breakpoint columns you see, we sort them by the file they're in and all that.
So let me go ahead and bring up-- So first I just wanted to go through the interface real quickly here. This is the default layout. You'll notice, so here we have your threads, we have the summary, and we have the editor. And here, let me actually just get it populated with data. And so what you'll notice here is that for areas that we have symbols and we have access to source code information, we will show you in black. And so, for example, if I go to, like, NS Application, well, I don't have AppKit source code on my machine right now, so that's in gray.
And also you'll notice that because we don't have debug symbols, we don't have any pop--we're not populating over here. But when you do, we'll show the variables over here and we'll show the current line we're on. This is the break point, is the gray part. The red point is actually the PC and I'll get back to that in a little bit. So what I'd like to do here is show you the different--let's show you the other debugger layouts. We got feedback that some people really didn't like this style of layout. So we actually have two that you from. In this one, for those people who either you have long function names or you have a lot of variables, you can do it this way. And either one, we actually have this little widget here that controls all three windows. Because we got the feedback that, oh, you know, it kind of sucks when I have to drag one way and then I have to find another widget to drag out the other way. And I really want one place to just control all three panes. And so we added that.
It really is the small stuff. It's amazing. So the other thing I wanted to show you is the console drawer. So in Project Builder, we had this little console drawer. Fortunately, small screens. Let me pull it up a little bit. And here it is. You can get access to GDB. But we did get feedback that some people were like, oh, I don't really want it in a drawer. I'd rather just have a separate window. It's a lot easier for me if I'm going to do a lot of GDB stuff to make it really big. So we now have its own little window.
So let's go ahead and hit Continue. Let me just bring up a-- don't save. So let's bring up a new file. So what I want to show you now is I want to go into a little bit about how the data formatters work. So for this example, I'll go to my breakpoint again. And let me actually-- I want to shit-- switch back to the other debugger layout. I prefer the other debugger layout. Sorry.
So if I was really cool, I'd be using demo assistant. And if I was a really good typist, I'd just type it in. I am neither. So I'm just going to copy and paste. And so what you've got here is I'm going to go through these in order. This first one, what I'd like to do is a lot of times with Objective-C, you can run into retain release problems. So I want a quick way in the debugger to see, OK, what's my retain count on this object? So what I'm going to paste in here basically is saying, OK, I want to get an integer back. And basically, I'm asking the variable self, what's your retain count? So if I return that, it comes up as one. Well, that's OK. But it's kind of confusing, because one what? So I guess one thing I could do is I could just add the retain count.
Well, that's great too, but the nasty thing with self is, well, what is self? I can't tell from here. I mean, I could go down and I could look for isa and all that kind of crap, but that's lame. I want to be able to do it right here.
So let's go ahead and add this beginning part. And I'll just stop here for a second and just point out that what I'm doing is I'm basically asking the isa for the name property. And then I'm saying, OK, I want to return this as a string, and I'm just prepending that to the front of what I've done here already. So now you see I get SDK RangTangle, and you're like, oh, that's great, Scott, but does it really work? What if I do something different? So let's go ahead and hit Continue.
There you go. So now I've got this nice little formatter that for self on all these SKT graphics, it's going to show me, oh, here's the class, and here's the retain count. And we think this is going to be very useful for people when they need to get custom information quickly when you're in a tight loop, or you just don't want to have to dig down deep and open up all the little twiddle triangles to see what you really want.
And at this point, I believe I am done. Take it away, Jeff. JEFFREY POSNICK: Thanks, Scott. So that actually was a pretty good, very simple demonstration of how powerful the custom beta form writers can be. We're looking for feedback on how we could improve them, adding different format strings, what you think would be useful. So any input would be greatly appreciated. So now I want to spend some time talking about fix and continue. So you've seen it. What is it? What does it really do? I mean, why do you want it? It really is debugging without stopping your program. And probably the most important use of this is you've got a program, you've been debugging it for hours and you find a problem, it'll take hours to get back to that spot in your program run again. You don't want to take that time. So you can make a change, continue debugging, and you save that hour or two that it would take to get to the state that caused the problem.
So it's very important to note that this does depend on tight integration between Xcode and GDB. Xcode is telling GDB which files are changing, which binaries are being recompiled and how it--and how--and which source file it's associated with. So, again, I will say it again and I will say this many times, this will not work from the command line. And also, this is not the panacea of changing. There are a lot of things that just won't work. And I'm going to go through how this is implemented at kind of a high level in the hope that it will help you understand the types of changes that won't be allowed. Other thing, this only works with GCC 3.3 because of some compile code generation options that we needed to have. And it does work with zero link enabled. So you don't have to worry about what your zero link setting is for this feature to work.
So actually I covered this out of order, but yes, it's--the other thing that this is useful for in addition to taking a lot of time for long-running programs is for certain types of things you can do prototype style development. You know, if you're debugging, you can do quick changes, modify the behavior of a class or procedure and just keep going until you get it right.
So how does it work? So here we have a simple, simple program with a main and two subroutines. I chose C just because it's easy to show on the screen, but Fix and Continue works with C, Objective-C, and Objective-C++, and C++. So here we have main making a call to foo. Let's see if I get these clicks right for all the animations. This is going to be fun. So foo runs, and you find a problem in foo. You're in your debug session. There's a problem. What do you do? So you go and look at your source.
So then you're going to edit your file, you're going to recompile it, and click on the little tape dispenser fix button. And what that does is it tells Xcode to tell GDB to reload that file. And what actually is happening is GDB is actually loading another little bundle, which I'm calling fixed foo, in addition to the old version of foo, which is in memory. And in addition to it loading that, what GDB is then going to do, oops, here we go, is it actually scribbles on the first few instructions of the old foo routine to point to the new foo. Now, you might think, "Why should I do that? Why don't we just, you know, get rid of the old one and use the new one?" Well, you could have a function pointer to foo that you still want to have it be valid. You could have it stashed in a global variable somewhere. And so, we actually keep all the old data--or the old pointers valid.
So after that scribbling happens, actually I should point out before I go on that we also do fix up references to static global data also at this time too. So if you're referencing a global that happens to be in that module, the right thing should happen. So let's get back to the flow here. So now I'm later on in my program and Barr wants to call foo. So it actually is going to jump to foo, the old version of foo, because we don't want to patch all calls to foo to point to the new one. That would take a lot of time. So it's going to vector through that little jump thing that we scribbled at the beginning of the old version of foo and end up in fixed foo. And then that's going to run-- oops, come on.
And it comes back and returns just like Foo would have. So that is kind of a very simple version of what's happening. And I see the engineer that implemented this has got a big smile on his face saying, I simplified it way too much. But we can deal with that during the questions later, because that would take a long time to really explain all the details. So limitations. So it doesn't support all types of changes. I mentioned that earlier. What we are hoping and what we will be doing over the process of--releases of Xcode is that we want to have common unfixable changes flagged by the IDE. And again, do not try this from the command line. It just won't work.
So what doesn't work? What types of things aren't going to work? So if you're going to be changing structure or class layouts that actually is going to cause different code generations or references from other pieces of your code, that's going to run you into problems. Changing function signatures, again, because you're not recompiling the whole program. You're just recompiling pieces of the program. Adding global or static data in the module, since the rest of the program won't know about it. you won't be able to access it.
Anything that needs an initialization, like static initializer, either C++ or C, because the initializers won't get run when this thing gets reloaded. And again, virtual member functions similar to changing class layouts for the same reasons. And also in addition, if you're trying to modify a function that is at the top of the stack currently active, changing local data is a bad thing to do also because you'll be changing the stack frame that's currently in memory and that would confuse a lot of things. And also, if you are adding something that gets initialized like, you know, even like I equals 5 earlier in the function than where you are and you want to reference I and expect that value to be set, that won't happen because we continue execution from where the PC was before.
So what does work? That sounds like a lot of things that don't work. But in fact, a lot of things do work, as you've seen all week. Common logic errors tend to be something that are easily patchable. Off by one errors, I forgot to multiply by two, et cetera. All those things should be fixable.
You can also add new calls, add new logic to existing routines, no problem. You can delete code. You can change constants, string constants, integer constants, whatever. And pretty much any other common operation, you can try it and if it works, great. If not, Whoops, come on, come on. Flip, flip. You can always, if the fix fails and the IDE tells you the fix failed, you can always rerun your application from the beginning. And it's our goal that over time we actually increase the amount, the types of changes that are allowable to be fixed and we get a lot better doing error detection if it finds an unfixable patch. So I bet you can find some things that crash your debug session today with the preview, But again, we appreciate feedback if you find those and have something that's reproducible and then we can fix it either for the GM or for the release after. Thank you.
One caveat with the preview also is right now if you do a bunch of fix and continues and then want to rerun from the beginning, the build system isn't quite correctly hooked up so you will actually lose those fixes if you rerun because it doesn't know it needs to recompile. There's actually two different Dottos that get generated. It's kind of a bundle that gets fixed as opposed to your regular Dotto that would get linked in or zero linked into your application and that'll be fixed for the GM. But right now, make sure you click build and run instead of just run after you do a fix and continue session.
So again, I'll bring Scott back up, and he's going to show some more fix and continue. So already you've probably seen this several times now. And you'll notice that one of the themes before was we were showing it without showing you the debugger active at the time. We kind of hit off the debugger. And you were just editing. And you do a fix. And ooh, you're all excited. So what I'm going to do is now focus on, well, what if you're doing a debugging session, you're paused and you want to do a fix? What's the experience like there? So what I'm going to do is I'm going to start debugging Sketch again.
And let's use that same breakpoint. And so what I'm gonna do, so we're stopped and we're about to draw. And so what I'll do is I'm just gonna comment out some lines and uncomment out some lines. So we're gonna change the color to red. And let's see what else. And let's change the stroke line width to 8, for example. And so I can go ahead and do fix, and then I'll continue.
OK, so I got the stroke line width, but I didn't get the red. So as Jeff pointed out, we remember where you are in the function, so when you fix the function, we'll take you back there and continue on. So one thing in the demos that we've done before is we've done things where it's always refreshing. But just something to remember, when you're in the debugger, that we're just going to continue on from where you are. So that brings up the next thing, which is, well, what if I wanted to have it actually get that fixed? Well, it would be really cool to be able to move the PC, wouldn't it? Well, guess what?
So let me go ahead and let's just to show that something's actually going on here. We fixed it. So now I'm going to change the color to green. and I'm gonna go ahead and I'm gonna fix. But let me take this to say there. there. There. And let's continue. Oh, continue. Mer. Yeah, this is what I get for being cocky.
Yeah, this is-- yeah. OK, well, we'll do one more fix. So let me go ahead and do the fix. Good. So let me-- oops. No, no, no. Continue. Continue. Continue. Continue. Continue. So what we're going to do-- let me just-- let's just catch up here. So let's close this file just so we can get rid of the clutter. Open up a new one.
Okay. So for example, let's change it to, let's change it back to, let's change it to black. So we'll just get a black one. So just to show you that I really, I'm not using all this to just divert the attention, but so I'm going to fix it. So I'll go ahead and fix this. And then I'm going to drag the PC back. And so then I'm going to say, and I'm also going to just for Disable that, and let's continue. There you go.
One more thing we wanted to show you at Jeff's request is there will be several times when you do something and it doesn't fix. So we'd like to show you a case of where that actually happens. So when this comes back as failing, it's supposed to fail. So I'm going to go ahead and I'm going to turn this breakpoint back on.
And then let me go up here, and I'm going to go up to the top. And so as Jeff pointed out, I really shouldn't do this. But I'm not listening to Jeff because I know what's best. And I'm going to fix it. Error. In the future, we want to have a much more apparent-- well, for one thing, we want to show you the entire error and not just cut it off.
But we'd like to have a panel or a sheet come down telling you that a fix has not completed. And over time, like Jeff said, we want it to have more feedback that, OK, this isn't going to work, so you don't even get in these situations. And at that point, back to you, Jeff. JEFFREY POSNICK: All right, thanks, Scott.
I seem to have been talking very quickly. So we're going to have a lot of time for questions at the end, which is good for this type of session. So another couple of things that we have added, you've seen this I think in one of the sessions earlier in the week is we've added support for stopping and catching and throwing of C++ exceptions, which is well--an often requested feature for C++ programmers. It's a very simple UI to show. I don't know if you caught it when Scott had the debug menu pulled down, but there's two menu items under the debug menu, stop on C++ catch, stop on C++ throw, and then it just stops. You get a stack trace, you can see what's going on. You can also do a little bit more sophisticated things from the command line, which we'll talk after, or Cle might actually cover it when he's talking about the command line.
So I think I talked about a lot of these caveats already, but I'm going to go over them again. The first one I didn't mention, but yes, the custom formatters are really cool. If you notice, they actually have a lot of code in them or can cause a lot of code to be executed in your inferior debug process. So if you have hundreds of them on your screen, you're starting and stopping your process 100 times and getting a value back to GDB. This can cause single stepping to be slow. And you may want to disable them if you're stepping and then re-enable them when you care. Also, it's important that since you are actually executing code in the debugged process that you do not take code that might cause a deadlock to happen. So if, you know, you're debugging some graphics thing and a core graphics routine in the system has a lock held and you're waiting for that lock to--as part of your data formatter, that could be a problem depending on when that data formatter is called. And also, for the preview release right now, and we hope to improve this over time, large zero-link applications could slow down your debugging. And what I mean by that is times to display the stack or times to single step as it's dynamically faulting in the modules as needed. And it could take a few seconds right now. You may see that and we're going to try to improve that for the final shipment for the GM. So with that, I want to bring up Klee Deans, who's a senior GDB engineer. And he's going to talk about how to use this stuff from the command line.
Hi. So for the most part, the things that we do on the GDB command line are things that we hope to have be in the background. We want them to be things that you don't pay too much attention to. So for the next five or ten minutes, I'm going to take those things into the foreground and hopefully tell you one really cool feature that we've added and then a couple of neat things that you've always been able to do but you might not have known you were able to do.
So our new feature is cache symbol file support. This is basically a feature that's designed to improve the startup time of GDB by letting you take large symbol files and have them get stored on disk so that when GDB goes to read them, instead of reading them, you'll see little dots go across your screen as the symbol files get loaded. The goal is to have it instead of being dot, dot, dot, it should just be dot, dot, dot, dot, dot, dot, done. A lot of people have said, hey, we really wish UDB startup could be faster. We'd love to make it so that it's always instant, but the least we can do is make it so that it's a lot faster the second time you do stuff. It's just a cache. It doesn't change anything about how it processes symbols. It should do everything exactly the same. The only goal is to make it start up your program a lot quicker and get you debugging a lot quicker. The way it works right now is when you install new developer tools on your system, it builds a cache for all the system libraries the system. And it'll go ahead and use those. It'll build those just once.
You can optionally tell it to create a cache for your user binaries. I'll tell you how to do that in a few minutes. So this is the first time we've shipped this. There's, as always, a couple of things that can go wrong. The very first one is sometimes those caches can get out of date. The way you'll know that they're out of date is you'll see the big warning. It won't be red on your system but it'll be there and it'll say precompiled symbol file is out of date.
Don't panic. All it means is that it's going to take another couple of seconds to load the symbols. It should be exactly the same as if they weren't there at all. You can, at any time, you can update them. There's a command. I don't know if you get the slides on this but it's, you know, sudo or sudo and there is a command user lib exec gdb cache sim files and that'll regenerate the whole cache for the whole disk. Similarly, if you think something's going wrong, you just don't trust them, for whatever reason, you can delete them whenever you feel like. And you can recreate them later if you discover that it wasn't actually that that was causing the problem or whatever else, or if you just want to update them.
So, the reason that we didn't make this always the case for all binaries is that, A, it's a new feature and we want to get some use of it out in the field before we make it the default for everyone. But also, that sometimes you don't want it. So if you have, for example, a lot of binaries that don't change very often and you're just changing one thing and one part of it, like you have three frameworks in an application, it's great. the symbol files for the libraries, start up the application, everything's quick. If you have one giant, massive binary, it might not be so quick necessarily because there's a little bit of overhead in creating the cache. So if you just had to regenerate that cache every single time, it might take longer to regenerate the cache than you'd say by starting up. So to get around this, for right now, we've made it something that you can set on a per project basis. So basically in your per project directory, there's a file called.gdb init that will control how GDB behaves. And if you set the variable generate precompiled symbol files to one, it will go ahead and create those cache symbol files for every framework that's loaded by GDB as GDB sees it. So the idea is that the first time you've debugged a framework that you've written or some other object that you're writing, it It'll take another second or two to load it up, but after that, it'll just be dot, dot, dot, dot, dot, just like we're hoping. The cache files are stored in a directory with your project called gdb_sim_file_cache. If you want to disable it, you can just remove those files. If it's project builder, there's a cool thing. Since project builder stores in its build directory generally all in the same place, those will be shared among the project builder files, which I think is kind of neat.
So, you know, I'd love if you can try that out. You know, be sure, please let us know how well it works for you, if it's really affecting your performance, if it's really improving your performance. We really hope it does. So now onto a couple of neat features in GDB. These have been there for a long time, but they're still really neat. The first one is watch points, which is basically a way to have GDB stop when some expression in your program has changed. So the most useful part of this is when you want to find data value has changed. Typically it's for finding memory crashers or something in this library is changing some data variable of mine and I don't know where it's getting changed, I don't know how it's getting changed, but I sure know that it's bugging the hell out of me that it got changed and I want to know where. So what you can do is at any point you can set a watch point on an expression.
It can be any expression that's valid in your program. Ideally it shouldn't be something that calls function calls, but any valid C expression is a valid watch point expression. And what it will do is GDB will protect the memory pages that are involved in that expression. And whenever they get modified, it will stop, it will check the expression, check the old value, and it will see if it needs to report the value has changed. A couple of caveats is, you know, if you change that value all the time, GDB is going to be stopping your program all the time. That's going to be slow. If it's changing it constantly, particularly if it's something on your stack, stack, because everything's on your stack. That's going to be getting modified constantly. It's going to be stopping constantly. It's going to have to start the inferior over and over and over again. That's going to be a very unpleasant experience.
But for things that are in global data, things that aren't accessed constantly, it's a tremendously valuable feature, particularly for, I don't know what's trashing my memory. So sort of the classic example of that is to watch a buffer and say, who changed this buffer? And you'll see in In my example, there's this little, cute little curly brackets around stuff, which some of you have probably seen before, some of you haven't. What that's saying is just take the variable buff and treat it as if it were an array of 1,024 characters. So that's basically a shorthand for tell me when anything on this page has changed. I want GDB to stop, tell me when on the page has changed, and give me a backtrace so I can figure out exactly how I got to what was doing that.
Another very useful feature is breakpoint conditions. It's basically the ability to add a conditional expression or a set of commands to any breakpoint or watchpoint. So what that lets you do, for example, in the case of watchpoints, is add extra conditions. So you can say, "I not just want to stop when this is changed, I want to stop when it's changed to 10, or I want to stop when this pointer has become invalid." Or, you can use it for all kinds of crazy features. In the second example, you'll see what we've done is we've stopped on the open call. So we said, you know, whenever I open a file, no matter how, whatever reason, I want you to stop, and then I want you to tell me what file I opened. So since the argument's always going to be in the first argument register R3, we can just have it print out the file that was opened, cast it to a string, and continue on the way. So what you'll see as you're running your program is just a big output dump of, you Open this, open this, open this, open this, right down the line. I'm sure there are other ways to do that exact same thing, but it's neat that you can do that without having to use any special features just by setting breakpoints and putting logs. And some people have done some very impressive things with just breakpoint conditions to log their programs. While we're on the topic of memory smashers, I want to talk a little bit about some other tools that are out there for debugging memory smashers.
One of the best being malloc-debug, which is a tool that is designed to help badly behaved programs crash for you. So what it will do is it will overwrite freed memory after it's been freed, and it will add guard pages before and after malloc regions, so that when you free something, if you go to write to it later, it will occasionally check to see, hey, did you just write on something that you shouldn't have? Did you just overwrite the buffer? Did you write before the beginning of the buffer? And it'll generate logging messages for you. The main reason I'm mentioning it here is that it actually works really well with GDB. If you run a program both under malloc_debug and GDB at the same time, you can set breakpoints at the malloc_debug output routines. So if you set a breakpoint at, say, for example, malloc_printf, malloc_debug will stop in the debugger, let you get a backtrace of exactly what's doing the wrong thing and let you do normal debugger operations on that. So you can look at the backtrace, find out how you got to the thing that's trash in the memory and go about fixing that. For more information on that, you can just check out the Help menu in the malloc-to-bug documentation, and there's actually a pretty sizable chunk of stuff there on how to do that.
The last one is on the topic of connecting up to things that you're running or something else is the GDB attach command. This is something that I know we'd love to have in Xcode. It isn't in there yet, so for right now, you can pretend it is by doing it from the GDB command line.
It's a very simple syntax. You just basically say attach and then the name of the program or the name of the program, follow the process ID or just the process ID by itself. And what GDB will do is it will connect up to the program you're debugging, and it's pretty much just as if you ran the program under GDB, except that you could run it under something else. And this is really great for programs that are hard to attach to. Say they might have a complicated startup routine. They might be, you know... expecting to be double clicked from something or a plug-in from something.
We use it for debugging in the Finder. It's a really handy way to hook up to something without having to figure out all the complexities of how to get it started exactly the way it expects to get started. It's also a very handy way to hook up to something that's, say, running under malloc debug or running under sampler or running under both at the same time even. The one thing that people have problems with is, well, how do I debug something that's right in the very init code of this thing that I'm double-clicking on. The easiest way to do that is you go ahead and just stick a sleep call right in the beginning of your program. And what will happen is when it runs, it will start sleeping. When you attach, you'll be stuck at the sleep. You can say continue, and it will just continue right on from there, which can be pretty handy. We also ship a command line version of the sampler tool, which is basically a handy thing that you can run, sort of like command line GDB, it's a command line version of Sampler. So syntax is pretty simple. Sample, you can give it a process ID, just like GDB attaches to, and it'll write it out into a file in /tmp that you can look at later. I'm told that if you play some games with it, you can actually read that into sampler.app, but you probably want to check the release notes on that to know how that would go. You can also use that just like you would use gdb attach, so if you've got something that's already running and you want to know how it, you know, exactly how it's performing, instead of having to figure out how to start it under sampler, you can just attach to it just like you could with gdb. And back to Jeff.
Thanks, Klee. So, just a couple more things to wrap up and then we'll go over to Q&A. We're not too short. So, you know, one of the things I want to get across is this is--we've done a lot to try to improve your debugging--your development productivity by improving debugging. And yeah, I made the joke at the beginning, but we all write bugs and the best way to fix the bugs is to find them as fast as possible. And the tools are very important to do that. Fix and continue, it's great. Play with it as much as you can. Feedback, definitely appreciated. We know there's problems with it. This is a first shot at this technology. We want to make it as robust as possible in time for our GM when Panther ships. Custom data formatters, you saw kind of just a real quick, brief demonstration of how powerful that can be. But remember, lots of them can make your debugging experience slow. And then Klee talked about cache sim files. This again, it's brand new. One thing I don't think Klee mentioned was that we actually, at install time, generate cache files for all the system libraries. So these are actually there by default. Klee was talking about how to generate the caches for your own private code.
So how to learn more? There's documentation in Xcode from the Help menu. Matt Morse has shown that a number of times this week already, how to access that. And we've actually spent a lot of effort on that too. Search, Look, man pages for GDB are on the system. Release notes for the compiler actually are helpful for some of this stuff. So make sure you get the right settings for debugging. And standard documentation, which don't need to go through. Who to contact? Godfrey DeGiorgi. There's his email.
Email should go to him. There's also the Xcode feedback mail list and bug reporter for bugs. Please, please file bugs. In the past, people have complained about GDB and the debugging experience and have not filed bugs. And we look at bugs, we fix bugs. We don't fix bugs if we don't know about the bugs. Or if they just appear in a mailing list at some point, they will be forgotten when we're going through a bug list. So please, please file bugs in Bug Reporter. And the mailing lists do provide lots of information and help for people.
So it's the end of the week. There still are a couple more sessions I need to plug for Chris in the morning, 9:00 AM, bright and early. He's going to go over debugging and tuning Carbon apps, and he will spend a significant amount of time talking about how to write custom data format or plug-ins for Carbon types. And please attend if you're interested. And there's also a discussion on software test tools later in the day Friday. So with that, I would like to bring up Godfrey to keep me under control and everybody else and the GDB team and the rest of the QA panel. If our QA panelist can please come up. Thank you very much, Jeff. That was a great job.
By the way, a comment came up earlier today that the Xcode [email protected] address was not accepting email. If you send to that email, please copy to my address above that. That way, that's a good test for me to see whether it has actually gone to the mailing list.