CrackedRuby logo

CrackedRuby

Remote Debugging with Debug Gem

Overview

The debug gem provides remote debugging capabilities that establish network connections between debugger clients and target Ruby processes. The gem creates a debug server within the target application, accepting connections from external debugger clients over TCP sockets. This architecture separates the debugging interface from the running process, enabling debugging across network boundaries.

Remote debugging operates through a client-server model where the target Ruby process acts as the debug server. The debug/open require statement initializes the debug server, binding to a specified host and port. External clients connect using the rdbg command-line tool or compatible debugger interfaces.

# Start remote debug server in target application
require 'debug/open'

class CalculationService
  def process_data(numbers)
    binding.b  # Breakpoint triggers remote debugging session
    numbers.sum / numbers.length
  end
end

service = CalculationService.new
result = service.process_data([10, 20, 30])

The debug gem supports multiple transport protocols including TCP sockets and Unix domain sockets. TCP connections enable debugging across network boundaries, while Unix sockets provide local inter-process debugging with reduced network overhead.

# Configure remote debugging with specific host and port
require 'debug/open_nonstop'

ENV['RUBY_DEBUG_HOST'] = '192.168.1.100'
ENV['RUBY_DEBUG_PORT'] = '12345'

# Application continues execution while debug server runs
puts "Debug server available at #{ENV['RUBY_DEBUG_HOST']}:#{ENV['RUBY_DEBUG_PORT']}"

The remote debugging session maintains full debugging capabilities including breakpoints, step execution, variable inspection, and expression evaluation. The debug server serializes debugging state and commands between client and target process, maintaining debugging context across the network connection.

Basic Usage

Remote debugging requires configuring the target application as a debug server and connecting external clients to the debugging session. The debug/open module initializes the server automatically when required, accepting default configuration or environment-based customization.

Starting a basic remote debug server requires adding the debug gem to the application and requiring the appropriate module:

# Basic remote debugging setup
require 'debug/open'

def fibonacci(n)
  return n if n <= 1
  binding.b  # Remote debugger pauses here
  fibonacci(n - 1) + fibonacci(n - 2)
end

puts fibonacci(8)

The debug server binds to localhost:12345 by default. External clients connect using the rdbg command with the --attach option:

# Connect to remote debug session
rdbg --attach 12345

Environment variables control server configuration without modifying application code. The debug gem reads these variables during initialization:

# Configure through environment variables
ENV['RUBY_DEBUG_HOST'] = '0.0.0.0'    # Accept connections from any host
ENV['RUBY_DEBUG_PORT'] = '9001'       # Use custom port
ENV['RUBY_DEBUG_SOCK_PATH'] = '/tmp/debug.sock'  # Unix socket path

require 'debug/open'

class DataProcessor
  def initialize(config)
    @config = config
    binding.b if config[:debug]  # Conditional breakpoint
  end

  def transform(data)
    data.map { |item| process_item(item) }
  end

  private

  def process_item(item)
    # Processing logic here
    item.upcase
  end
end

The debug/open_nonstop variant starts the debug server without immediately pausing execution. This approach enables attaching debuggers to running applications without interrupting normal operation:

require 'debug/open_nonstop'

# Server starts in background, application continues
loop do
  puts "Application running... #{Time.now}"
  sleep 1
end

Remote debugging sessions support multiple concurrent client connections. Each client receives independent debugging views while sharing access to the same target process state. The debug server handles command serialization and state synchronization across connected clients.

Client connections persist across breakpoint encounters and continue operations. When execution reaches a breakpoint, all connected clients receive notification and can inspect the debugging context simultaneously.

Error Handling & Debugging

Remote debugging introduces network-related failure modes beyond standard debugging concerns. Connection failures, network interruptions, and port conflicts require specific error handling approaches to maintain debugging reliability.

Network connectivity issues manifest as connection timeouts or refused connections when clients attempt to attach to debug servers. The debug gem provides connection status feedback through error messages and exit codes:

# Robust debug server with error handling
begin
  require 'debug/open'
rescue StandardError => e
  puts "Debug server failed to start: #{e.message}"
  puts "Continuing without debugging..."
end

class RobustService
  def critical_method(data)
    validate_data(data)
    binding.b  # Only activates if debug server started successfully
    process_validated_data(data)
  rescue ValidationError => e
    # Handle validation errors appropriately
    log_error(e)
    raise
  end

  private

  def validate_data(data)
    raise ValidationError, "Invalid data format" unless data.is_a?(Hash)
  end

  def process_validated_data(data)
    data.transform_values(&:to_s)
  end

  def log_error(error)
    puts "[ERROR] #{error.class}: #{error.message}"
  end
