Overview
Ruby's Debugger API exposes the internal debugging infrastructure through several key classes and modules. The primary interface centers around the Debug
module, which provides methods for setting breakpoints, stepping through code execution, and inspecting runtime state.
The API operates through a client-server architecture where the debugger server runs within the Ruby process and accepts commands from debugging clients. The Debug::Session
class manages individual debugging sessions, while Debug::ThreadClient
handles thread-specific debugging operations.
require 'debug'
# Start debugging session
Debug.start
# Set a breakpoint programmatically
Debug.bp(file: "app.rb", line: 25)
# Access current session
session = Debug.session
puts session.inspect
The debugger maintains an internal state machine that tracks execution flow, breakpoint status, and variable scope. When execution hits a breakpoint, control transfers to the debugger which can examine local variables, call stack, and object state.
# Enable debugging for specific methods
class Calculator
def add(a, b)
Debug.bp # Set breakpoint at this line
result = a + b
result
end
end
calc = Calculator.new
calc.add(5, 3) # Execution pauses here
The API supports both synchronous and asynchronous debugging modes. In synchronous mode, the debugger blocks program execution until commands complete. Asynchronous mode allows debugging without stopping the main program flow, which proves essential for debugging concurrent applications.
# Configure debugging session
Debug.start do |session|
session.add_breakpoint(file: "model.rb", line: 45, condition: "user.nil?")
session.set_exception_breakpoint(exception_class: ArgumentError)
end
Basic Usage
The Debugger API activates through the Debug.start
method, which initializes the debugging environment and establishes communication channels. This method accepts a block that receives the current debugging session as a parameter.
require 'debug'
Debug.start do |session|
# Debugging session is active within this block
puts "Debug session ID: #{session.id}"
puts "Available commands: #{session.commands.keys}"
end
Setting breakpoints programmatically requires specifying either a file and line number combination or a method reference. The API validates breakpoint locations and raises Debug::Error
for invalid positions.
# File-based breakpoint
Debug.bp(file: "lib/user.rb", line: 42)
# Method-based breakpoint
Debug.bp(method: User.instance_method(:authenticate))
# Conditional breakpoint
Debug.bp(file: "service.rb", line: 18, condition: "params[:debug] == true")
The debugging session provides methods for controlling execution flow. The step_over
, step_into
, and step_out
methods correspond to common debugging operations.
Debug.start do |session|
session.step_over # Execute next line without entering method calls
session.step_into # Step into method calls
session.step_out # Continue until current method returns
# Continue execution until next breakpoint
session.continue
end
Variable inspection occurs through the session's evaluation capabilities. The eval
method executes Ruby code within the current debugging context, providing access to local variables and object state.
Debug.start do |session|
# Evaluate expressions in current context
local_vars = session.eval("local_variables")
user_name = session.eval("@user.name")
# Execute complex expressions
result = session.eval("defined?(Rails) && Rails.env.development?")
puts "Development mode: #{result}"
end
Exception handling during debugging requires careful consideration. The debugger intercepts exceptions before they propagate, allowing inspection of error conditions and program state at the point of failure.
# Set exception breakpoints
Debug.start do |session|
session.add_exception_breakpoint(StandardError)
session.add_exception_breakpoint(NoMethodError, enabled: false)
end
def risky_operation
raise StandardError, "Something went wrong"
end
risky_operation # Debugger pauses when exception occurs
Advanced Usage
Complex debugging scenarios require advanced breakpoint management and conditional logic. The API supports breakpoint groups, enabling and disabling multiple breakpoints simultaneously.
Debug.start do |session|
# Create breakpoint group
auth_group = session.create_breakpoint_group("authentication")
auth_group.add_breakpoint(file: "auth/login.rb", line: 15)
auth_group.add_breakpoint(file: "auth/session.rb", line: 30)
auth_group.add_breakpoint(method: User.instance_method(:verify_password))
# Enable/disable entire group
auth_group.disable
auth_group.enable
end
Custom debugging commands extend the debugger's functionality through the command registration system. Commands receive the current debugging context and can perform arbitrary operations.
Debug.start do |session|
# Register custom command
session.register_command("inspect_user") do |context, args|
user = context.eval("@user")
if user
puts "User ID: #{user.id}"
puts "User roles: #{user.roles.map(&:name)}"
puts "Last login: #{user.last_login_at}"
else
puts "No user in current context"
end
end
# Command becomes available in debugging session
session.execute_command("inspect_user")
end
The debugger supports watch expressions that monitor variable changes across execution. Watch expressions evaluate automatically when execution pauses, providing continuous monitoring of critical values.
Debug.start do |session|
# Add watch expressions
session.add_watch("@total_price", notify_on_change: true)
session.add_watch("items.length")
session.add_watch("Time.current", format: :iso8601)
# Watch expressions evaluate at each pause
def calculate_order
@total_price = 0
items.each do |item|
@total_price += item.price # Watch expression updates here
end
end
end
Remote debugging capabilities allow connecting to Ruby processes running on different machines or in containerized environments. The debugger server listens on configurable ports and accepts connections from remote clients.
# Start remote debugging server
Debug.start(host: "0.0.0.0", port: 8989, wait_for_attach: true) do |session|
puts "Debugger server listening on port 8989"
session.wait_for_client_connection
# Program execution pauses until client connects
puts "Client connected, continuing execution"
end
Integration with development tools requires careful session management and state synchronization. The API provides hooks for editor integration and external tool communication.
class DebuggerIntegration
def initialize
@sessions = {}
@breakpoint_sync = Queue.new
end
def start_session(file_path)
Debug.start do |session|
@sessions[file_path] = session
# Synchronize breakpoints with external tools
session.on_breakpoint_hit do |bp_info|
@breakpoint_sync << {
type: :hit,
file: bp_info[:file],
line: bp_info[:line],
variables: session.eval("local_variables")
}
end
yield session if block_given?
end
end
def sync_breakpoints
while info = @breakpoint_sync.pop(non_block: true)
notify_external_tool(info)
end
rescue ThreadError
# Queue empty
end
end
Production Patterns
Production debugging requires careful consideration of performance impact and security implications. The debugger should remain disabled in production environments unless explicitly activated for troubleshooting specific issues.
class Application
def self.configure_debugging
return unless debugging_enabled?
Debug.configure do |config|
config.server_host = ENV.fetch("DEBUG_HOST", "127.0.0.1")
config.server_port = ENV.fetch("DEBUG_PORT", "8989").to_i
config.auth_token = ENV["DEBUG_AUTH_TOKEN"]
config.log_level = :error # Minimize debug output
end
end
private
def self.debugging_enabled?
ENV["RAILS_ENV"] != "production" || ENV["ENABLE_PRODUCTION_DEBUG"] == "true"
end
end
Conditional debugging activation allows enabling debugging features only when specific conditions occur. This approach minimizes performance overhead while maintaining debugging capabilities for error conditions.
module ConditionalDebugging
def self.debug_on_error(&block)
block.call
rescue => exception
if should_debug_exception?(exception)
Debug.start do |session|
puts "Exception occurred: #{exception.message}"
puts "Backtrace: #{exception.backtrace.first(5)}"
# Allow interactive debugging
session.interactive_mode
end
end
raise exception
end
def self.should_debug_exception?(exception)
critical_exceptions = [NoMethodError, TypeError, ArgumentError]
critical_exceptions.include?(exception.class) && Rails.env.development?
end
end
# Usage in application code
ConditionalDebugging.debug_on_error do
perform_complex_operation
end
Logging integration ensures debugging activities appear in application logs without interfering with normal log processing. The debugger supports custom log formatters and filtering.
class DebuggerLogger
def initialize(base_logger)
@base_logger = base_logger
@debug_events = []
end
def log_debug_event(event_type, details)
timestamp = Time.current
event = {
timestamp: timestamp,
type: event_type,
details: details,
thread_id: Thread.current.object_id
}
@debug_events << event
@base_logger.debug("[DEBUGGER] #{event_type}: #{details}")
# Limit event history
@debug_events.shift if @debug_events.length > 1000
end
def debug_session_summary
return "No debug events recorded" if @debug_events.empty?
summary = {
total_events: @debug_events.length,
event_types: @debug_events.group_by { |e| e[:type] }.transform_values(&:count),
first_event: @debug_events.first[:timestamp],
last_event: @debug_events.last[:timestamp]
}
@base_logger.info("[DEBUGGER] Session summary: #{summary}")
end
end
Container debugging requires specific configuration for Docker and Kubernetes environments. The debugger server must bind to appropriate interfaces and handle container networking constraints.
# docker-compose.yml equivalent configuration
class ContainerDebugConfig
def self.configure_for_container
Debug.configure do |config|
# Bind to all interfaces in container
config.server_host = "0.0.0.0"
config.server_port = ENV.fetch("DEBUG_PORT", "8989").to_i
# Handle container shutdown gracefully
config.on_shutdown do
puts "Debugger shutting down gracefully"
save_debug_session_state
end
# Container-specific logging
config.logger = ContainerLogger.new
end
end
def self.save_debug_session_state
state = {
breakpoints: Debug.session&.breakpoints,
watch_expressions: Debug.session&.watch_expressions,
timestamp: Time.current
}
File.write("/tmp/debug_state.json", JSON.pretty_generate(state))
end
end
Common Pitfalls
Performance degradation represents the most significant risk when using the Debugger API inappropriately. Leaving breakpoints active in production code or enabling verbose logging can severely impact application performance.
# BAD: Breakpoint left active in production
class OrderProcessor
def process_order(order)
Debug.bp # This will pause execution in production!
order.items.each { |item| calculate_tax(item) }
end
end
# GOOD: Conditional debugging
class OrderProcessor
def process_order(order)
Debug.bp if Rails.env.development? && ENV["DEBUG_ORDERS"]
order.items.each { |item| calculate_tax(item) }
end
end
Memory leaks occur when debugging sessions accumulate without proper cleanup. Long-running applications must manage session lifecycle carefully to prevent memory exhaustion.
# BAD: Sessions never cleaned up
class LeakyDebugger
def initialize
@sessions = []
end
def debug_method(method_name)
@sessions << Debug.start # Memory leak!
# Session objects accumulate indefinitely
end
end
# GOOD: Proper session management
class ManagedDebugger
def initialize
@current_session = nil
end
def debug_method(method_name)
cleanup_session if @current_session
@current_session = Debug.start do |session|
yield session if block_given?
end
ensure
cleanup_session
end
private
def cleanup_session
@current_session&.terminate
@current_session = nil
end
end
Thread safety issues arise when multiple threads access the same debugging session simultaneously. The debugger API requires synchronization for concurrent access patterns.
# BAD: Race condition on session access
class UnsafeDebugger
def initialize
@session = Debug.start
end
def debug_in_thread
Thread.new do
@session.eval("some_expression") # Race condition!
end
end
end
# GOOD: Thread-safe debugging
class ThreadSafeDebugger
def initialize
@session = Debug.start
@session_mutex = Mutex.new
end
def debug_in_thread
Thread.new do
@session_mutex.synchronize do
@session.eval("some_expression")
end
end
end
end
Security vulnerabilities emerge when debugging interfaces expose sensitive information or accept unvalidated input. Remote debugging servers require authentication and input validation.
# BAD: Unrestricted remote debugging
Debug.start(host: "0.0.0.0", port: 8989) do |session|
# Anyone can connect and execute arbitrary code!
session.interactive_mode
end
# GOOD: Secured remote debugging
class SecureRemoteDebugger
def self.start_secure_session
return unless authorized_environment?
Debug.start(
host: "127.0.0.1", # Localhost only
port: secure_port,
auth_token: generate_session_token
) do |session|
session.set_eval_filter(method(:safe_eval_filter))
yield session if block_given?
end
end
private
def self.safe_eval_filter(code)
dangerous_methods = %w[system exec ` eval instance_eval class_eval]
dangerous_methods.any? { |method| code.include?(method) } ? nil : code
end
def self.generate_session_token
SecureRandom.hex(32)
end
end
Exception handling complexity increases when debugging code itself raises exceptions. Proper error handling prevents debugging operations from masking original application errors.
# Handle debugging errors gracefully
module SafeDebugging
def self.safe_breakpoint(location_info = {})
Debug.bp(**location_info)
rescue Debug::InvalidLocationError => e
Rails.logger.warn("Invalid breakpoint location: #{e.message}")
rescue Debug::SessionError => e
Rails.logger.error("Debugging session error: #{e.message}")
end
def self.safe_eval(expression, default_value = nil)
Debug.session&.eval(expression)
rescue NameError, SyntaxError => e
Rails.logger.debug("Evaluation failed: #{expression} - #{e.message}")
default_value
rescue Debug::EvaluationError => e
Rails.logger.warn("Debug evaluation error: #{e.message}")
default_value
end
end
Reference
Core Classes and Modules
Class/Module | Purpose | Key Methods |
---|---|---|
Debug |
Main debugger interface | start , bp , session , configure |
Debug::Session |
Individual debugging session | eval , step_over , continue , add_breakpoint |
Debug::ThreadClient |
Thread-specific debugging | pause , resume , backtrace |
Debug::Breakpoint |
Breakpoint representation | enable , disable , condition= |
Primary Methods
Method | Parameters | Returns | Description |
---|---|---|---|
Debug.start(**opts, &block) |
host (String), port (Integer), block |
Debug::Session |
Starts debugging session |
Debug.bp(**location) |
file (String), line (Integer), condition (String) |
Debug::Breakpoint |
Sets breakpoint |
session.eval(code) |
code (String) |
Object |
Evaluates code in current context |
session.step_over |
None | nil |
Steps over next line |
session.step_into |
None | nil |
Steps into method calls |
session.continue |
None | nil |
Continues execution |
Configuration Options
Option | Type | Default | Description |
---|---|---|---|
server_host |
String | "127.0.0.1" |
Debugging server bind address |
server_port |
Integer | 8989 |
Debugging server port |
auth_token |
String | nil |
Authentication token for remote access |
log_level |
Symbol | :info |
Logging verbosity level |
wait_for_attach |
Boolean | false |
Wait for client connection |
Breakpoint Types
Type | Location Syntax | Example |
---|---|---|
Line | file: , line: |
file: "app.rb", line: 42 |
Method | method: |
method: User.instance_method(:save) |
Exception | exception_class: |
exception_class: ArgumentError |
Conditional | condition: |
condition: "user.admin?" |
Session States
State | Description | Available Operations |
---|---|---|
inactive |
No debugging session | Start session only |
paused |
Execution paused at breakpoint | Step, continue, evaluate |
running |
Normal execution with breakpoints active | Pause, add/remove breakpoints |
terminated |
Session ended | None |
Exception Classes
Exception | Inheritance | Raised When |
---|---|---|
Debug::Error |
StandardError |
General debugging errors |
Debug::SessionError |
Debug::Error |
Session management failures |
Debug::InvalidLocationError |
Debug::Error |
Invalid breakpoint location |
Debug::EvaluationError |
Debug::Error |
Code evaluation failures |
Debug::ConnectionError |
Debug::Error |
Remote debugging connection issues |
Command Categories
Category | Commands | Description |
---|---|---|
Execution Control | continue , step , next , finish |
Control program flow |
Breakpoints | break , clear , info breakpoints |
Manage breakpoints |
Inspection | print , pp , info locals |
Examine program state |
Navigation | up , down , backtrace |
Navigate call stack |
Session | quit , restart , help |
Manage debugging session |
Integration Patterns
Pattern | Use Case | Implementation |
---|---|---|
Conditional Activation | Development-only debugging | Environment variable checks |
Exception Debugging | Error investigation | Exception-triggered breakpoints |
Remote Debugging | Container/production debugging | Network-accessible sessions |
Test Integration | Debugging test failures | Test-specific breakpoint placement |