CrackedRuby logo

CrackedRuby

byebug

Overview

Byebug provides interactive debugging capabilities for Ruby programs through breakpoints, step execution, and runtime inspection. The gem implements a command-line debugger that integrates directly into Ruby code execution, allowing developers to pause program flow and examine application state at specific points.

The debugger operates through the byebug method call, which creates breakpoints where execution stops. When a breakpoint triggers, byebug opens an interactive prompt that accepts debugging commands for navigation, inspection, and code evaluation. The debugger maintains a call stack context, variable scope access, and step-by-step execution control.

require 'byebug'

def calculate_total(items)
  byebug  # Execution stops here
  total = 0
  items.each do |item|
    total += item[:price] * item[:quantity]
  end
  total
end

items = [
  { name: 'Widget', price: 10.99, quantity: 2 },
  { name: 'Gadget', price: 25.50, quantity: 1 }
]

result = calculate_total(items)

Byebug supports remote debugging through TCP connections, conditional breakpoints based on expressions, and integration with popular editors and IDEs. The debugger works with standard Ruby programs, Rails applications, and test suites without requiring application modifications beyond adding breakpoint calls.

The gem provides thread-aware debugging for concurrent applications and maintains separate debugging sessions for each thread. Remote debugging enables debugging applications running in different processes or on remote servers through network connections.

Basic Usage

Installing byebug requires adding the gem to your application's dependencies and calling the byebug method where debugging should begin. The debugger provides immediate access to the current execution context, including local variables, method parameters, and instance variables.

require 'byebug'

class OrderProcessor
  def initialize(orders)
    @orders = orders
    @processed_count = 0
  end

  def process_all
    @orders.each do |order|
      byebug  # Interactive debugging starts
      process_single_order(order)
      @processed_count += 1
    end
  end

  private

  def process_single_order(order)
    order[:status] = 'processed'
    order[:processed_at] = Time.now
  end
end

When execution reaches a byebug call, the debugger displays the current line and opens an interactive prompt. The list command shows surrounding code context, while next executes the following line. The step command enters method calls, and continue resumes normal execution until the next breakpoint.

Variable inspection uses the p command followed by variable names or expressions. The debugger evaluates Ruby expressions in the current context, providing access to local variables, instance variables, and method calls:

def complex_calculation(data, options = {})
  multiplier = options[:multiplier] || 1.5
  threshold = options[:threshold] || 100
  
  byebug
  # At debug prompt:
  # p data           # Shows data contents
  # p multiplier     # Shows 1.5
  # p threshold      # Shows 100
  # data.size        # Evaluates expression
  
  results = data.map { |item| item * multiplier }
  filtered = results.select { |value| value > threshold }
  filtered.sum
end

The backtrace command displays the call stack, showing method calls leading to the current breakpoint. The up and down commands navigate between stack frames, changing the inspection context to different method calls in the execution chain.

Breaking out of the debugger requires the quit command, which terminates the debugging session and continues program execution. The finish command executes until the current method returns, while continue runs until the next breakpoint.

Setting breakpoints programmatically uses the break command with file names and line numbers. The debugger maintains breakpoint lists and provides commands for enabling, disabling, and removing breakpoints during debugging sessions.

Advanced Usage

Byebug supports conditional breakpoints that trigger only when specified expressions evaluate to true. The break command accepts Ruby expressions as conditions, enabling targeted debugging based on variable values, object states, or complex logical conditions.

class DataProcessor
  def process_batch(records)
    records.each_with_index do |record, index|
      # Conditional breakpoint: stop only for problematic records
      byebug if record[:error_count] > 5 || record[:status] == 'failed'
      
      result = transform_record(record)
      validate_result(result)
      save_record(result)
    end
  end

  def transform_record(record)
    # Complex transformation logic
    transformed = record.dup
    transformed[:processed_at] = Time.now
    transformed[:hash] = calculate_hash(record)
    transformed
  end
end

Remote debugging enables debugging applications running on different machines or in containerized environments. The byebug gem provides server and client modes for remote debugging sessions:

require 'byebug/core'

# Start remote debugging server
Byebug.start_server('localhost', 8989)

class RemoteService
  def perform_operation
    byebug  # Triggers remote debugger
    complex_operation
  end
end

# Connect from remote client:
# byebug -R localhost:8989

Thread-aware debugging handles concurrent applications by maintaining separate debugging contexts for each thread. The debugger can switch between threads during debugging sessions, inspect thread-specific variables, and set thread-specific breakpoints:

require 'byebug'

