Skip to main content

Debugging

This guide provides techniques for debugging Rust-based Ruby extensions, covering debuggers, memory analysis, and logging.

Compiling with Debug Symbols

To effectively debug your extension, compile it with debug symbols. Use the dev Cargo profile.

Set the profile with an environment variable:

RB_SYS_CARGO_PROFILE=dev bundle exec rake compile

Or create a Rake task:

# Rakefile
desc "Compile the extension with debug symbols"
task "compile:debug" do
ENV["RB_SYS_CARGO_PROFILE"] = "dev"
Rake::Task["compile"].invoke
end

Using a Debugger (GDB/LLDB)

Live Session

Run your Ruby process inside gdb or lldb to catch crashes and inspect state.

  1. Start the debugger:

    # LLDB on macOS
    lldb -- ruby -Ilib -e 'require "my_extension"; MyExtension.method_to_debug'

    # GDB on Linux
    gdb --args ruby -Ilib -e 'require "my_extension"; MyExtension.method_to_debug'
  2. Set a breakpoint and run: Set breakpoints on Rust functions (e.g., by file and line number).

    (lldb) breakpoint set --file src/lib.rs --line 42
    (lldb) run

Post-Mortem Debugging (Core Dumps)

Analyze core dumps for segmentation faults.

  1. Enable core dumps (Linux/macOS): ulimit -c unlimited

  2. Run the code that causes the crash to generate a core file.

  3. Load the core dump:

    gdb --core=core --quiet `which ruby`
  4. Get a backtrace: Use bt to show the stack trace.

VSCode Configuration

The vadimcn.vscode-lldb extension provides integrated debugging.

// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug Extension",
"preLaunchTask": "rake: compile:debug",
"program": "/path/to/your/ruby",
"args": ["-Ilib", "-r", "my_gem", "my_script.rb"],
"cwd": "${workspaceFolder}",
"sourceLanguages": ["rust"]
}
]
}

Logging

Structured logging is invaluable for development and production.

Setup

Use the log and env_logger crates.

# Cargo.toml
[dependencies]
log = "0.4"
env_logger = "0.10"

Initialize env_logger in your extension's Init function.

#[magnus::init]
fn init() {
env_logger::init();
// ...
}

Usage

Run your Ruby code with RUST_LOG set to the desired log level (error, warn, info, debug, trace).

RUST_LOG=debug ruby my_script.rb
// In your Rust code
use log::{debug, info};

fn my_function(some_data: &[i64]) {
debug!("Starting operation with data: {:?}", some_data);
// ...
info!("Operation completed successfully.");
}

fn another_function() {
my_function(&[1, 2, 3]);
}

Memory Leak Detection

Use ruby_memcheck to detect memory leaks, filtering out Ruby's GC noise.

  1. Install dependencies:

    gem install ruby_memcheck
    # Debian/Ubuntu
    apt-get install valgrind
  2. Set up a Rake task:

    # Rakefile
    require 'ruby_memcheck'

    namespace :test do
    RubyMemcheck::TestTask.new(valgrind: :compile) do |t|
    t.libs << "test"
    t.test_files = FileList["test/**/*_test.rb"]
    end
    end
  3. Run the task:

    bundle exec rake test:valgrind