CrackedRuby logo

CrackedRuby

Debug Gem

Overview

Debug Gem provides Ruby's standard debugging interface, replacing the deprecated debugger and byebug gems as the official debugging solution. The gem integrates deeply with Ruby's virtual machine to offer breakpoint management, step-through debugging, variable inspection, and call stack navigation.

The core functionality centers around the Debugger class and its associated command interface. When activated, Debug Gem pauses program execution at specified breakpoints and opens an interactive REPL session where developers can examine program state, modify variables, and control execution flow.

Debug Gem operates through several key mechanisms: breakpoint insertion via binding.break, remote debugging capabilities through DAP (Debug Adapter Protocol), and integration with development servers. The gem supports both synchronous debugging where execution halts completely, and asynchronous debugging where multiple threads can be inspected simultaneously.

# Basic breakpoint insertion
def calculate_total(items)
  binding.break  # Execution pauses here
  total = items.sum { |item| item.price }
  total * 1.08  # Tax calculation
end

The debug session provides access to local variables, instance variables, and method definitions within the current scope. Developers can execute arbitrary Ruby code, call methods, and inspect object states without modifying the original program flow.

# Conditional breakpoint
def process_orders(orders)
  orders.each do |order|
    binding.break if order.total > 1000  # Only break for large orders
    order.process
  end
end

Debug Gem maintains compatibility with Ruby's standard REPL features while extending functionality for debugging-specific tasks. The command interface supports navigation commands, variable inspection, and execution control through a consistent syntax that integrates with existing Ruby development workflows.

Basic Usage

Debug Gem activation requires adding the gem to your project and inserting breakpoints using binding.break. The most direct approach involves placing breakpoint calls at specific code locations where inspection is needed.

require 'debug'

class OrderProcessor
  def initialize(orders)
    @orders = orders
    binding.break  # Inspect initial state
  end
  
  def process_all
    @orders.each_with_index do |order, index|
      binding.break if index == 5  # Break on sixth order
      process_single(order)
    end
  end
  
  private
  
  def process_single(order)
    binding.break(pre: -> { order.status == 'pending' })  # Conditional break
    order.calculate_shipping
    order.apply_discounts
  end
end

Once a breakpoint is hit, Debug Gem opens an interactive session with several navigation commands. The step command advances execution by one line, while next advances to the next line in the current method without descending into method calls. The continue command resumes normal execution until the next breakpoint.

Variable inspection uses standard Ruby syntax within the debug session. Local variables, instance variables, and method calls execute within the current scope context. The info command displays comprehensive information about the current execution state.

# In debug session:
# (rdbg) p order           # Print order object  
# (rdbg) p @orders.count   # Print instance variable
# (rdbg) info locals       # List all local variables
# (rdbg) step              # Advance one line
# (rdbg) continue          # Resume execution

Debug Gem supports remote debugging through the --open flag, which starts a debug server accessible via TCP connection. This enables debugging applications running in containers, remote servers, or separate processes.

# Start with remote debugging
# ruby -r debug/open script.rb
# Connect from another terminal: rdbg --attach

class RemoteService
  def start
    binding.break  # Will be accessible remotely
    perform_complex_calculation
  end
end

Method breakpoints allow setting breakpoints on specific methods without modifying source code. The break command within a debug session can set breakpoints on method definitions, class instantiations, or exception conditions.

# Debug session commands:
# (rdbg) break User#save              # Break when User#save is called
# (rdbg) break User.new               # Break on User instantiation  
# (rdbg) break rescue StandardError   # Break on exception rescue

Debug Gem maintains a history of commands and supports readline functionality for command completion and editing. The history command displays previous commands, while tab completion assists with variable names and method suggestions.

Advanced Usage

Debug Gem provides sophisticated breakpoint management through programmatic APIs and advanced command configurations. Custom breakpoint conditions can execute complex Ruby expressions to determine when execution should pause, enabling precise control over debugging scenarios.

require 'debug'