class ConcurrentProcessor
  def initialize
    @results = Queue.new
    @workers = []
  end

  def start_workers(work_items)
    work_items.each_slice(10) do |chunk|
      @workers << Thread.new(chunk) do |items|
        Thread.current[:name] = "Worker-#{Thread.current.object_id}"
        
        items.each do |item|
          byebug  # Thread-specific debugging
          result = process_item(item)
          @results << result
        end
      end
    end
    
    @workers.each(&:join)
  end
end

The debugger provides expression evaluation with full Ruby syntax support, including method calls, variable assignments, and object creation. This capability enables testing fixes and exploring alternative implementations during debugging sessions:

def analyze_performance(dataset)
  start_time = Time.now
  
  byebug
  # At debug prompt, test different approaches:
  # dataset.select { |x| x > 100 }.size
  # dataset.count { |x| x > 100 }
  # benchmark_approach(dataset)
  
  filtered_data = dataset.select { |item| item[:score] > threshold }
  processed_data = filtered_data.map { |item| transform_item(item) }
  
  end_time = Time.now
  { data: processed_data, duration: end_time - start_time }
end

Post-mortem debugging captures debugging sessions from crashed applications or failed operations. The debugger can attach to existing processes or analyze crash dumps for debugging applications that have already terminated.

Error Handling & Debugging

Byebug integrates with Ruby's exception handling system to provide debugging capabilities when errors occur. The debugger can catch exceptions before they propagate, examine error contexts, and provide detailed inspection of failure states.

Setting breakpoints on exception raises enables debugging at the moment errors occur, before stack unwinding begins. This approach provides access to the exact variable states and execution context when problems first manifest:

require 'byebug'

class PaymentProcessor
  def process_payment(payment_data)
    begin
      validate_payment_data(payment_data)
      charge_result = charge_payment(payment_data)
      update_payment_record(charge_result)
    rescue => error
      byebug  # Debug on any exception
      # Inspect error context:
      # p error.message
      # p error.backtrace
      # p payment_data
      # p charge_result if defined?(charge_result)
      
      handle_payment_error(error, payment_data)
    end
  end

  private

  def validate_payment_data(data)
    raise ArgumentError, "Missing amount" unless data[:amount]
    raise ArgumentError, "Invalid amount" if data[:amount] <= 0
    raise ArgumentError, "Missing payment method" unless data[:payment_method]
  end
end

Debugging failed assertions and test cases requires strategic breakpoint placement before assertion calls. The debugger enables inspection of actual versus expected values and examination of object states leading to test failures:

class UserRegistrationTest
  def test_user_creation_with_invalid_email
    user_params = { 
      name: 'John Doe', 
      email: 'invalid-email',
      password: 'secure123' 
    }
    
    user = User.new(user_params)
    
    byebug  # Debug before assertion
    # Inspect validation state:
    # p user.valid?
    # p user.errors.full_messages
    # user.errors.details
    
    assert_not user.valid?, "User should be invalid with malformed email"
    assert_includes user.errors[:email], "is not a valid email address"
  end
end

Debugging complex object hierarchies and nested data structures requires systematic inspection techniques. The debugger provides methods for exploring object relationships and identifying data inconsistencies:

class OrderAnalyzer
  def analyze_order_discrepancies(order)
    byebug
    # Debug complex nested structure:
    # pp order  # Pretty print entire structure
    # order.line_items.each_with_index { |item, i| puts "#{i}: #{item.inspect}" }
    # order.line_items.map(&:total).sum == order.subtotal
    
    line_item_total = order.line_items.sum(&:total)
    calculated_tax = calculate_tax(line_item_total, order.tax_rate)
    expected_total = line_item_total + calculated_tax + order.shipping_cost
    
    if expected_total != order.total
      byebug  # Debug discrepancy
      # p line_item_total
      # p calculated_tax
      # p order.shipping_cost
      # p expected_total
      # p order.total
    end
    
    { discrepancy: expected_total - order.total }
  end
end

Memory debugging identifies memory leaks and excessive object creation through debugging session inspection. The debugger can examine object references, garbage collection states, and memory allocation patterns during execution.

Integration with logging systems enables debugging based on log patterns and error frequencies. The debugger can trigger based on specific log events or error threshold conditions, providing targeted debugging for production issues.

Production Patterns

Production debugging with byebug requires careful consideration of performance impacts and security implications. Remote debugging provides the safest approach for production environments, avoiding direct code modifications in production deployments.

Remote debugging servers enable debugging production applications without embedding breakpoints in production code. The debugging server runs as a separate process and connects to the application through inter-process communication:

# In production application initialization
if ENV['DEBUG_MODE'] == 'enabled'
  require 'byebug/core'
  Byebug.start_server(ENV['DEBUG_HOST'] || 'localhost', 
                     ENV['DEBUG_PORT']&.to_i || 8989)
