Module Breakpoint
In: lib/breakpoint.rb

Methods

Classes and Modules

Module Breakpoint::CommandBundle
Class Breakpoint::FailedAssertError

Constants

Version = current_version   The Version of ruby-breakpoint you are using as String of the 1.2.3 form where the digits stand for release, major and minor version respectively.

Attributes

asserts_cause_exceptions  [RW]  Whether an Exception should be raised on failed asserts in non-$DEBUG code or not. By default this is disabled.
optimize_asserts  [RW]  Whether asserts should be ignored if not in debug mode. Debug mode can be enabled by running ruby with the -d switch or by setting $DEBUG to true.

Public Instance methods

Will run Breakpoint in DRb mode. This will spawn a server that can be attached to via the breakpoint-client command whenever a breakpoint is executed. This is useful when you are debugging CGI applications or other applications where you can’t access debug sessions via the standard input and output of your application.

You can specify an URI where the DRb server will run at. This way you can specify the port the server runs on. The default URI is druby://localhost:42531.

Please note that breakpoints will be skipped silently in case the DRb server can not spawned. (This can happen if the port is already used by another instance of your application on CGI or another application.)

Also note that by default this will only allow access from localhost. You can however specify a list of allowed hosts or nil (to allow access from everywhere). But that will still not protect you from somebody reading the data as it goes through the net.

A good approach for getting security and remote access is setting up an SSH tunnel between the DRb service and the client. This is usually done like this:

$ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com (This will connect port 20000 at the client side to port 20000 at the server side, and port 10000 at the server side to port 10000 at the client side.)

After that do this on the server side: (the code being debugged) Breakpoint.activate_drb("druby://127.0.0.1:20000", "localhost")

And at the client side: ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000

Running through such a SSH proxy will also let you use breakpoint.rb in case you are behind a firewall.

Detailed information about running DRb through firewalls is available at www.rubygarden.org/ruby?DrbTutorial

Security considerations

Usually you will be fine when using the default druby:// URI and the default access control list. However, if you are sitting on a machine where there are local users that you likely can not trust (this is the case for example on most web hosts which have multiple users sitting on the same physical machine) you will be better off by doing client/server communication through a unix socket. This can be accomplished by calling with a drbunix:/ style URI, e.g. Breakpoint.activate_drb(‘drbunix:/tmp/breakpoint_server’). This will only work on Unix based platforms.

[Source]

     # File lib/breakpoint.rb, line 398
398:   def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'],
399:     ignore_collisions = false)
400: 
401:     return false if @use_drb
402: 
403:     uri ||= 'druby://localhost:42531'
404: 
405:     if allowed_hosts then
406:       acl = ["deny", "all"]
407: 
408:       Array(allowed_hosts).each do |host|
409:         acl += ["allow", host]
410:       end
411: 
412:       DRb.install_acl(ACL.new(acl))
413:     end
414: 
415:     @use_drb = true
416:     @drb_service = DRbService.new
417:     did_collision = false
418:     begin
419:       @service = DRb.start_service(uri, @drb_service)
420:     rescue Errno::EADDRINUSE
421:       if ignore_collisions then
422:         nil
423:       else
424:         # The port is already occupied by another

425:         # Breakpoint service. We will try to tell

426:         # the old service that we want its port.

427:         # It will then forward that request to the

428:         # user and retry.

429:         unless did_collision then
430:           DRbObject.new(nil, uri).collision
431:           did_collision = true
432:         end
433:         sleep(10)
434:         retry
435:       end
436:     end
437: 
438:     return true
439:   end

This asserts that the block evaluates to true. If it doesn’t evaluate to true a breakpoint will automatically be created at that execution point.

You can disable assert checking in production code by setting Breakpoint.optimize_asserts to true. (It will still be enabled when Ruby is run via the -d argument.)

Example:

  person_name = "Foobar"
  assert { not person_name.nil? }

Note: If you want to use this method from an unit test, you will have to call it by its full name, Breakpoint.assert.

[Source]

     # File lib/breakpoint.rb, line 268