class AdvancedDebugger
  def self.configure_conditional_breaks(processor)
    # Set breakpoint with complex condition
    processor.define_singleton_method(:debug_checkpoint) do |context|
      condition = -> { 
        context[:user_id] > 1000 && 
        context[:order_total] > 500.0 && 
        Time.current.hour.between?(9, 17) 
      }
      binding.break(pre: condition, do: -> { puts "High value order detected" })
    end
  end
  
  def self.setup_method_tracing(target_class)
    target_class.prepend(Module.new do
      def method_added(name)
        original_method = instance_method(name)
        define_method(name) do |*args, &block|
          binding.break(pre: -> { args.length > 3 })  # Break on complex calls
          original_method.bind(self).call(*args, &block)
        end
        super
      end
    end)
  end
end

Thread debugging requires careful consideration of concurrent execution patterns. Debug Gem can attach to specific threads and provides commands for switching between thread contexts during debugging sessions.

class ConcurrentProcessor
  def initialize
    @threads = []
    @results = Concurrent::Hash.new
  end
  
  def process_parallel(data_chunks)
    data_chunks.each_with_index do |chunk, index|
      @threads << Thread.new do
        Thread.current[:name] = "worker_#{index}"
        binding.break(pre: -> { Thread.current[:name] == 'worker_3' })
        
        result = process_chunk(chunk)
        @results[index] = result
      end
    end
    
    @threads.each(&:join)
  end
  
  private
  
  def process_chunk(chunk)
    # Complex processing that might need debugging
    chunk.map { |item| transform_item(item) }.compact
  end
end

Advanced breakpoint management includes watchpoints that trigger when variable values change, and tracepoints that monitor method calls, returns, and exceptions across the entire program execution.

# Debug session advanced commands:
# (rdbg) watch @user_count                    # Break when @user_count changes
# (rdbg) trace call User                      # Trace all User method calls
# (rdbg) catch ArgumentError                  # Break on ArgumentError
# (rdbg) eval Thread.list.map(&:name)         # Execute complex expressions

Custom debug configurations can be saved in .rdbgrc files to establish consistent debugging environments across projects. These configurations can define custom commands, set default breakpoints, and configure output formatting.

# .rdbgrc configuration file
config.no_color = false
config.use_colorize = true
config.show_src_lines = 10
config.show_frames = 5

# Custom command definition
config.command('show_request') do
  puts "Request: #{request.inspect}"
  puts "Params: #{params.inspect}"  
  puts "Session: #{session.keys}"
end

# Auto-break configuration
config.postmortem = true  # Enter debugger on unhandled exceptions

Integration with development tools extends Debug Gem functionality through editor plugins and IDE extensions. The Debug Adapter Protocol (DAP) enables connection with VS Code, Vim, and other editors for graphical debugging interfaces.

# Launch with DAP support
# ruby -r debug/open_nonstop --dap-port=12345 app.rb

class DevelopmentIntegration
  def self.setup_editor_debugging
    if ENV['EDITOR_DEBUG']
      require 'debug/open_nonstop'
      DEBUGGER__.start_dap_server(host: '0.0.0.0', port: ENV['DAP_PORT'] || 12345)
    end
  end
  
  def critical_method(data)
    # Breakpoint will appear in editor
    binding.break
    process_data(data)
  end
end

Production Patterns

Production debugging requires careful consideration of performance impact and security implications. Debug Gem provides mechanisms for enabling debugging features only in development environments while maintaining production safety.

class ProductionSafeDebugger
  def self.configure(environment:, enabled_features: [])
    return unless ['development', 'staging'].include?(environment)
    
    if enabled_features.include?(:remote_debugging)
      require 'debug/open_nonstop'
      port = ENV.fetch('DEBUG_PORT', 12345).to_i
      DEBUGGER__.start_dap_server(host: '127.0.0.1', port: port)
    end
    
    if enabled_features.include?(:conditional_breaks)
      setup_production_breakpoints
    end
  end
  
  private
  
  def self.setup_production_breakpoints
    # Only break on specific error conditions
    TracePoint.new(:raise) do |tp|
      if tp.raised_exception.is_a?(CustomBusinessError)
        binding.break(pre: -> { ENV['ENABLE_ERROR_DEBUGGING'] == 'true' })
      end
    end.enable
  end
end

Rails applications require special consideration for Debug Gem integration due to request-response cycles and multi-threaded server architectures. Debug Gem can be configured to work within Rails development servers while avoiding interference with normal request processing.

