Skip to content

Mon-Ouie/pry_debug

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PryDebug

PryDebug is a pure-ruby debugger, It simply relies on set_trace_func and uses Pry (an alternative to IRB) to evaluate code on breakpoints. This means you have complete access to local variables (you can even change them!), and can get any information you want using methods and Pry's commands (find out the value of an instance variable, for example).

If you wonder why using a debugger: how often have you written things like this?

puts "HERE!!!"

# add as many variables as needed until you find the source of the issue.
p :foo => foo, :bar => bar, :baz => baz, :self => self

Adding a breakpoint on that line would tell you whether it was executed and let you see the value of all those variables and all the other without running your code again.

Installation

gem install pry_debug

PryDebug works has been tested with CRuby 1.9.2 and 1.8.7, and works with some glitches in JRuby.

Example

Just use the pry_debug executable, configure the debugger (add breakpoints, ...) and type run.

$ pry_debug file.rb
debugged file set to test_debug.rb
pry(main)> b Foo.foo
added breakpoint 0 at Foo.foo
pry(main)> r
reached breakpoint 0 at Foo.foo

From: /home/kilian/code/pry_debug/test_debug.rb @ line 2 in Class#foo:

     1: class Foo
 =>  2:   def self.foo
     3:     a, b, c = 1, 2, 3
     4:     @foo = a + b + c
     5:   end
     6:
     7:   def initialize(name)
pry(Foo):1> n
stepped at /home/kilian/code/pry_debug/test_debug.rb:3

From: /home/kilian/code/pry_debug/test_debug.rb @ line 3 in Class#foo:

     1: class Foo
     2:   def self.foo
 =>  3:     a, b, c = 1, 2, 3
     4:     @foo = a + b + c
     5:   end
     6:
     7:   def initialize(name)
     8:     @name = name
pry(Foo):2> n
stepped at /home/kilian/code/pry_debug/test_debug.rb:4

From: /home/kilian/code/pry_debug/test_debug.rb @ line 4 in Class#foo:

     1: class Foo
     2:   def self.foo
     3:     a, b, c = 1, 2, 3
 =>  4:     @foo = a + b + c
     5:   end
     6:
     7:   def initialize(name)
     8:     @name = name
     9:   end
pry(Foo):3> p a
1
=> 1
pry(Foo):3> ls -i
Instance variables: []

Features

Break on a line

That's quite an important feature: just b file.rb:line to break whenever that line gets executed. Notice you can pass file.rb, path/to/file.rb, or /full/path/to/file.rb to refer to the same file.

It will run Pry once the breakpoint is reached. When you're done, just type c to continue.

Breakpoints can be listed and removed whenever Pry is running:

pry(Foo):3> b test_debug.rb:10
added breakpoint 1 at test_debug.rb:10
pry(Foo):3> bl
breakpoint 0 at Foo.foo
breakpoint 1 at test_debug.rb:10
pry(Foo):3> d 0
breakpoint 0 deleted
pry(Foo):3> bl
breakpoint 1 at test_debug.rb:10

(You can use breakpoint instead of b, breakpoint-list instead of bl, delete or del instead of d, and continue instead of c; I'm sure most people can't stand typing the full command names, though)

Break on a method

Often, you just want to break when a method gets called. Surely you can find the location of a method in your code most of the time, but that won't work for C-defined methods, and it is more work than just typing the name of the method, isn't it?

pry(Foo):3> b SomeClass#some_method
added breakpoint 2 at SomeClass#some_method

SomeClass#some_method is used to break on the instance method some_method of the class SomeClass. Both SomeClass.some_method and SomeClass::some_method will break on a class method.

Note that, due to implementation details, it is currently hard for PryDebug to identify that some call to Class#new was in fact a call to Foo.new. It still can find what was called in most other cases.

Conditional breakpoints

You may want a breakpoint to be run only when a particular condition is met. You can add a condition to the breakpoint using cond breakpoint_id some code, where some code will get run every time the breakpoint is reached. If it evaluated to nil or false, then PryDebug won't actually stop at that point. Conditions can also be removed using uncond breakpoint_id.

pry(Foo):3> b foo.rb:15
added breakpoint 3 at foo.rb:15
pry(Foo):3> cond 3 @array.size > 10
condition set to @array.size > 10
pry(Foo):3> bl
breakpoint 3 at foo.rb:15 (if @array.size > 10)

(Beware, exceptions in conditions are silently ignored)

Step and next

Breaking at a given place is sometimes not enough. Sometimes, it is useful to execute each line to find out what changed. That's what the step command is for: it makes the debugger execute code until the next line before breaking. You can also use step instead of run. It will then break on the first line of your code.

next is a similar command, that will break on the next line in the same file (though it would ideally break on the next line in the same method instead of stepping into it if it is defined in the same file).

Exceptions

You don't need to do anything to benefit from this feature: whenever an exception isn't rescued, PryDebug will rescue it and bring you back to the place where it occured so you can find out why it was raised:

unrescued exception: RuntimeError: foo
returning back to where the exception was raised

From: /home/kilian/code/pry_debug/test_debug.rb @ line 22 in Object#N/A:

    17:
    18: 100.times.map do |n|
    19:   n * 2
    20: end
    21:
 => 22: raise "foo"
    23:
    24: __END__
    25: b Foo.foo
    26: b Class#inherted
    27: b FooBar.jump
pry(main):3>

Break on raise

This is disabled by default because it is annoying in code where an exception being raised and then rescued is normal. It can still be helpful: once enabled, raising an exception causes PryDebug to stop. This can be toggled using the bor (break-on-raise) command.

pry(main):4> bor
break on raise enabled
pry(main):4> r
exception raised: RuntimeError: foo

From: /home/kilian/code/pry_debug/test_debug.rb @ line 22 in Object#N/A:

    17:
    18: 100.times.map do |n|
    19:   n * 2
    20: end
    21:
 => 22: raise "foo"
    23:
    24: __END__
    25: b Foo.foo
    26: b Class#inherted
    27: b FooBar.jump
pry(main):5>

Start in an existing program

Instead of starting your program from PryDebug, you can start PryDebug from your program. In this case, it won't handle unrescued exceptions automatically, though.

require 'pry_debug'
PryDebug.start(false) # true makes PryDebug load its own file

# you can make it handle exceptions yourself (or break-on-raise instead):
begin
  # ...
rescue SystemExit
  # nothing
rescue Exception => ex
  # PryDebug can still be started

  if binding = PryDebug.context_of_exception(ex)
    PryDebug.start_pry binding
  else
    PryDebug.start_pry ex
  end
end

Threads

It is completely possible you will want to use PryDebug in code that uses Thread. PryDebug will ensure that only one thread uses Pry. It will also keep information about breakpoints and stepping on a thread basis, to avoid unexpected and undetermined results.