end

Port binding conflicts occur when multiple applications attempt to use the same debug port or when previous debug sessions leave sockets in use. The debug gem exits with error messages indicating port availability issues:

# Handle port conflicts with fallback configuration
debug_port = ENV.fetch('RUBY_DEBUG_PORT', '12345').to_i

begin
  ENV['RUBY_DEBUG_PORT'] = debug_port.to_s
  require 'debug/open_nonstop'
  puts "Debug server started on port #{debug_port}"
rescue Errno::EADDRINUSE
  debug_port += 1
  retry if debug_port < 13000  # Try up to 500 ports
  puts "No available debug ports found"
end

Connection drops during active debugging sessions cause client disconnection without affecting the target process. The debug server continues running and accepts new client connections. Lost debugging state requires re-establishing breakpoints and navigation context:

# Application with debugging hooks for connection monitoring
require 'debug/open_nonstop'

class MonitoredApplication
  def initialize
    @debug_active = debug_server_running?
    setup_debug_hooks if @debug_active
  end

  def main_loop
    iteration = 0
    loop do
      iteration += 1
      binding.b if iteration % 100 == 0  # Periodic debug checkpoint
      process_iteration(iteration)
    end
  end

  private

  def debug_server_running?
    # Check if debug server is accessible
    ENV['RUBY_DEBUG_PORT'] && !ENV['RUBY_DEBUG_PORT'].empty?
  end

  def setup_debug_hooks
    puts "Debug server available for attachment"
    puts "Connect with: rdbg --attach #{ENV['RUBY_DEBUG_PORT']}"
  end

  def process_iteration(num)
    # Main application logic
    sleep 0.1
  end
end

Debugging remote applications with restricted network access requires careful firewall and security configuration. The debug server accepts connections from configured network interfaces, potentially exposing debugging capabilities to unauthorized clients. Production environments should restrict debug server access through network segmentation or authentication mechanisms.

Advanced Usage

Advanced remote debugging scenarios involve complex network topologies, custom transport configurations, and integration with deployment orchestration systems. The debug gem supports sophisticated debugging workflows through extensive configuration options and programmatic interfaces.

Multi-hop debugging enables debugging applications running in containerized environments or behind network proxies. SSH tunneling and port forwarding establish secure debugging channels across network boundaries:

# SSH tunnel for secure remote debugging
ssh -L 9001:localhost:12345 user@remote-server

# Application on remote server
RUBY_DEBUG_PORT=12345 ruby application.rb

# Local debugger connects through tunnel
rdbg --attach 9001

Container-based debugging requires exposing debug ports and configuring network access. Docker containers must publish debug ports and configure appropriate networking modes:

# Container-optimized debug configuration
ENV['RUBY_DEBUG_HOST'] = '0.0.0.0'  # Accept external connections
ENV['RUBY_DEBUG_PORT'] = ENV['DEBUG_PORT'] || '12345'

require 'debug/open_nonstop'

class ContainerizedService
  def self.start
    puts "Debug server: #{ENV['RUBY_DEBUG_HOST']}:#{ENV['RUBY_DEBUG_PORT']}"
    new.run
  end

  def run
    setup_signal_handlers
    main_service_loop
  end

  private

  def setup_signal_handlers
    trap('TERM') { shutdown_gracefully }
    trap('INT') { shutdown_gracefully }
  end

  def main_service_loop
    loop do
      binding.b if should_debug?
      process_request
    end
  end

  def should_debug?
    ENV['ENABLE_DEBUGGING'] == 'true'
  end

  def process_request
    # Service logic here
    sleep 1
  end

  def shutdown_gracefully
    puts "Shutting down service..."
    exit 0
  end
end

Custom debug server configuration enables specialized debugging workflows. The debug gem accepts configuration through Ruby code in addition to environment variables:

require 'debug'

# Programmatic debug server configuration
DEBUG.configure do |config|
  config.host = '192.168.1.100'
  config.port = 9001
  config.sock_path = nil  # Disable Unix socket
  config.sock_mode = nil
  config.log_level = 'INFO'
end

require 'debug/open_nonstop'