# config/environments/development.rb
Rails.application.configure do
  # Enable debug gem for Rails development
  if Rails.env.development?
    config.after_initialize do
      if ENV['RAILS_DEBUG_ENABLED']
        require 'debug/open_nonstop'
        DEBUGGER__.start_dap_server(host: '127.0.0.1', port: 12346)
      end
    end
  end
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :debug_checkpoint, if: :debugging_enabled?
  
  private
  
  def debug_checkpoint
    if params[:debug] && Rails.env.development?
      binding.break(do: -> { 
        puts "Request: #{request.method} #{request.path}"
        puts "User: #{current_user&.id}"
      })
    end
  end
  
  def debugging_enabled?
    Rails.env.development? && ENV['CONTROLLER_DEBUG'] == 'true'
  end
end

Container environments present unique challenges for Debug Gem usage. Docker configurations must expose debug ports and configure networking to allow external connections to debugging sessions.

# Dockerfile debugging configuration
# EXPOSE 12345
# ENV DEBUG_PORT=12345

class ContainerDebugSetup
  def self.configure_for_container
    if ENV['CONTAINER_DEBUG'] == 'true'
      require 'debug/open'
      
      # Bind to all interfaces in container
      Thread.new do
        begin
          DEBUGGER__.start_dap_server(
            host: '0.0.0.0', 
            port: ENV.fetch('DEBUG_PORT', 12345).to_i
          )
        rescue => e
          puts "Debug server failed to start: #{e.message}"
        end
      end
    end
  end
end

Monitoring and logging integration ensures debug sessions are tracked and managed appropriately in production-like environments. Debug Gem can be configured to log debugging activity and integrate with existing monitoring systems.

class DebugMonitoring
  def self.setup_logging(logger:)
    DEBUGGER__.singleton_class.prepend(Module.new do
      def start_session(*)
        logger.info("Debug session started", 
          thread: Thread.current.object_id,
          location: caller_locations(1, 1).first.to_s,
          timestamp: Time.current.iso8601
        )
        super
      end
      
      def end_session(*)
        logger.info("Debug session ended",
          thread: Thread.current.object_id,
          timestamp: Time.current.iso8601
        )
        super
      end
    end)
  end
  
  def self.setup_metrics(metrics_client:)
    # Track debugging usage patterns
    original_break = Kernel.instance_method(:binding)
    Kernel.define_method(:binding) do
      result = original_break.bind(self).call
      if caller_locations.any? { |loc| loc.label == 'break' }
        metrics_client.increment('debug.breakpoint_hit', 
          tags: { file: caller_locations.first.path })
      end
      result
    end
  end
end

Performance monitoring during debug sessions prevents debugging from significantly impacting application performance. Debug Gem can be configured with timeouts and resource limits to ensure debugging sessions don't consume excessive system resources.

class PerformanceAwareDebugger
  def self.configure_limits
    DEBUGGER__.configure do |config|
      config.session_timeout = 300  # 5 minute timeout
      config.max_memory_usage = 100 * 1024 * 1024  # 100MB limit
      config.max_string_display = 1000  # Limit large string display
    end
    
    # Monitor debug session resource usage
    Thread.new do
      loop do
        if DEBUGGER__.session_active?
          memory_usage = `ps -o rss= -p #{Process.pid}`.to_i * 1024
          if memory_usage > 500 * 1024 * 1024  # 500MB
            DEBUGGER__.end_session("Memory limit exceeded")
          end
        end
        sleep(10)
      end
    end
  end
end

Common Pitfalls

Debug Gem usage involves several common mistakes that can lead to unexpected behavior or performance problems. The most frequent issue occurs when developers leave binding.break statements in production code, causing applications to hang indefinitely when breakpoints are hit.

# PROBLEMATIC: Debug statement left in production code
class PaymentProcessor
  def charge_card(amount)
    binding.break  # This will hang in production!
    process_payment(amount)
  end
end

# SOLUTION: Environment-conditional debugging
class PaymentProcessor  
  def charge_card(amount)
    binding.break if Rails.env.development?  # Safe for production
    process_payment(amount)
  end
  
  # Better approach: Use custom debug method
  def debug_break
    binding.break if ENV['DEBUG_MODE'] == 'true'
  end
  
  def charge_card(amount)
    debug_break
    process_payment(amount)
  end
end

