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