class AdvancedDebuggingExample
  def initialize(options = {})
    @options = options
    @debug_enabled = options[:debug] || ENV['ENABLE_DEBUG'] == '1'
  end

  def process_with_conditional_debugging(data_set)
    data_set.each_with_index do |item, index|
      # Conditional breakpoint based on data characteristics
      binding.b if @debug_enabled && complex_condition?(item, index)
      result = transform_data(item)
      yield result if block_given?
    end
  end

  private

  def complex_condition?(item, index)
    # Complex debugging condition logic
    item.is_a?(Hash) && item.key?(:error) && index > 10
  end

  def transform_data(item)
    case item
    when Hash
      item.transform_keys(&:to_sym)
    when Array
      item.map(&:to_s)
    else
      item.to_s
    end
  end
end

Debugging distributed applications requires coordinating debug sessions across multiple service instances. Custom debugging hooks enable synchronized debugging workflows:

require 'debug/open_nonstop'

class DistributedServiceDebugger
  def self.setup_coordinated_debugging(service_id, coordinator_url)
    new(service_id, coordinator_url).setup
  end

  def initialize(service_id, coordinator_url)
    @service_id = service_id
    @coordinator_url = coordinator_url
    @debug_port = calculate_debug_port(service_id)
  end

  def setup
    ENV['RUBY_DEBUG_PORT'] = @debug_port.to_s
    register_with_coordinator
    setup_cleanup_hooks
  end

  private

  def calculate_debug_port(service_id)
    # Generate unique port based on service ID
    12000 + (service_id.hash.abs % 1000)
  end

  def register_with_coordinator
    puts "Service #{@service_id} debug server: port #{@debug_port}"
    # Registration logic would connect to coordinator service
  end

  def setup_cleanup_hooks
    at_exit { cleanup_debug_registration }
  end

  def cleanup_debug_registration
    puts "Cleaning up debug registration for service #{@service_id}"
  end
end

Production Patterns

Remote debugging in production environments requires careful security considerations, performance impact assessment, and operational monitoring. Production debugging workflows balance debugging accessibility with system stability and security requirements.

Security-hardened remote debugging restricts network access and implements authentication mechanisms. Production debug servers should bind to internal network interfaces and require authenticated connections:

# Production-safe debug configuration
if ENV['RAILS_ENV'] == 'production' && ENV['ENABLE_PRODUCTION_DEBUG'] == '1'
  # Restrict to internal network only
  ENV['RUBY_DEBUG_HOST'] = '10.0.0.0'  # Internal network interface
  ENV['RUBY_DEBUG_PORT'] = ENV['DEBUG_PORT'] || '12345'
  
  # Authentication would be handled by network security
  require 'debug/open_nonstop'
  
  Rails.logger.info "Production debug server enabled on #{ENV['RUBY_DEBUG_HOST']}:#{ENV['RUBY_DEBUG_PORT']}"
end

class ProductionController < ApplicationController
  def critical_operation
    if Rails.env.production? && debug_conditions_met?
      binding.b  # Conditional production debugging
    end
    
    perform_critical_logic
  rescue StandardError => e
    Rails.logger.error "Critical operation failed: #{e.message}"
    raise
  end

  private

  def debug_conditions_met?
    # Strict conditions for production debugging
    current_user&.admin? && 
    ENV['ENABLE_PRODUCTION_DEBUG'] == '1' &&
    Time.current.hour.in?(9..17)  # Business hours only
  end

  def perform_critical_logic
    # Business logic implementation
  end
end

Load balancer and reverse proxy configurations must account for debug server ports. Infrastructure components require configuration updates to route debug traffic appropriately:

# Kubernetes/Docker production setup
class KubernetesDebugSetup
  def self.configure_pod_debugging
    return unless debugging_enabled?
    
    debug_port = ENV['DEBUG_PORT'] || '12345'
    pod_ip = ENV['POD_IP'] || '0.0.0.0'
    
    ENV['RUBY_DEBUG_HOST'] = pod_ip
    ENV['RUBY_DEBUG_PORT'] = debug_port
    
    require 'debug/open_nonstop'
    
    puts "Pod debugging available at #{pod_ip}:#{debug_port}"
    puts "Use kubectl port-forward for external access"
  end

  def self.debugging_enabled?
    ENV['ENABLE_DEBUG'] == '1' && 
    ENV['RAILS_ENV'] != 'production' || 
    ENV['PRODUCTION_DEBUG_OVERRIDE'] == '1'
  end
end

# Initialize in application startup
KubernetesDebugSetup.configure_pod_debugging if defined?(Rails)

Monitoring and logging debug server activity enables tracking debugging usage and detecting unauthorized access attempts. Production systems should log debug connections and session activity:

require 'debug/open_nonstop'

