Wednesday, February 4, 2015

A Personal History of Ruby Debuggers, Part 1: Ruby 1.8

Introduction


I'm writing yet another debugger for Ruby 2.x. Or more precisely I am extending the debugger I started in Ruby 1.9.x for Ruby 2.x.  And that is an extension of the Ruby debugger(s) I worked on in Ruby 1.8. Why?

I think this is progress with Ruby and debuggers and the run-time support needed for debuggers, but no as much as there could be.

So let me start with a little personal history of debuggers and run-time support — where we've been — before describing what I hope to achieve in the debugger effort this time around.

A Personal History if Ruby Debuggers - Ruby 1.8


My first encounter with a Ruby debugger was in Ruby 1.8. It was the one that Matz wrote. It is still available today. You can invoke the debugger using the -r flag with the module name debug. Putting those together you get an invocation that looks something like this:

  $ ruby -rdebug myprogram.rb

I started extending and changing this to make Matz's code a little more like gdb. For example the frame command in that debugger and gdb's frame command do two different things.

But early on I came across Kent Sibilev's excellent debugger for Ruby 1.8  called ruby-debug. However in contrast to Matz's debug module there was a command-line program you could invoke called rdebug.

While the command-line name was clever, ultimately I think it caused a lot of confusion between running:


   $ ruby -rdebug myprogram.rb

versus:

   $ rdebug myprogram.rb

Before going on, let me note two facets of my activity in debuggers from the outset:
  • rather than write a debugger from scratch, I started extending someone else's code.
  • I was interested in keeping commands rather than inventing or reappropriating them. Here I follow the gdb commands.
Kent Sibilev billed ruby-debug as the "fast" Ruby debugger. The adjective was I think justified. Matz's debugger was written in pure Ruby, and two things made it slow:
  • In order to be able to support evaluating Ruby expressions inside the debugger, the callback API dictates that a binding object be created for every callback.
  • there are lots of callbacks to the Ruby code. This is okay if you are single stepping, but if you are running "step over", "step out" or "step to breakpoint" the slowness is noticeable
Kent Sibilev handled these two bottlenecks by coding stop-condition determination and binding object creation in C using the API that Matz provided for Ruby 1.8. Furthermore, to mitigate the slowness in tracing overhead, Kent added the ability to turn it on and off. This was done by the calls:

   Debugger.start

and:

   Debugger.stop

But these needed to be used in conjunction with "require 'ruby-debug'." Also you need to make an initial call to the debugger. Other alternatives included passing a block to the start method, or encountering an unhandled exception after setting up post-mortem debugging. Later we added a convenience mechanism to reduce these three steps to one:

   require "ruby-debug/debugger"

Another cool idea Kent had in ruby-debug was a stepping mode where you could "force" the next stopping point to be on a different line. Originally the command invocation was set force, but I changed it or rather allowed "set different on" to mean the same thing.

One last thing about ruby-debug is worth mentioning.  From the beginning, Kent allowed different ways to interact with the debugger. One way of course is via a terminal in the same process on the same computer; another way is to run batch debugger commands as happens in running a user profile. A third way was similar to how Java debuggers run: the debugger sets up a listener on a socket. 

Early on, folks working on IDEs were interested in using the C extension as a back end, and of course, there was no interest in the command-line front end. To accommodate this, we split the gem into two parts,  a "base" part which IDEs could use, and a pure Ruby part.

Finally, I'd like to note a couple of aspects that came up from the start.  In my opinion, Ruby 1.8 didn't have enough run-time support to enable writing an industrial-strength debugger. However to Matz's credit, in Ruby version 1.8.4 an API was added that made it possible for Kent Sibilev to provide that run-time support.

Kent Sibilev's a good guy. As sometimes happens in the open-source community things change and people sometime drop out of communication. I haven't heard from Kent since around the end of 2007.

Note: There are a lot of people whose contributions I have omitted. I am sure there are things I don't remember; possibly I've gotten things wrong. If you feel slighted or I've got something wrong, contact me or comment below so I can correct this.

No comments:

Post a Comment