A better Rails debugger: ruby-debug

Just a few days ago, Kent Sibilev quietly released an amazing little plugin with far reaching consequences: ruby-debug. This is a marked improvement over Ruby’s own rdebug. The major difference is that he removed the major slow down that impacted rdebug usability with Ruby on Rails so it is blazing fast.

Instead of using the Kernel#set_trace_func API, which makes it possible to implement a debugger in Ruby, but has a negative effect on the speed on your program execution, he uses a native extension with a new hook using the Ruby C API

For each trace call Ruby interpreter creates a Binding object, even though it is not being used most of the time. ruby-debug library moves most of the functionality of debug.rb to a native extension, this way significantly improving the execution of your program.

This means that watchpoints and the standard tracing facility are not supported, but I’m gladly giving that up for the comfort and speed. And if you are wondering how the speed is, that is really the difference between utter frustration each time you debug something to bliss!

Previously I covered some options for Debugging in Rails. This is going to become my preferred option by far, and I’m willing to bet this will become yours too. Why? Because you can see the source code of where you are, you can execute step while watching some or your variables, you can inspect of your variables (this you could do with breakpointer), you can use conditional breakpoints, you can get a list of expressions to be displayed every time you step. The downside compared to breakpointer? You can’t do it remotely, which in most instances, as best as I can tell, is not going to be a problem at all.

Installation

Ruby-debug comes as a gem so to install, just run:

sudo gem install ruby-debug

Make sure you chose the proper windows or ruby version depending on your platform.

Using ruby-debug

To use in your rails application, assuming you want this to only be available in development mode (this is not such a great idea to leave in in production mode, just in case you forget to remove the breakpoints, and also for performance reasons as I’ll explain in a bit)

In environement.rb add the following:

SCRIPT_LINES__ = {} if ENV['RAILS_ENV'] == 'development'

This line is important if you want to be able to see your source code. SCRIPT_LINES__ is an obscure feature of the ruby interpreter. If it is defined, it will store all loaded ruby file in a hash, which debug-ruby will use to display where you are in your source code. The only problem is that it can have some impact on performance, and worst of all, use up quite a bit of memory, which is not so good in production (hence the “if ENV['RAILS_ENV'] == ‘development’”). SCRIPT_LINES__ needs to be initialized as early as possible so it can capture all loaded ruby files.

To add a breakpoint, you will need to use:

require 'ruby-debug'
...
def your_method
  ...
  debugger if ENV['RAILS_ENV] == 'development'
  ...
end

Then start your app using webrick (it does not work with lighttpd and I have not investigated why just yet):

script/server webrick

When the code hits the breakpoint, you are in a console like mode (not unlike irb or script/console).

Ruby-debug commands

  • b[reak]

    list breakpoints
  • b[reak] [file|class:]LINE|METHOD [if expr]
  • b[reak] [class.]LINE|METHOD [if expr]

    set breakpoint to some position, optionally if expr == true
  • cat[ch]

    show catchpoint
  • cat[ch] EXCEPTION

    set catchpoint to an exception
  • disp[lay] EXPRESSION
    add expression into display expression list
  • undisp[lay][ nnn]

    delete one particular or all display expressions if no expression number given
  • del[ete][ nnn]

    delete some or all breakpoints (get the number using “break”)
  • c[ont]

    run until program ends or hit breakpoint
  • r[un]

    alias for cont
  • s[tep][ nnn]

    step (into methods) one line or till line nnn
  • n[ext][ nnn]

    go over one line or till line nnn
  • w[here]

    displays stack
  • f[rame]

    alias for where
  • l[ist][ (-|nn-mm)]

    list program, – list backwards, nn-mm list given lines. No arguments keeps listing
  • up[ nn]

    move to higher frame
  • down[ nn]

    move to lower frame
  • fin[ish]

    return to outer frame
  • q[uit]

    exit from debugger
  • v[ar] g[lobal]

    show global variables
  • v[ar] l[ocal]

    show local variables
  • v[ar] i[nstance] OBJECT

    show instance variables of object
  • v[ar] c[onst] OBJECT

    show constants of object
  • m[ethod] i[nstance] OBJECT

    show methods of object
  • m[ethod] CLASS|MODULE

    show instance methods of class or module
  • th[read] l[ist]

    list all threads
  • th[read] c[ur[rent]]

    show current thread
  • th[read] [sw[itch]] nnn

    switch thread context to nnn
  • th[read] stop nnn

    stop thread nnn
  • th[read] resume nnn

    resume thread nnn
  • p EXPRESSION

    evaluate expression and print its value
  • pp EXPRESSSION

    evaluate expression and print its value
  • h[elp]

    print this help
  • RETURN KEY

    redo previous command. Convenient when using list, step, next, up, down,
  • EVERYHTING ELSE

    evaluate

Happy debugging!

Tags: ,

35 Responses to “A better Rails debugger: ruby-debug”

  1. Christian Romney July 14, 2006 at 11:42 pm #

    Why not stick the script lines call in environments/development.rb?

  2. Pascal July 14, 2006 at 11:42 pm #

    This is an excellent idea, Christian.

    This works well too.

  3. Jerry West July 14, 2006 at 11:42 pm #

    You may also need to put

    Debugger.start`

    in environment/development.rb, or

    Debugger.start if ENV['RAILS_ENV'] == 'development'

    in environment.rb

  4. Pascal July 14, 2006 at 11:42 pm #

    Jerry, you are correct. This is indeed required with the latest versions. Or use

    rdebug script/server webrick

    to start your app.

  5. Steve Midgley July 14, 2006 at 11:42 pm #

    Hi,

    I’ve been toying around with Eclipse for Ruby/RoR and was frustrated that I couldn’t debug using methods like you describe but with a full GUI. So I hacked something out today that allows me to use all the debugging in-line features of Eclipse on a live Rails app. Not sure if this is useful – or its speed could possibly be increased by using some of the code you describe?

    My write up on the (kludgy) method is here:

    http://www.misuse.org/cms/article.php?story=20060913182223765

    Drop me a line if this is useful!

    Steve

  6. Pascal July 14, 2006 at 11:42 pm #

    Steve, nice writeup. An upcoming version of RadRails is going to use ruby-debug instead of the buildin breakpointer stuff that can slow down your app quite dramatically. See [RadRails is switching to ruby-debug](http://blog.nanorails.com/articles/2006/08/02/radrails-is-switching-to-ruby-debug).

  7. Michael July 14, 2006 at 11:42 pm #

    You say add:

    SCRIPT_LINES__ = {} if ENV['RAILS_ENV'] == ‘development’

    to config/environment.rb. Isn’t that equivalent to adding:

    SCRIPT_LINES__ = {}

    to config/environments/development.rb? Is there any reason to do it one way or the other?

  8. Pascal July 14, 2006 at 11:42 pm #

    It is almost equivalent, if I recall correctly, I was putting it there to get it as quickly as possible during initialization. Since it starts recording loaded files when SCRIPTLINES gets initialized. So

    I say was doing that, because with the newest versions of ruby-debug, you are much better off running your app with rdebug anyway. And you don’t need this anymore. use “rdebug script/server webrick”

  9. rails guy July 14, 2006 at 11:42 pm #

    Do I have to place
    debugger if ENV['RAILS_ENV'] == ‘development’

    in all of my methods?

  10. Pascal July 14, 2006 at 11:42 pm #

    If you want to be sure that no debugger call ends up in your production version, yes.
    A better practice, though, is to only add calls to “debugger” when troubleshooting, then remove them once you are done.
    I’ve found that using grep (or your editor’s equivalent) before you commit to make sure you’ve removed everything is a good practice.

  11. karmajunkie July 14, 2006 at 11:42 pm #

    How well would it work to put the debugger call in a helper method or something like that? I know you’d start one level deeper in the stack, but you could just step out of the method call and be right back where you need to be…

  12. Pascal July 14, 2006 at 11:42 pm #

    That’s a possibility, but I’m afraid this may get old pretty quickly as this would break the display command, the source list command to quickly know where you are, and see some key variables right away.
    Overall, being disciplined about removing calls to debugger when you are done tracking a bug is a practice that has worked for me and not let me down again (for that first few times…)

  13. Don Park July 14, 2006 at 11:42 pm #

    haha. its funny how noone reads the comments before posting. How about this idea: Update the article instead of having everyone ask the same question.

  14. Xin the Zen July 14, 2006 at 11:42 pm #

    Thanks for the great writeup.

    This also works great with Mongrel!

  15. Roger Pack July 14, 2006 at 11:42 pm #

    Looks like the latest version of ruby-debug is 0.9.3, so you may want to update your links or just change them to a link to [http://rubyforge.org/frs/?group\_id=1900&release\_id=11354](http://rubyforge.org/frs/?group_id=1900&release_id=11354) since it gets updated fairly often, it appears.

  16. Pascal July 14, 2006 at 11:42 pm #

    Thanks, ROger.

    In fact, installation is a lot simpler now:

    gem install ruby-debug

    I’ve updated the page.

  17. Tom July 14, 2006 at 11:42 pm #

    Would the configuration described above allow breakpoints when testing? Am I the only person that uses breakpoints when running my unit and functional tests?

    Thanks,
    Tom

  18. Pascal July 14, 2006 at 11:42 pm #

    To debug my test, I usually run the test directly using rdebug. For example:

    rdebug test/unit/person_test.rb
  19. Helder July 14, 2006 at 11:42 pm #

    Hello,

    How I get out from ruby-debug ?

    I try “c”, but the no stop next breakpoint

  20. Pascal July 14, 2006 at 11:42 pm #

    try ‘q’ (as in quit)

  21. roger pack July 14, 2006 at 11:42 pm #

    These days for it to work in rails do thus:

    require ‘ruby-debug’
    add the word
    debugger
    somewhere (anywhere?) in your code

  22. Pascal Belloncle July 14, 2006 at 11:42 pm #

    You also just do:

    rdebug script/server

    No need to use include that way.

  23. Stan July 14, 2006 at 11:42 pm #

    Nice writeup but I can’t believe you didn’t mention the use of “irb” in a debugging session. I find that more useful than all the other debugger commands combined.

    Just type irb and you get full access to the interpreter. Slick.

  24. Pascal July 14, 2006 at 11:42 pm #

    Sure, irb is part of the arsenal, as well as script/console.

    What ruby-debug gives you that irb or console can’t, is access to the environment exactly as the request handling happens, including anything you can do in irb.

  25. Stan July 14, 2006 at 11:42 pm #

    You’re missing my point: use irb within a “debugger” session. That way you do get the exact environment, and the full power of ruby.

    Almost always, when I invoke debugger, I type “irb” at the ‘(rdb:1) ’ prompt.

    My point was the author neglected to mention “irb” as a Ruby-debug command.

  26. Pascal July 14, 2006 at 11:42 pm #

    Point taken! I was not aware of that command. It did not exist at the time I wrote this page, and probably missed this from the release notes.

    Thank you for pointing that out, Stan! Should prove to be very valuable :)

  27. Max Williams July 14, 2006 at 11:42 pm #

    This syntax: require ‘ruby-debug’

    I’ve not seen these ampersand-hash-number-semicolon symbols before: are we really meant to use these, or is my browser doing something weird? (firefox in ubuntu and ie in windows both show this so i don’t think it is my browser)

  28. Max Williams July 14, 2006 at 11:42 pm #

    woah…my last post translated the symbols into single quotes when it put the comment text on your page…so there is definitely some formatting weirdness going on with your page. Just in case you don’t know what i’m on about ( :o) ), the second code example window, which is introduced with the text “To add a breakpoint, you will need to use:”

    Displays for me as (writing the name of the symbols):

    require ampersand-hash-8216-semicolon ruby-debug ampersand-hash-8217-semicolon

  29. Pascal July 14, 2006 at 11:42 pm #

    yes, all the html entities are really quotes. That was smartypants doing something silly. Should be fixed now. Thanks!

  30. Jesús Navarrete July 14, 2006 at 11:42 pm #

    Years later and still works XD, thanks about the howto.

  31. Pascal July 14, 2006 at 11:42 pm #

    You are welcome Jesús. Be sure to read this as well http://blog.nanorails.com/articles/2008/1/12/ruby-debug-quick-tips-init-file-and-n-option

  32. Rechtsanwalt aus Stuttgart July 14, 2006 at 11:42 pm #

    Thx for the tip.

    Do you know is it possible to debug such code in Eclipse with with a full UI support (I’m new in Eclipse\Rails world)?

  33. Pascal July 14, 2006 at 11:42 pm #

    I don’t know. I haven’t used eclipse for rails in a long time. Textmate is the only thing I use really, plus vi for remote edit.

  34. Attila July 14, 2006 at 11:42 pm #

    Pascal, when you run the server, you probably need to pass a—debugger parameter, otherwise the breakpoints will get ignored.

  35. Pascal July 14, 2006 at 11:42 pm #

    That was not the case when I wrote this post, but now, you’d either use – u or – – debugger directly, yes.