Thread safety issues emerge when debugging multi-threaded applications. Debug Gem sessions can interfere with concurrent execution, causing race conditions or deadlocks that wouldn't occur during normal execution.

# PROBLEMATIC: Debugging in thread-sensitive code
class ThreadedProcessor
  def initialize
    @mutex = Mutex.new
    @shared_resource = []
  end
  
  def add_item(item)
    @mutex.synchronize do
      binding.break  # Dangerous: holds mutex during debug session
      @shared_resource << item
    end
  end
end

# SOLUTION: Debug before critical sections
class ThreadedProcessor
  def add_item(item)
    binding.break if ENV['DEBUG_THREADS']  # Debug outside mutex
    
    @mutex.synchronize do
      @shared_resource << item
    end
  end
  
  # Alternative: Use thread-specific debugging
  def add_item_with_safe_debug(item)
    if Thread.current[:debug_enabled]
      item_copy = item.dup  # Work with copy during debugging
      binding.break(do: -> { puts "Processing: #{item_copy.inspect}" })
    end
    
    @mutex.synchronize do
      @shared_resource << item
    end
  end
end

Variable modification during debug sessions can alter program behavior in unexpected ways. Changes made to objects during debugging persist after continuing execution, potentially masking bugs or creating new ones.

# PROBLEMATIC: Modifying state during debugging
class OrderCalculator
  def calculate_total(items)
    total = 0
    items.each do |item|
      binding.break
      # In debug session: item.price = 999  # Accidentally modified!
      total += item.price
    end
    total
  end
end

# SOLUTION: Use inspection methods instead of modification
class OrderCalculator
  def calculate_total(items)
    total = 0
    items.each_with_index do |item, index|
      binding.break(do: -> {
        puts "Item #{index}: #{item.inspect}"
        puts "Current total: #{total}"
        # Don't modify item directly - use inspection only
      })
      total += item.price
    end
    total
  end
  
  # Create debug-safe inspection methods
  def debug_item_state(item, index, running_total)
    {
      index: index,
      item_id: item.id,
      price: item.price,
      running_total: running_total,
      item_copy: item.dup  # Safe copy for inspection
    }
  end
end

Infinite loops and recursive breakpoints can occur when debug conditions create circular debugging scenarios. This happens when breakpoint conditions themselves trigger additional breakpoints or when debugging recursive methods without proper guards.

# PROBLEMATIC: Recursive debugging
def fibonacci(n)
  binding.break  # Breaks on every recursive call!
  return n if n <= 1
  fibonacci(n-1) + fibonacci(n-2)
end

# SOLUTION: Use depth-limited or conditional debugging  
def fibonacci(n, debug_depth = 0)
  # Only debug first few levels
  binding.break if debug_depth < 3 && ENV['DEBUG_RECURSION']
  
  return n if n <= 1
  fibonacci(n-1, debug_depth + 1) + fibonacci(n-2, debug_depth + 1)
end

# Alternative: Debug specific conditions only
def fibonacci(n)
  binding.break if n > 10 && n % 5 == 0  # Debug only specific values
  return n if n <= 1
  fibonacci(n-1) + fibonacci(n-2)
end

Performance degradation occurs when debug configurations remain enabled in production-like environments. Debug Gem maintains internal state and monitoring that consumes memory and CPU cycles even when breakpoints aren't hit.

# PROBLEMATIC: Always loading debug gem
require 'debug'  # Loaded even in production

class Application
  def start
    setup_debugging  # Always called
    run_main_loop
  end
end

# SOLUTION: Conditional loading and setup
class Application
  def start
    setup_debugging if debug_mode_enabled?
    run_main_loop  
  end
  
  private
  
  def debug_mode_enabled?
    ENV['RAILS_ENV'] == 'development' || ENV['DEBUG_MODE'] == 'true'
  end
  
  def setup_debugging
    require 'debug' unless defined?(DEBUGGER__)
    configure_debug_environment
  end
  
  def configure_debug_environment
    return unless debug_mode_enabled?
    
    DEBUGGER__.configure do |config|
      config.skip_path = [/gems/]  # Skip gem code
      config.show_src_lines = 5   # Limit output
    end
  end
end

Remote debugging security issues arise when debug servers are exposed without proper authentication or network restrictions. Debug sessions provide full Ruby execution access, making them significant security risks if improperly configured.