end

class ProductionService
  def critical_operation(data)
    # No embedded byebug calls in production
    result = perform_complex_calculation(data)
    validate_production_result(result)
    result
  end
  
  def debug_hook
    # Optional debug entry point for remote debugging
    byebug if ENV['DEBUG_MODE'] == 'enabled'
  end
end

Conditional debugging based on environment variables and feature flags enables debugging capabilities without permanent code changes. This approach maintains clean production code while providing debugging access when needed:

class FeatureDebugger
  DEBUGGING_ENABLED = ENV['FEATURE_DEBUG'] == 'true'
  DEBUG_FEATURES = (ENV['DEBUG_FEATURES'] || '').split(',')
  
  def self.debug_if_enabled(feature_name)
    return unless DEBUGGING_ENABLED
    return unless DEBUG_FEATURES.include?(feature_name)
    
    byebug
  end
end

class PaymentGateway
  def process_transaction(transaction)
    FeatureDebugger.debug_if_enabled('payments')
    
    gateway_response = send_to_gateway(transaction)
    process_gateway_response(gateway_response)
  end
end

Rails integration enables debugging within request-response cycles while maintaining application performance. The debugger integrates with Rails middleware stack and provides debugging capabilities for controller actions, model operations, and view rendering:

class ApplicationController < ActionController::Base
  before_action :debug_hook, if: -> { debug_mode_enabled? }
  
  private
  
  def debug_hook
    # Debug specific controllers or actions
    if params[:controller] == 'orders' && params[:action] == 'create'
      byebug
    end
  end
  
  def debug_mode_enabled?
    Rails.env.development? && 
    session[:debug_enabled] && 
    current_user&.admin?
  end
end

class OrdersController < ApplicationController
  def create
    @order = Order.new(order_params)
    
    # Conditional debugging for order creation issues
    if @order.total > 10000 && Rails.env.production?
      byebug  # Debug high-value orders in production
    end
    
    if @order.save
      redirect_to order_path(@order)
    else
      render :new
    end
  end
end

Background job debugging requires integration with job processing frameworks and queue systems. The debugger can pause job execution for inspection of job parameters, processing contexts, and error states:

class DebuggableJob
  include Sidekiq::Worker
  
  def perform(job_data)
    if debugging_enabled_for_job?(job_data)
      byebug
    end
    
    process_job_data(job_data)
  rescue => error
    if critical_error?(error)
      byebug  # Debug critical job failures
    end
    
    raise error
  end
  
  private
  
  def debugging_enabled_for_job?(data)
    data['debug'] == true || 
    data['user_id'].in?(debug_user_ids)
  end
end

Common Pitfalls

Byebug sessions can inadvertently modify application state through expression evaluation and variable assignments during debugging. The debugger executes Ruby code in the current context, making permanent changes to objects and variables if expressions contain side effects.

class StateModificationExample
  def initialize
    @counter = 0
    @items = []
  end
  
  def process_items(new_items)
    byebug
    # Dangerous debug commands that modify state:
    # @counter += 1           # Permanently increases counter
    # @items << 'debug_item'  # Adds debug item to real data
    # new_items.clear         # Destroys input data
    
    # Safe alternatives:
    # @counter                # Inspect without modifying
    # @items.dup << 'debug'   # Test changes on copy
    # new_items.size          # Check data without modifying
    
    @items.concat(new_items)
    @counter += new_items.size
  end
end

Thread-related debugging issues occur when breakpoints pause one thread while other threads continue execution. This situation can lead to race conditions, deadlocks, and inconsistent debugging results in concurrent applications:

class ConcurrentDebuggingProblem
  def initialize
    @shared_resource = 0
    @mutex = Mutex.new
  end
  
  def worker_method(thread_id)
    @mutex.synchronize do
      byebug  # Pausing here blocks other threads
      # Other threads wait for mutex while debugging
      @shared_resource += 1
      puts "Thread #{thread_id}: #{@shared_resource}"
    end
  end
  
  # Better approach: conditional debugging
  def safer_worker_method(thread_id)
    @mutex.synchronize do
      # Only debug specific threads or conditions
      byebug if thread_id == 'debug_thread' && debugging_enabled?
      
      @shared_resource += 1
      puts "Thread #{thread_id}: #{@shared_resource}"
    end
  end
end

Production debugging pitfalls include leaving debugging statements in deployed code, which can halt production processes when breakpoints trigger. Remote users or automated processes cannot interact with debugging prompts, causing application hang-ups:

