Now on to the final installment of the debugging series. I basically plan to just tie up some loose ends and explain a few things about debugging to you. Some of it's going to get technical, and some of it you're going to find really useful some day.
The first thing I want to discuss is how debugging works. Once you've got a better understanding of how debugging actually functions (it's not magic, I promise), you'll be better equipped to understand situations where debugging is going to fall short.
When you hit the Run button in REALbasic, a "debug" version of your application is compiled. What this means is that the application you run under the debugger is not the same as the application you build to distribute to users. The debug application has a lot of extra code and data built into it so that it can interact with the debugger in a meaningful way. Let's take a look at what changes are automatically made to your application.
The first major change is that your application is littered with extra code that are called debugger entrypoints. These entrypoints correspond to every single line of code that your application executes, and they're what allow the magic of breakpoints and stepping to work. In essence, every debugger entrypoint is a spot of code in which the application can break so that the IDE can do things like step over, step into, step out or use breakpoints -- if the application is supposed to be broken into the debugger, it just sits in a loop waiting for the IDE to tell it what to do next. They're the building blocks behind debugging.
The next change is that your application actually gets a new starting entrypoint -- one that connects back to the IDE. So your application really isn't running until it's had the chance to connect back to the IDE and start communicating. Once that's happened, your user code is actually executed.
So why does this affect you? Well, there's a few reasons actually. The first is: if you are doing any sort of benchmarking, you should not use a debug version of the application because your numbers will be skewed (due to the extra code compiled into the application). So if you're benchmarking, always use a release application to make sure your numbers are more accurate. The second is: the debugger can throw other timing issues off. For example, if you're broken into the debugger in a serial's DataAvailable event, there's a loop happening in the background and so more data may very well be piling up while you wait. So when you call Read, you may get even more data than you were expecting -- not because the debugger is working improperly since the data's not actually filling an internal buffer while waiting, but because the act of calling Read causes the serial control to attempt to read new data as well as buffered data (note: not all controls work this way, most notably sockets).
Another issue that a lot of people forget about is that a debugger causes interactions with the debugged application because they're running on the same machine and events aren't always supressed. For example, when you break into the debugger, it comes to the front, which can obscure your application. So when you resume execution, you'll now get an Activate event and a Paint event. If you're trying to debug those events, then the debugger "gets in the way" since it's actually triggering those events. But never fear, there's a solution for you. These situations are perfect for remote debugging. When you remote debug, the debugger can no longer interact with the application like that, so your activate and paint events only fire "normally" instead of being hindered by the debugger itself.
This concludes my series on how to debug in REALbasic. I hope you found the posts informative, and if you've got questions that I haven't answered, feel free to ask them.
Hi Aaron,
It's been a very good explanation of the debugger with all it in-and-outs. You made the use of the debugger much clearer to me. While doing the hands-on-part i missed a back-button in the head of the debugger-pane to quickly go back to the just left page. Maybe you can persuade Mars to build in such an option if he has a spare moment in the future when he has nothing to do anymore.
Further the whole explanation shows how well you guys at Rs have thought about the implementation of the whole concept of the new RB2005 IDE. Despite of the very complexity of it, it never gets confusing where to find things. Marvelous job!
And THANKS for this splendid explanation.
Perhaps this is a "trade secret", but I'd like to learn more about RB's compiler. Are cross-platform compiles completely separate, or is there an intermediate byte-code format that is then compiled into native machine code? How are classes implemented--are they just chunks of machine code that get dropped into our executables as needed, or are they custom-compiled? Thanks.
The compiler has a single front-end that generates code in an intermediate representation, which is then converted into machine code for the target platform. This is how all modern compilers work even if they only compile for one platform.
I'm afraid I don't quite understand your question about classes. The framework classes are included as compiled machine code, and are linked into your app after compilation is finished. Classes you write are essentially just a bunch of methods that all share a common data structure.
Mars,
Thanks for the explanation. I was indeed talking about framework classes. I'm in an assembly/compilation class that is ridiculously slow and shallow, but it's got my curiosity up...