# PROBLEMATIC: Unsecured remote debugging
DEBUGGER__.start_dap_server(host: '0.0.0.0', port: 12345)  # Exposed to all!

# SOLUTION: Secure remote debugging setup
class SecureDebugServer
  def self.start_if_authorized
    return unless authorized_debug_environment?
    
    host = ENV.fetch('DEBUG_HOST', '127.0.0.1')  # Default to localhost
    port = ENV.fetch('DEBUG_PORT', 12345).to_i
    
    # Add authentication if needed
    if ENV['DEBUG_AUTH_TOKEN']
      setup_authenticated_debugging(host, port)
    else
      DEBUGGER__.start_dap_server(host: host, port: port)
    end
  end
  
  private
  
  def self.authorized_debug_environment?
    allowed_environments = ['development', 'test']
    current_env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
    
    allowed_environments.include?(current_env) && 
    ENV['DEBUG_ENABLED'] == 'true'
  end
  
  def self.setup_authenticated_debugging(host, port)
    # Implement token-based authentication for remote debugging
    expected_token = ENV['DEBUG_AUTH_TOKEN']
    
    # Custom debug server with authentication
    require 'socket'
    server = TCPServer.new(host, port)
    
    Thread.new do
      loop do
        client = server.accept
        handle_authenticated_client(client, expected_token)
      end
    end
  end
end

Reference

Core Methods

Method Parameters Returns Description
binding.break pre: Proc, do: Proc, path: String nil Sets breakpoint at current location with optional conditions
DEBUGGER__.start host: String, port: Integer void Starts debug server with network configuration
DEBUGGER__.configure block void Configures global debug settings
DEBUGGER__.stop none void Stops debug server and closes sessions

Debug Session Commands

Command Syntax Description
step s [count] Execute next line, entering method calls
next n [count] Execute next line in current method
continue c Resume execution until next breakpoint
finish fin Execute until current method returns
break b <location> Set breakpoint at specified location
delete del <number> Delete breakpoint by number
info i <type> Display information (locals, ivars, methods)
eval e <code> Evaluate Ruby expression in current context
watch w <variable> Set watchpoint on variable
trace trace <type> Enable method or line tracing
thread th [list|switch] List or switch between threads
backtrace bt [count] Display call stack
help h [command] Display help information

Breakpoint Locations

Location Type Syntax Example
Method ClassName#method_name User#save
Class Method ClassName.method_name User.find
Line Number filename:line app.rb:42
Exception rescue ExceptionClass rescue ArgumentError

Configuration Options

Option Type Default Description
no_color Boolean false Disable colored output
use_colorize Boolean true Enable syntax highlighting
show_src_lines Integer 10 Lines of source code to display
show_frames Integer 2 Number of stack frames to show
skip_path Array [] Paths to skip during debugging
postmortem Boolean false Enter debugger on unhandled exceptions
session_timeout Integer 300 Debug session timeout in seconds

Environment Variables

Variable Purpose Example
DEBUG_MODE Enable/disable debugging DEBUG_MODE=true
DEBUG_PORT Set debug server port DEBUG_PORT=12345
DEBUG_HOST Set debug server host DEBUG_HOST=0.0.0.0
RUBY_DEBUG_NO_COLOR Disable colored output RUBY_DEBUG_NO_COLOR=1
RUBY_DEBUG_SHOW_SRC_LINES Source lines to display RUBY_DEBUG_SHOW_SRC_LINES=5

Exception Classes

Exception Description
DEBUGGER__::Error Base debug error class
DEBUGGER__::ConfigurationError Invalid configuration
DEBUGGER__::ConnectionError Remote debugging connection failed
DEBUGGER__::SessionError Debug session error

Integration Patterns

Pattern Use Case Configuration
Rails Development Web application debugging Add to Gemfile development group
Remote Debugging Container/server debugging Start with --open flag
DAP Integration Editor/IDE debugging Configure DAP server port
Test Suite Debugging RSpec/Test debugging Conditional breakpoints in tests

Command Line Options

Option Description
-r debug/open Start with remote debugging enabled
--dap-port=PORT Specify DAP server port
--no-color Disable colored output
--show-src-lines=N Set source line display count