An Interest In:
Web News this Week
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
- March 28, 2024
- March 27, 2024
- March 26, 2024
A Sneak Peek of Ruby's New Debugger!
debug
is Ruby's new debugger and will be included in Ruby 3.1. Since I've been both contributing to and using it for a while, I feel it's time to give you guys a sneak peek before its 1.0
release
(Since it's not officially released yet, any feature mentioned in this article could still be modified/removed in the released version)
Introduction
As I have mentioned, it's planned to be a standard library of Ruby 3.1. And currently, you can install it as a gem, like:
$ gem install debug --pre
or
# Gemfile# it's under active development, so I suggest using GitHub as source when possiblegem "debug", github: "ruby/debug"
Functionality-wise, debug
is similar to the famous GDB
debugger and Ruby's byebug
gem. It provides a rich set of debug commands and has some unique features.
Quoted from its README:
New debug.rb has several advantages:- Fast: No performance penalty on non-stepping mode and non-breakpoints.- Remote debugging: Support remote debugging natively. - UNIX domain socket - TCP/IP - VSCode/DAP integration (VSCode rdbg Ruby Debugger - Visual Studio Marketplace)- Extensible: application can introduce debugging support with several ways: - By `rdbg` command - By loading libraries with `-r` command line option - By calling Ruby's method explicitly- Misc - Support threads (almost done) and ractors (TODO). - Support suspending and entering to the console debugging with `Ctrl-C` at most of timing. - Show parameters on backtrace command.
And these are my favorite features:
- It's colorized.
- When showing backtrace with the
backtrace
command, it also shows method arguments, block arguments, and the return value.
=>#0 Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30 #1 block {|ten=10|} in second_call at target.rb:8
- It's possible to script your debug commands with
binding.break
and reduce manual operations. (See the combinations section for examples) - There are several commands to set breakpoints that trigger under different conditions, like
break
,catch
, andwatch
.
binding.break
(alias: binding.b
)
If you're a heavy pry
user like me, you can use a familiar binding.break
(or just binding.b
) to kick off the debug session as usual.
But binding.break
is actually more powerful than binding.pry
, because it can take commands!
For example:
binding.b(do: "catch CustomException")
- debugger will execute the command (catch customExeption
) and continue the program.binding.b(pre: "catch CustomException")
- debugger will execute the command (catch customExeption
) and stop at the line.
(To execute multiple commands, use ;;
as the separator: "cmd1 ;; cmd2 ;; cmd3"
)
Fequently Used Commands
The new debugger has many powerful commands. And here are the ones I use the most:
break
(alias: b
)
class A def foo; end def self.bar; endendclass B < A; endclass C < A; endB.barC.barb1 = B.newb2 = B.newc = C.newb1.foob2.fooc.foo
Basic Usages
b A#foo
- stops whenb1.foo
,b2.foo
, andc.foo
is calledb A.bar
- stops whenB.bar
andC.bar
is calledb B#foo
- stops whenb1.foo
andb2.foo
is calledb B.bar
- stops whenB.bar
is calledb b1.foo
- stops whenb1.foo
is called
Commands
b b1.foo do: cmd
- executescmd
whenb1.foo
is called but doesn't stopb b1.foo pre: cmd
- executescmd
whenb1.foo
is called and stops
catch
class FooException < StandardError; endclass BarException < StandardError; enddef raise_foo raise FooExceptionenddef raise_bar raise BarExceptionendraise_fooraise_bar
catch StandardError
- stops when any instance ofStandardError
is raised, includingFooException
andBarException
catch FooException
- stops whenFooException
is raised
backtrace
(alias bt
)
Example Output:
=>#0 Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30 #1 block {|ten=10|} in second_call at target.rb:8 #2 Foo#third_call_with_block(block=#<Proc:0x00007f9283101568 target.rb:7>) at target.rb:15 #3 Foo#second_call(num=20) at target.rb:7 #4 Foo#first_call at target.rb:3 #5 <main> at target.rb:23
bt
- shows all frames on the stackbt 10
- only shows the first 10 framesbt /my_lib/
- only shows the frames with path that matchesmy_lib
outline
(alias ls
)
Similar to the ls
command in irb
or pry
.
binding.b
+ Command Combinations
binding.b(do: "b Foo#bar do: bt")
It allows you to inspect a method call's backtrace without touching the method definition or typing commands manually.
Script:
binding.b(do: "b Foo#bar do: bt")class Foo def bar endenddef some_method Foo.new.barendsome_method
Output:
DEBUGGER: Session start (pid: 75555)[1, 10] in target.rb=> 1| binding.b(do: "b Foo#bar do: bt") 2| 3| class Foo 4| def bar 5| end 6| end 7| 8| def some_method 9| Foo.new.bar 10| end=>#0 <main> at target.rb:1(rdbg:binding.break) b Foo#bar do: btuninitialized constant Foo#0 BP - Method (pending) Foo#bar do: btDEBUGGER: BP - Method Foo#bar at target.rb:4 do: bt is activated.[1, 10] in target.rb 1| binding.b(do: "b Foo#bar do: bt") 2| 3| class Foo=> 4| def bar 5| end 6| end 7| 8| def some_method 9| Foo.new.bar 10| end=>#0 Foo#bar at target.rb:4 #1 Object#some_method at target.rb:9 # and 1 frames (use `bt' command for all frames)Stop by #0 BP - Method Foo#bar at target.rb:4 do: bt(rdbg:break) bt=>#0 Foo#bar at target.rb:4 #1 Object#some_method at target.rb:9 #2 <main> at target.rb:12
binding.b(do: "b Foo#bar do: info")
It allows you to inspect a method's environment (e.g. argument) when called:
Script:
binding.b(do: "b Foo#bar do: info")class Foo def bar(a) a endenddef some_method Foo.new.bar(10)endsome_method
Output:
DEBUGGER: Session start (pid: 75924)[1, 10] in target.rb=> 1| binding.b(do: "b Foo#bar do: info") 2| 3| class Foo 4| def bar(a) 5| a 6| end 7| end 8| 9| def some_method 10| Foo.new.bar(10)=>#0 <main> at target.rb:1(rdbg:binding.break) b Foo#bar do: infouninitialized constant Foo#0 BP - Method (pending) Foo#bar do: infoDEBUGGER: BP - Method Foo#bar at target.rb:4 do: info is activated.[1, 10] in target.rb 1| binding.b(do: "b Foo#bar do: info") 2| 3| class Foo 4| def bar(a)=> 5| a 6| end 7| end 8| 9| def some_method 10| Foo.new.bar(10)=>#0 Foo#bar(a=10) at target.rb:5 #1 Object#some_method at target.rb:10 # and 1 frames (use `bt' command for all frames)Stop by #0 BP - Method Foo#bar at target.rb:4 do: info(rdbg:break) info%self = #<Foo:0x00007fdac491c200>a = 10
I'm a Rails developer, so I usually put the combination code at the beginning of a controller action, like:
class SomeController < ApplicationController def index binding.b(pre: "b User#buggy_method do: info") # other code endend
And then the debugger would execute the command and/or stops at the method I expected.
I don't need to jump between multiple files for adding binding.pry
or puts
anymore
A Small Drawback
However, the new debugger isn't all perfect (yet). Unlike in byebug
or pry
, you can't directly evaluate a Ruby expression in the debug session:
(rdbg) 1 + 1unknown command: 1 + 1
To evaluate an expression, you need to use p
or pp
command:
(rdbg) p 1 + 1=> 2
But according to the project's maintainer @ko1's 'comment, expression evaluation may be supported before the official 1.0
release.
Final Thoughts
Although it's not officially released yet, I've started using it at work daily. And I believe it'll soon become an must-have tool in every Rubyists' toolbox. So if you're curious about its capability, I encourage to give it a try
Original Link: https://dev.to/st0012/a-sneak-peek-of-ruby-s-new-debugger-5caa
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To