Rocky Bernstein
unread,Nov 30, 2012, 10:04:54 PM11/30/12Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to ruby-d...@googlegroups.com
A number of questions have come up and there has been inline comments and back and forth.
Rather than more inline which can lose track of bigger points, I'd like to try to cover some of this here. In particular:
* Location
* "interesting events" and
* debugger and programmer interaction
One simple bug important piece of information a programming language can convey is where a Ruby program is at some point in time. Maybe that point in time is before the program died, or maybe that point in time is when you are stopped inside a debugger, or maybe it is in are gathering some sort of report information that might be used in a code coverage report.
The programmer would of course like to think of the location as some place in the source code, even though what's currently running is some implementation of that which is only guaranteed to match the semantics of the source code. A compiler or interpreter is free to run the statements in any order, omit some of them, combine parts of separate statements into common code and so on -- as long a the semantics stay the same. In Ruby and most interpreters, code rearrangement or code combination doesn't happen all that much that it is generally that confusing.
Another aspect of most Ruby implementation is that they record location only to the granularity of a line, rather than say (ranges) of line(s), or column(s), or character offset(s) from the beginning.
Furthermore the boundaries at which position information is stored in Ruby and most other programming languages is at the point where a new statement occurs. More granularity would occur for example if position information were put after each expression.
Although I would greatly welcome more fine-grained location information getting stored in the runtime, realistically it isn't going to happen.
So now let's come back to the question of how one precisely indicates where you are in a program. The most accurate and honest piece of information is to give the program offset. This by itself most programmers won't find useful at all, no matter however accurate or honest it is. Fortunately though what Ruby implementations do underneath is use that offset to find some plausible line number and report that.
As I've written before, a problem with line numbers for positions is that there can be a lot of stuff going on in any given line; it sometimes is helpful to pinpoint that. The example given previously is which of the first or second parameters were we working on if a ZeroDivison error is given in:
f(a/b, c/d)
I have thought about this a bit and solicited suggestions from others. The only solution anyone has come up with is to give a line number and a program counter and then give a dissasembly around the area of the program counter.
All of this is really easily and reliably doable. (Well, the line number part might not be, but let's skip that subtlety). If one also wants to try to decompile the disassembly, okay. That is less easily doable but often something can be done there.
Those that don't understand or care about the VM instruction set are free to ignore it; perhaps this additional information is only given on request. But for those who really want more information such as myself, the disassembly (along with the usual container and line number information) gives me exactly enough information to know exactly where the program was at some point in time.
And so that leads to the relation and interaction of the programming system and programmer.
I realize that most people who use debuggers are somewhat novices. For them, the level of sophistication in using a debugger tool may be a little more limited than someone who knows or is willing to learn a lot about the system they use.
But this shouldn't come as a surprise. Many Ruby programmers will do very sophisticated things in Ruby and many programmer may use Ruby in only the simplest way. Ruby accommodates both of these.
For myself I want to provide debuggers that will make what's going on as transparent as possible, even if the details are a bit involved. Some features like stopping at locations that are not at the start of a statement boundary may be a little bit too intricate for some. But let me also say that I am not alone in wanted to be able to stop inter-statement. In the Euruko 2010 conference when I gave a demo someone in the question portion basically asked about this aspect.
Anyone who has used gdb on optimized code understands that things can be a little weird. gdb even allows one to step through assembly instructions and although I've never made use of that, I am glad to know that it is there.
But I have made use of using gdb to debug optimized code and have found and fixed problems even though things were a little weird. To be told "Sorry this is too complicated for you, so you are not allowed to debug anything here" is not something I would appreciate. And so it is with the debuggers I write.
In particular, the trepanning debuggers allow you to see a dissassembly of code (and even colorized courtesy of coderay) wherever you are stopped. Of course no one is required to look at disassembly. But when the debugger shows you a location, it also shows the PC of that location. At one point I had this shown only as under an option, now it is done unconditionally
Even if you don't care to understand about PC's, you might still simply pick up that the number of the PC is different, even when the line number and file name might be the same when stopped two different places. And possibly you might pick up that when a PC is greater it probably indicates a place that is further along the line than another place on that line that has a smaller PC. (Of course to really be certain, I'd disassemble around the two PCs)
Just as Matz has said he designed Ruby to please him, I write debuggers to please me. I don't treat the programmer as an ignoramus that is to be shielded from what is going on underneath since it might confuse him or her. I don't treat myself as an ignoramus who needs to be shielded. I have not gotten complaints to date that the location display is too complicated.
I also don't treat myself as someone who can't be allowed be able to look lower level information like the VM stack, or not to be trusted to be able to change any of this.
Sure, I make mistakes - that's why I write debuggers! But if I decide I want to change lower-level information and make a mistake in changing a VM register, I take responsibility for that -- my bad!
If I want to protect myself from doing damage to myself or want to run a system that I want to protect from others doing damage, I can run a version of Ruby that isn't debugger enabled and make sure to uninstall all debuggers including removing debug.rb which comes installed by default. (Or perhaps set $SAFE). But in truth I've never had to do that.
So with all of this background, finally we come down to a question asked previously: where do I allow breakpoints and how can I tell if this is "interesting" or not?
But before I do that I just wanted clarify something I wrote before about "interesting and safe stoppable points". Suppose division were done as several VM instructions. (it's not, it is a primative, but @a/@b is 4 VM instructions excluding the trace instruction). Even though an exception might be raised after the first or but before the last instruction, "safe" here would mean here before the first instruction or after the last instruction even though an exception might actually occur somewhere in the middle. And as far as location reporting goes, I don't really care that much if the run-time system were to lie a little and say that the program was stopped just after the last instruction. I don't see why it should lie a little, but that wouldn't change anything substantively as far as helping me pinpoint error location.
At any rate, for me how to figure out what is interesting and safe is simple: I disassemble code around where I am interested and set a breakpoint at that VM instruction. trepanning does check that breakpoints can only be set at valid VM offsets; but aside from that, trepanning just allows the breakpoint. If I don't want to disassemble, then I can go the route I mentioned before of stepping to the next return event and then "line" stepping. But the choice is mine to make rather than the implementer deciding (who in my case usually happens to be me) what I can and can't do.
If sometime in the future I discover some sort of automated way to narrow things, I can then add that logic to narrow where breakpoints can be set -- just as I have an automated way to determine if an offset in an instruction sequence is valid.
Before I started working on ruby-debug, it allowed one to set a breakpoint at any line in a file whether or not there was code associated with it. This led complaints that ruby-debug was broken because it never stopped on lines programmers had mistakenly given where there wasn't any code on that line to stop at.
Okay, mistakes happen and ruby-debug got better and learned from that. But it really would have been more wrong and less useful for ruby-debug to declare in those early days that it wasn't going to allow breakpoints until it could figure out if it was valid to do so.
- - -
I realize that my view of what a debugger should provide, the level of flexibility that it should offer, and that it should function both as a high-level debugger and as as low-level debugger is at odds with the philosophy of some core developers. So as such, I accept that I'll probably always have to live with a fork of MRI that allows me to give me the pleasure of debugging support I want.