268:   def assert(context = nil, &condition)
269:     return if Breakpoint.optimize_asserts and not $DEBUG
270:     return if yield
271: 
272:     callstack = caller
273:     callstack.slice!(0, 3) if callstack.first["assert"]
274:     file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
275: 
276:     message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."
277: 
278:     if Breakpoint.asserts_cause_exceptions and not $DEBUG then
279:       raise(Breakpoint::FailedAssertError, message)
280:     end
281: 
282:     message += " Executing implicit breakpoint."
283: 
284:     if context then
285:       return handle_breakpoint(context, message, file, line)
286:     end
287: 
288:     Binding.of_caller do |context|
289:       handle_breakpoint(context, message, file, line)
290:     end
291:   end

This will pop up an interactive ruby session at a pre-defined break point in a Ruby application. In this session you can examine the environment of the break point.

You can get a list of variables in the context using local_variables via local_variables. You can then examine their values by typing their names.

You can have a look at the call stack via caller.

The source code around the location where the breakpoint was executed can be examined via source_lines. Its argument specifies how much lines of context to display. The default amount of context is 5 lines. Note that the call to source_lines can raise an exception when it isn’t able to read in the source code.

breakpoints can also return a value. They will execute a supplied block for getting a default return value. A custom value can be returned from the session by doing throw(:debug_return, value).

You can also give names to break points which will be used in the message that is displayed upon execution of them.

Here’s a sample of how breakpoints should be placed:

  class Person
    def initialize(name, age)
      @name, @age = name, age
      breakpoint("Person#initialize")
    end

    attr_reader :age
    def name
      breakpoint("Person#name") { @name }
    end
  end

  person = Person.new("Random Person", 23)
  puts "Name: #{person.name}"

And here is a sample debug session:

  Executing break point "Person#initialize" at file.rb:4 in `initialize'
  irb(#<Person:0x292fbe8>):001:0> local_variables
  => ["name", "age", "_", "__"]
  irb(#<Person:0x292fbe8>):002:0> [name, age]
  => ["Random Person", 23]
  irb(#<Person:0x292fbe8>):003:0> [@name, @age]
  => ["Random Person", 23]
  irb(#<Person:0x292fbe8>):004:0> self
  => #<Person:0x292fbe8 @age=23, @name="Random Person">
  irb(#<Person:0x292fbe8>):005:0> @age += 1; self
  => #<Person:0x292fbe8 @age=24, @name="Random Person">
  irb(#<Person:0x292fbe8>):006:0> exit
  Executing break point "Person#name" at file.rb:9 in `name'
  irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
  Name: Overriden name

Breakpoint sessions will automatically have a few convenience methods available. See Breakpoint::CommandBundle for a list of them.

Breakpoints can also be used remotely over sockets. This is implemented by running part of the IRB session in the application and part of it in a special client. You have to call Breakpoint.activate_drb to enable support for remote breakpoints and then run breakpoint_client.rb which is distributed with this library. See the documentation of Breakpoint.activate_drb for details.

Please use breakpoint() instead of Breakpoint.breakpoint(). If you use Breakpoint.breakpoint() you might get a shell with a wrong self context meaning that you will not be able to access instance variables, call methods on the object where you are breakpointing and so on. You will however still be able to access local variables.

The former is believed to be caused by a bug in Ruby and it has been reported to ruby-core: www.ruby-forum.com/topic/67255

[Source]

     # File lib/breakpoint.rb, line 120
120:   def breakpoint(id = nil, context = nil, &block)
121:     callstack = caller
122:     callstack.slice!(0, 3) if callstack.first["breakpoint"]
123:     file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
124: 
125:     message = "Executing break point " + (id ? "#{id.inspect} " : "") +
126:               "at #{file}:#{line}" + (method ? " in `#{method}'" : "")
127: 
128:     if context then
129:       return handle_breakpoint(context, message, file, line, &block)
130:     end
131: 
132:     Binding.of_caller do |binding_context|
133:       handle_breakpoint(binding_context, message, file, line, &block)
134:     end
135:   end

Deactivates a running Breakpoint service.

[Source]

     # File lib/breakpoint.rb, line 442
442:   def deactivate_drb
443:     Thread.exclusive do
444:       @service.stop_service unless @service.nil?
445:       @service = nil
446:       @use_drb = false
447:       @drb_service = nil
448:     end
449:   end

Returns true when Breakpoints are used over DRb. Breakpoint.activate_drb causes this to be true.

[Source]

     # File lib/breakpoint.rb, line 453
453:   def use_drb?
454:     @use_drb == true
455:   end

[Validate]