class ProductionPitfall
  def user_registration(params)
    user = User.new(params)
    
    # DANGEROUS: This will hang production if triggered
    byebug if user.email.blank?
    
    # SAFER: Conditional debugging with safeguards
    if user.email.blank? && debug_mode_safe?
      byebug
    end
    
    user.save!
  end
  
  private
  
  def debug_mode_safe?
    Rails.env.development? || 
    (ENV['PRODUCTION_DEBUG'] == 'true' && interactive_session?)
  end
  
  def interactive_session?
    # Check if running in interactive environment
    $stdin.tty? && $stdout.tty?
  end
end

Performance implications include significant execution slowdowns when breakpoints trigger frequently within loops or high-traffic code paths. Each debugging session adds substantial overhead compared to normal execution:

class PerformancePitfall
  def process_large_dataset(records)
    records.each do |record|
      # PROBLEMATIC: Creates thousands of debug sessions
      byebug if record[:flag]
      
      transform_record(record)
    end
  end
  
  # Better approach: Sample debugging
  def optimized_processing(records)
    debug_sample_rate = 0.01  # Debug 1% of records
    
    records.each_with_index do |record, index|
      should_debug = record[:flag] && 
                     (rand < debug_sample_rate || index < 10)
      
      byebug if should_debug
      
      transform_record(record)
    end
  end
end

Variable scope confusion occurs when debugging in complex method contexts with multiple levels of local variables, block parameters, and closure variables. The debugger reflects the exact scope at the breakpoint location, which may not include all expected variables:

class ScopeConfusion
  def complex_method(input)
    outer_var = "outer"
    
    result = input.map do |item|
      inner_var = "inner"
      
      item.transform do |element|
        byebug  # Scope includes: element, inner_var, outer_var, input
        # But not variables from other closures or methods
        
        element.process
      end
    end
    
    final_var = "final"
    byebug  # Scope includes: final_var, result, outer_var, input
    # But not inner_var or element
    
    result
  end
end

Reference

Core Methods

Method Parameters Returns Description
byebug None nil Creates breakpoint at current line
debugger None nil Alias for byebug method
remote_byebug(host, port) host (String), port (Integer) nil Connects to remote debugging server

Interactive Commands

Command Syntax Description
next n [lines] Execute next line(s) without entering methods
step s [lines] Step into method calls
continue c Resume execution until next breakpoint
finish f Execute until current method returns
up up [frames] Move up call stack frames
down down [frames] Move down call stack frames
list l [start,end] Show source code around current line
print p <expression> Evaluate and display expression
pp pp <expression> Pretty print expression
backtrace bt Show call stack
break b <file:line> Set breakpoint at specified location
delete del <number> Remove breakpoint by number
quit q Exit debugger and terminate program

Breakpoint Management

Command Syntax Description
break break <file>:<line> Set breakpoint at file and line
break break <method> Set breakpoint at method definition
condition condition <n> <expr> Add condition to breakpoint n
enable enable <numbers> Enable specified breakpoints
disable disable <numbers> Disable specified breakpoints
info breakpoints info breakpoints List all breakpoints

Variable Inspection

Command Syntax Description
var local var local Show local variables
var instance var instance Show instance variables
var global var global Show global variables
var class var class Show class variables
var const var const Show constants
display display <expr> Auto-display expression at each stop
undisplay undisplay <number> Remove auto-display

Thread Commands

Command Syntax Description
thread list thread list Show all threads
thread switch thread switch <n> Switch to thread n
thread stop thread stop <n> Stop thread n
thread resume thread resume <n> Resume thread n

Configuration Options

Setting Values Default Description
autolist true, false true Auto-display code listings
autoirb true, false false Auto-start IRB at breakpoints
autopry true, false false Auto-start Pry at breakpoints
callstyle short, long short Call stack display format
histfile String ~/.byebug_history Command history file location
histsize Integer 256 Maximum history entries
linetrace true, false false Trace line execution
savefile String ~/.byebug_save Saved commands file
width Integer 80 Display width for listings

Remote Debugging

Method Parameters Description
Byebug.start_server host, port, **opts Start remote debugging server
Byebug.client host, port Connect to remote debugging server
Byebug.stop_server None Stop remote debugging server

Environment Variables

Variable Values Description
BYEBUG_RC File path Custom configuration file location
BYEBUG_HISTORY File path Command history file location
BYEBUG_OPTS Options string Default command-line options

Common Patterns

Conditional breakpoints:

byebug if condition_met?
break method_name if variable > threshold

Exception debugging:

rescue => e
  byebug
  raise e
end

Remote debugging setup:

Byebug.start_server('0.0.0.0', 8989)
# Connect with: byebug -R host:port