class MonitoredDebugServer
  def self.setup_monitoring
    return unless debug_server_active?
    
    monitor = new
    monitor.setup_connection_logging
    monitor.setup_metrics_collection
  end

  def setup_connection_logging
    # Debug server connection monitoring would be implemented here
    puts "Debug monitoring active - connections logged to #{log_file_path}"
  end

  def setup_metrics_collection
    Thread.new do
      loop do
        collect_debug_metrics
        sleep 60  # Collect metrics every minute
      end
    end
  end

  private

  def self.debug_server_active?
    ENV['RUBY_DEBUG_PORT'] && !ENV['RUBY_DEBUG_PORT'].empty?
  end

  def log_file_path
    Rails.root.join('log', 'debug_server.log') if defined?(Rails)
  end

  def collect_debug_metrics
    # Collect metrics about debug server usage
    debug_port = ENV['RUBY_DEBUG_PORT']
    puts "Debug server metrics: port=#{debug_port}, time=#{Time.current}"
  end
end

Performance impact monitoring ensures debug server overhead remains within acceptable bounds. Production debugging should include performance metrics and automatic disabling mechanisms for high-load conditions:

class PerformanceAwareDebugging
  PERFORMANCE_THRESHOLD = 100  # milliseconds
  
  def self.conditional_debug_point(context = nil)
    return unless debugging_enabled?
    return if system_under_load?
    
    start_time = Time.current
    binding.b
    
    # Monitor debugging session impact
    duration = (Time.current - start_time) * 1000
    log_debug_performance(duration, context) if duration > PERFORMANCE_THRESHOLD
  end

  def self.system_under_load?
    # Check system load metrics
    load_average = `uptime`.match(/load average: ([\d.]+)/)[1].to_f rescue 0
    load_average > 2.0  # Disable debugging under high load
  end

  def self.debugging_enabled?
    ENV['ENABLE_DEBUG'] == '1' && Time.current.hour.in?(1..5)  # Off-peak hours only
  end

  def self.log_debug_performance(duration, context)
    Rails.logger.warn "Debug session took #{duration}ms in context: #{context}" if defined?(Rails)
  end
end

Reference

Environment Variables

Variable Default Description
RUBY_DEBUG_HOST localhost Debug server bind address
RUBY_DEBUG_PORT 12345 Debug server port number
RUBY_DEBUG_SOCK_PATH /tmp/ruby-debug-sock-<uid> Unix domain socket path
RUBY_DEBUG_SOCK_MODE 0600 Unix socket file permissions
RUBY_DEBUG_LOG_LEVEL WARN Debug server log verbosity

Debug Gem Modules

Module Behavior Use Case
debug/open Starts server, pauses execution Interactive debugging from start
debug/open_nonstop Starts server, continues execution Attach to running processes
debug Local debugging only Standard debugging without remote access

Client Connection Commands

Command Description
rdbg --attach <port> Connect to TCP debug server
rdbg --attach <host>:<port> Connect to remote TCP debug server
rdbg --attach unix:<path> Connect via Unix domain socket

Debugging Session Commands

Command Description
c or continue Continue execution until next breakpoint
n or next Execute next line in current method
s or step Step into method calls
b <location> Set breakpoint at specified location
info Display debugging session information
p <expression> Evaluate and print expression
pp <expression> Pretty-print expression result
l or list Show source code around current location
bt or backtrace Display call stack
up Move up call stack
down Move down call stack
kill Terminate debugging session

Network Configuration Options

Configuration TCP Sockets Unix Sockets
Host binding IP address or hostname Not applicable
Port specification 1024-65535 Not applicable
File permissions Not applicable 0600-0777
Cross-machine access Yes No
Security Network-dependent Local file system
Performance Network overhead Lower latency

Error Codes and Diagnostics

Error Cause Resolution
Errno::EADDRINUSE Port already in use Choose different port or stop conflicting process
Errno::EACCES Permission denied Use port >1024 or run with appropriate permissions
Errno::ECONNREFUSED Cannot connect to debug server Verify server is running and port is correct
Errno::ENETUNREACH Network unreachable Check network connectivity and firewall settings

Breakpoint Location Formats

Format Example Description
Line number b 42 Set breakpoint at line 42 in current file
File and line b app.rb:42 Set breakpoint at line 42 in app.rb
Method name b Class#method Set breakpoint at method definition
Class method b Class.method Set breakpoint at class method
Conditional b 42 if condition Set conditional breakpoint

Security Considerations

Aspect Risk Level Mitigation
Network exposure High Bind to internal interfaces only
Authentication High Use network security controls
Code access High Restrict to authorized personnel
Performance impact Medium Monitor and limit debug sessions
Log exposure Medium Secure debug logs appropriately