[ANN] breakpoint.rb -- now with remote debugging

Moin!

Breakpoint.rb is a small library that lets you set breakpoints by calling a method. When executed, a breakpoint causes an interactive ruby session to be spawned with the current context. This makes it especially useful for debugging because Ruby has powerful built-in capabilities for exploring object environments. (Kernel#caller, Object#inspect, Object#respond_to?, Kernel#local_variables, Object#instance_variables and so on.)

There's also support for overriding return values and for an assert() method that automatically sets a breakpoint in case an unexpected situation occurs.

binding_of_caller.rb (2.54 KB)

breakpoint.rb (7.39 KB)

breakpoint_client.rb (789 Bytes)

···

---

This new version includes support for remote debugging via DRb. Using this you can now also debug your CGI and Rails applications (and other applications where you can't pollute STDIN/STDOUT by running irb directly). To start the DRb breakpoint service you will have to call Breakpoint.activate_drb -- after that you can interact with that service by running breakpoint_client.rb.

This is a pre-release of the next version of the code already included in the Ruby dev-utils project (http://dev-utils.rubyforge.org/) meaning that I'm not sure if it is 100% bug-free (I refactored quite a lot) -- if this works out well the dev-utils version can be updated.

If you have any suggestions or if you find any problems with this please mention them now so that I can fix them as soon as possible. Thanks!

Kind regards,
Florian Gross

Because I'm too lazy to fully inspect both packages, can you describe how this differs from dev-utils/debug?

···

On Oct 11, 2004, at 9:29 AM, Florian Gross wrote:

Breakpoint.rb is a small library that lets you set breakpoints by calling a method. When executed, a breakpoint causes an interactive ruby session to be spawned with the current context. This makes it especially useful for debugging because Ruby has powerful built-in capabilities for exploring object environments. (Kernel#caller, Object#inspect, Object#respond_to?, Kernel#local_variables, Object#instance_variables and so on.)

Thank you SO much!

One small inconvenience that I have with this library is that it takes
some typing to set a breakpoint.

Say I have a util.rb file that is included more or less everywhere in my
app, and I would like to include there something like this:

def bp
  require 'breakpoint'
  breakpoint
end

so that setting a breakpoint would be simply 'bp'

This doesn't work, of course, because it opens irb in a wrong stack
frame (within bp method).

Any Ruby black magic to somehow inline it in the right stack frame?

Alex

···

On Mon, 2004-10-11 at 18:29, Florian Gross wrote:

This new version includes support for remote debugging via DRb. Using
this you can now also debug your CGI and Rails applications

This is very cool.

Just one observation: when running over DRb, you have to be very careful not
to type anything in the session which causes another breakpoint to trigger,
as it seems to get rather confused.

For example, in your Person example, at the second breakpoint typing 'name'
instead of '@name' causes a nested breakpoint call. This works just fine for
a directly-connected session:

$ ruby test.rb
Executing break point "Person#initialize" at test.rb:7 in `initialize'
irb(#<Person:0x8130f5c>):001:0> exit
Executing break point "Person#name" at test.rb:13 in `name'
irb(#<Person:0x8130f5c>):001:0> @name
=> "Random Person"
irb(#<Person:0x8130f5c>):002:0> name
Executing break point "Person#name" at test.rb:13 in `name'
irb(#<Person:0x8130f5c>):001:0> name
Executing break point "Person#name" at test.rb:13 in `name'
irb(#<Person:0x8130f5c>):001:0> exit
=> "Random Person"
irb(#<Person:0x8130f5c>):002:0> exit
=> "Random Person"
irb(#<Person:0x8130f5c>):003:0> exit
Name: Random Person

But not over DRb (it hangs at the nested breakpoint). Interestingly, I even
managed to make Ruby dump core in the program which was under test. Gory
details below.

I wonder if a 'console' model would work better; that is, run a private IRB
session in the object under test, and then just connect its input/output
stream? The debug session could accept a telnet connection, or communicate
over a socket or a pair of named pipes.

Also, if it could display +/-5 lines of source code context, that would be
very pleasant. But perhaps then it's getting too close to an IDE :slight_smile:

Regards,

Brian.

[brian@vaio lib]$ ruby test.rb &
[1] 71708
[brian@vaio lib]$ ruby breakpoint-client.rb
Executing break point "Person#initialize" at test.rb:7 in `initialize'
irb(#<DRb::DRbUnknown:0x8110728>):001:0> exit
Executing break point "Person#name" at test.rb:13 in `name'
irb(#<DRb::DRbUnknown:0x82e93e8>):001:0> @name
=> "Random Person"
irb(#<Person:0x8130c8c>):002:0> name
<< nothing happens here until I press ctrl-C >>
^CIRB::Abort: abort then interrupt!!
        from /usr/local/lib/ruby/1.8/irb.rb:81:in `load'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:611:in `recv_reply'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:865:in `recv_reply'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1104:in `send_message'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1015:in `method_missing'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1014:in `open'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1014:in `method_missing'
        from /usr/local/lib/ruby/1.8/irb/context.rb:198:in `old_evaluate'
        from ./breakpoint.rb:239:in `evaluate'
        from /usr/local/lib/ruby/1.8/irb.rb:148:in `eval_input'
        from /usr/local/lib/ruby/1.8/irb.rb:145:in `signal_status'
        from /usr/local/lib/ruby/1.8/irb.rb:145:in `eval_input'
        from /usr/local/lib/ruby/1.8/irb.rb:144:in `each_top_level_statement'
        from /usr/local/lib/ruby/1.8/irb/ruby-lex.rb:220:in `loop'
        from /usr/local/lib/ruby/1.8/irb/ruby-lex.rb:220:in `each_top_level_statement'
        from /usr/local/lib/ruby/1.8/irb/ruby-lex.rb:219:in `catch'
... 6 levels...
        from breakpoint-client.rb:22:in `call'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1434:in `__send__'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1434:in `perform_without_block'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1395:in `perform'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1468:in `main_loop'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1464:in `loop'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1464:in `main_loop'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1460:in `start'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1460:in `main_loop'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1327:in `run'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1324:in `start'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1324:in `run'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1249:in `initialize'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1505:in `new'
        from /usr/local/lib/ruby/1.8/drb/drb.rb:1505:in `start_service'
        from breakpoint-client.rb:16irb(#<Person:0x8130c8c>):003:0> @name
=> "Random Person"
irb(#<Person:0x8130c8c>):004:0> Name: Random Person
./breakpoint.rb:148: [BUG] Segmentation fault
ruby 1.8.2 (2004-07-29) [i386-freebsd4]

No connection to breakpoint service at druby://localhost:42531:
  (connection closed)
  Reconnecting in 10 seconds...
^C
<< no response to ^C so I had to do ^Z >>
^Z[1] Abort trap (core dumped) ruby test.rb

[2]+ Stopped ruby breakpoint-client.rb
[brian@vaio lib]$ kill %2
[2]+ Terminated ruby breakpoint-client.rb
[brian@vaio lib]$ jobs
[brian@vaio lib]$ gdb -c ruby.core ruby
GNU gdb 4.18 (FreeBSD)
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-unknown-freebsd"...
(no debugging symbols found)...
Core was generated by `ruby'.
Program terminated with signal 6, Abort trap.
Reading symbols from /usr/local/lib/libruby18.so.18...
(no debugging symbols found)...done.
Reading symbols from /usr/lib/libcrypt.so.2...(no debugging symbols found)...
done.
Reading symbols from /usr/lib/libm.so.2...(no debugging symbols found)...done.
Reading symbols from /usr/lib/libc.so.4...(no debugging symbols found)...done.
Reading symbols from /usr/local/lib/ruby/1.8/i386-freebsd4/readline.so...
(no debugging symbols found)...done.
Reading symbols from /usr/lib/libreadline.so.4...
(no debugging symbols found)...done.
Reading symbols from /usr/lib/libncurses.so.5...(no debugging symbols found)...
done.
Reading symbols from /usr/local/lib/ruby/1.8/i386-freebsd4/socket.so...
(no debugging symbols found)...done.
Reading symbols from /usr/local/lib/ruby/1.8/i386-freebsd4/fcntl.so...
(no debugging symbols found)...done.
Reading symbols from /usr/libexec/ld-elf.so.1...(no debugging symbols found)...
done.
#0 0x28190084 in kill () from /usr/lib/libc.so.4
(gdb) bt
#0 0x28190084 in kill () from /usr/lib/libc.so.4
#1 0x281d197a in abort () from /usr/lib/libc.so.4
#2 0x280827d1 in rb_bug () from /usr/local/lib/libruby18.so.18
#3 0x280e6e82 in sigsegv () from /usr/local/lib/libruby18.so.18
#4 0xbfbfffac in ?? ()
#5 0x2809127f in eval () from /usr/local/lib/libruby18.so.18
#6 0x2809166e in rb_f_eval () from /usr/local/lib/libruby18.so.18
#7 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18
#8 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#9 0x2808abd4 in rb_eval () from /usr/local/lib/libruby18.so.18
#10 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18
#11 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#12 0x2808abd4 in rb_eval () from /usr/local/lib/libruby18.so.18
#13 0x2808b54e in rb_eval () from /usr/local/lib/libruby18.so.18
#14 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18
#15 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#16 0x28090778 in rb_f_send () from /usr/local/lib/libruby18.so.18
#17 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18
#18 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#19 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18
#20 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18
#21 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#22 0x2808ac4c in rb_eval () from /usr/local/lib/libruby18.so.18
#23 0x2808b60e in rb_eval () from /usr/local/lib/libruby18.so.18
#24 0x28089fd6 in rb_eval () from /usr/local/lib/libruby18.so.18
#25 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18
#26 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#27 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18
#28 0x28089de1 in rb_eval () from /usr/local/lib/libruby18.so.18
---Type <return> to continue, or q <return> to quit---
#29 0x2808b504 in rb_eval () from /usr/local/lib/libruby18.so.18
#30 0x2808a11c in rb_eval () from /usr/local/lib/libruby18.so.18
#31 0x2808dceb in rb_yield_0 () from /usr/local/lib/libruby18.so.18
#32 0x2808e11e in rb_f_loop () from /usr/local/lib/libruby18.so.18
#33 0x2808fb51 in rb_call0 () from /usr/local/lib/libruby18.so.18
#34 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#35 0x2808abd4 in rb_eval () from /usr/local/lib/libruby18.so.18
#36 0x28089a8a in rb_eval () from /usr/local/lib/libruby18.so.18
#37 0x2808dceb in rb_yield_0 () from /usr/local/lib/libruby18.so.18
#38 0x2809a129 in rb_thread_yield () from /usr/local/lib/libruby18.so.18
#39 0x28099e47 in rb_thread_start_0 () from /usr/local/lib/libruby18.so.18
#40 0x2809a252 in rb_thread_start () from /usr/local/lib/libruby18.so.18
#41 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18
#42 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#43 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18
#44 0x28089a8a in rb_eval () from /usr/local/lib/libruby18.so.18
#45 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18
#46 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#47 0x2808ac4c in rb_eval () from /usr/local/lib/libruby18.so.18
#48 0x28089666 in rb_eval () from /usr/local/lib/libruby18.so.18
#49 0x2808a11c in rb_eval () from /usr/local/lib/libruby18.so.18
#50 0x2808dceb in rb_yield_0 () from /usr/local/lib/libruby18.so.18
#51 0x2809a129 in rb_thread_yield () from /usr/local/lib/libruby18.so.18
#52 0x28099e47 in rb_thread_start_0 () from /usr/local/lib/libruby18.so.18
#53 0x2809a252 in rb_thread_start () from /usr/local/lib/libruby18.so.18
#54 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18
#55 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#56 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18
#57 0x28089a8a in rb_eval () from /usr/local/lib/libruby18.so.18
---Type <return> to continue, or q <return> to quit---
#58 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18
#59 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#60 0x2808ac4c in rb_eval () from /usr/local/lib/libruby18.so.18
#61 0x2808b60e in rb_eval () from /usr/local/lib/libruby18.so.18
#62 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18
#63 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#64 0x280908d5 in rb_funcall2 () from /usr/local/lib/libruby18.so.18
#65 0x2809309c in rb_obj_call_init () from /usr/local/lib/libruby18.so.18
#66 0x280b850b in rb_class_new_instance () from /usr/local/lib/libruby18.so.18
#67 0x2808fb22 in rb_call0 () from /usr/local/lib/libruby18.so.18
#68 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#69 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18
#70 0x2808b60e in rb_eval () from /usr/local/lib/libruby18.so.18
#71 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18
#72 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#73 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18
#74 0x2809015a in rb_call0 () from /usr/local/lib/libruby18.so.18
#75 0x280905e4 in rb_call () from /usr/local/lib/libruby18.so.18
#76 0x2808a9af in rb_eval () from /usr/local/lib/libruby18.so.18
#77 0x28086168 in eval_node () from /usr/local/lib/libruby18.so.18
#78 0x280866fe in ruby_exec () from /usr/local/lib/libruby18.so.18
#79 0x28086771 in ruby_run () from /usr/local/lib/libruby18.so.18
#80 0x80485df in main ()
#81 0x8048516 in _start ()
(gdb)

I can. The early unreleased dev-utils/debug and the original
breakpoint.rb were independent works solving the same problem.
Florian went further than I did and I slurped it right up. So there's
no difference between them, based on what you've quoted.

Now Florian's gone further again, and implemented remote debugging.
That will eventually make its way into dev-utils/debug as well.

Cheers,
Gavin

···

On Tuesday, October 12, 2004, 10:48:21 PM, Gavin wrote:

On Oct 11, 2004, at 9:29 AM, Florian Gross wrote:

Breakpoint.rb is a small library that lets you set breakpoints by
calling a method. When executed, a breakpoint causes an interactive
ruby session to be spawned with the current context. This makes it
especially useful for debugging because Ruby has powerful built-in
capabilities for exploring object environments. (Kernel#caller,
Object#inspect, Object#respond_to?, Kernel#local_variables,
Object#instance_variables and so on.)

Because I'm too lazy to fully inspect both packages, can you describe
how this differs from dev-utils/debug?

Alexey Verkhovsky <alex@verk.info> writes:

···

On Mon, 2004-10-11 at 18:29, Florian Gross wrote:
> This new version includes support for remote debugging via DRb. Using
> this you can now also debug your CGI and Rails applications

Thank you SO much!

One small inconvenience that I have with this library is that it takes
some typing to set a breakpoint.

Say I have a util.rb file that is included more or less everywhere in my
app, and I would like to include there something like this:

def bp
  require 'breakpoint'
  breakpoint
end

so that setting a breakpoint would be simply 'bp'

This doesn't work, of course, because it opens irb in a wrong stack
frame (within bp method).

Any Ruby black magic to somehow inline it in the right stack frame?

Maybe you could use Florian Groß's Binding.of_caller to evaluate
'breakpoint' in the caller's binding.

Alexey Verkhovsky wrote:

This new version includes support for remote debugging via DRb. Using this you can now also debug your CGI and Rails applications

Thank you SO much!

Glad I could provide something useful. :slight_smile:

BTW, have you tested the remote debugging facilities yet? Any suggestions / problems?

One small inconvenience that I have with this library is that it takes
some typing to set a breakpoint.

Say I have a util.rb file that is included more or less everywhere in my
app, and I would like to include there something like this:

def bp
  require 'breakpoint'
  breakpoint
end

so that setting a breakpoint would be simply 'bp'

This doesn't work, of course, because it opens irb in a wrong stack
frame (within bp method).

alias :bp :breakpoint ought to work. You will have to do the require manually, however. (But it should not cause any overhead anyway.)

Regards,
Florian Gross

···

On Mon, 2004-10-11 at 18:29, Florian Gross wrote:

I don't want to require 'breakpoint' outside a bp method - other people
may not have it installed, and it will be an installation hassle then.
That's the main problem.

Alex

···

On Tue, 2004-10-12 at 23:34, Florian Gross wrote:

Alexey Verkhovsky wrote:

Alexey Verkhovsky wrote:

I don't want to require 'breakpoint' outside a bp method - other people
may not have it installed, and it will be an installation hassle then.
That's the main problem.

You could use Binding.of_caller then, but I'm not sure if you want to have that dependency...

Regards,
Florian Gross

Same problem - non-standard library. But...

Cheap and cheerful solutions being the essence of Russian engineering
ideal, I ended up copying both your classes to my CVS.
:slight_smile:

Brgds,
Alex

···

On Thu, 2004-10-14 at 15:14, Florian Gross wrote:

You could use Binding.of_caller then, but I'm not sure if you want to
have that dependency...