From byebug to ruby/debug

·

8 min read

Featured on Hashnode

Link to Japanese version: https://techracho.bpsinc.jp/hachi8833/2022_09_01 (Hashnode has issues generating the right link for this url)

Link to my talk: ruby/debug - The best investment for your productivity

Switching to a new debugger and potentially changing your debugging process could be scary. So I hope this post can help you get familiar with ruby/debug and make the migration smoother.

(In the rest of the article, I'll use debug to refer to ruby/debug)

Disclaimers:

  • I'm not as experienced with byebug as with debug. So please let me know if I listed incorrect/outdated information.
  • Its purpose is to give a higher-level comparison. To learn more about debug's specific usages, please check its official documentation.
  • It doesn't contain all the features but should already cover most of them.

Advantages of debug

Before we get into individual features, I want to quickly mention some advantages of debug:

Disadvantages of debug

  • Less flexible on thread control
  • Doesn't work well with Fiber yet
  • Less learning resources
  • No per-project configuration

byebug vs debug

Installation

(Although Ruby 3.1 comes with debug v1.4, I recommend always using its latest release)

byebugdebug
Supported Ruby version2.5+2.6+
Gem installgem install byebuggem install debug
Bundlergem "byebug"gem "debug"
DependenciesNoirb, reline
Has C extensionYesYes
Latest version11.1.3 (23 Apr 2020)1.8.0 (9 May 2023)

Start Debugging

byebugdebug
Via executablebyebug foo.rbrdbg foo.rb
Via debugger statementbyebugbinding.break, binding.b, debugger (the same)
Via requiringNorequire "debug/start"

User Experience Features

byebugdebug
ColorizingNoYes
Command historyYesYes
Help commandh[elp]h[elp]
Edit source fileeditedit

Evaluation

Code evaluation in REPL is more or less the same between byebug and debug, that is:

  • If the expression doesn't match a console command, like my_method(arg), it'll be evaluated as Ruby code
  • If the expression matches a console command, like n, you can use console commands to evaluate it
byebugdebug
Execute debugger commands<cmd><cmd>
Avoid expression/command conflictseval <expr>pp <expr>, p <expr>, or eval <expr>

Flow Control & Frame Navigation

debug provides the same stepping commands as byebug does. So byebug users can switch to debug without learning new behaviors.

byebugdebug
Step ins[tep]s[tep]
Step overn[ext]n[ext]
Finishfin[ish]fin[ish]
Move to <id> framef[rame] <id>f[rame] <id>
Move up a frameupup
Move down a framedowndown
Move up n framesup <n>No
Move down n framesdown <n>No
Continue the programc[ontinue]c[ontinue]
Quit the debuggerq[uit]q[uit]
Kill the programkillkill

Thread Control

As previously mentioned, debug stops all threads when suspended and doesn't allow per-thread management at the moment. So it has simpler thread-related commands.

byebugdebug
Thread suspensionOnly the current threadAll threads
List all threadsth[read] lth[read]
Switch to threadth[read] switch <id>th[read] <id>
Stop a threadth[read] stop <id>No
Resume a threadth[read] resume <id>No

Breakpoint

Although byebug already has decent breakpoints support, debug takes it to another level by:

  • Allowing breakpoints to execute commands with pre: and do: options
  • Supporting call-location-based triggering condition with the path: option
  • Supporting specialized catch and watch breakpoints

Setting Breakpoints

byebugdebug
On lineb[reak] <line>b[reak] <line>
On file:lineb[reak] <file>:<line>b[reak] <file>:<line>
On a methodb[reak] <class>#<method>b[reak] <class>#<method>
With a conditionb[reak] ... if <expr>b[reak] ... if: <expr>
With a path conditionNob[reak] ... path: /path/
To run a command and continueNob[reak] ... do: <cmd>
To run a command before stoppingNob[reak] ... pre: <cmd>
Exception breakpointNocatch <ExceptionClass>
Instance variable watch breakpointNowatch <@ivar>

Managing Breakpoints

byebugdebug
List all breakpointsinfo breakpointsb[reak]
Set a breakpointb[reak] ...b[reak] ...
Delete a breakpointdel[ete] <id>del[ete] <id>
Delete all breakpointsdel[ete]del[ete]

Information

Backtrace

Backtrace in debug contains values of method call or block arguments. This small improvement can save you from inspecting them manually between frames.

The filtering support can help you focus on relevant frames and ignore the ones from frameworks or libraries.

byebugdebug
Show backtracewhere, backtrace, btbacktrace, bt
Displaying method/block arguments in backtraceNoYes
Filter backtraceNobt /regexp/
Limit the number of backtracesNobt <n>

Varibles/Constants

byebugdebug
Show local varibalesvar localinfo l
Show instance variablesvar instanceinfo i
Show global varibalesvar globalinfo g
Show constantsvar constinfo c
Show only argumentsvar argsNo (included in info l)
Filter variables/constants by namesNoinfo ... /regexp/

Methods

While debug doesn't have a dedicated command to list an object's methods, it has a ls command that's similar to irb or pry's.

byebugdebug
obj.methodsmethod instance objNo
obj.methods(false)method obj.classls obj

Tracing

I will write another dedicated article to introduce debug's tracing functionalities, but I encourage you to already give them a try from time to time, especially:

  • trace exception - to monitor exceptions raised in your code. You may be surprised by the exceptions raised and rescued in your application under the surface.
  • trace object <expr> - to observe an object's activities: receiving a method call or being passed to a method call.

    trace object

byebugdebug
Line tracerset linetracetrace line
Global variable tracertarcevarNo
Allow multiple tracersNoYes
Method call tracerNotrace call
Exceptions tracerNotrace exception
Ruby object tracerNotrace object <expr>
Filter tracing outputNotrace ... /regexp/
Disable tracerset linetrace falsetrace off [tracer type]
Disable the specific tracerNotrace off <id>

Configuration

debug supports a wide range of configurations to make it fit your needs. But it doesn't support per-project RC files due to security concerns.

byebugdebug
List all configsNoconfig
Show a configshow <name>config show <name>
Set a configset <name> <value>config set <name> <value>
RC file name.byebugrc.rdbgrc (or .rdbgrc.rb for Ruby script)
RC file locations$HOME and project root$HOME

Remote Debugging

Remote debugging functionality is becoming crucial as we start containerizing our development environment and don't always have direct standard IO access. It's also crucial for connecting to different debugger clients like VSCode or Chrome.

So if you have needs in this area, migrating to debug is your best option.

byebugdebug
Connect via TCP/IPYesYes
Connect via Unix Domain SocketNoYes
VSCode integration through Debug Adapter Protocol (DAP)NoYes
Chrome integration through Chrome DevTools Protocol (CDP)NoYes

Wrapping Up

To most users, switching from byebug to debug should take little effort. But the potential productivity gain from debug's features could be significant.

byebug is a great debugger and has served the community well for many years. But as its development slows down (last release was 2 years ago) and debug receives constant updates from the Ruby core, the gap between them will only widen.

So adding debug to your toolbelt and exploring its powerful features will be a great productivity investment for the long term.

References