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.
-
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' -
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.
-
Enable core dumps (Linux/macOS):
ulimit -c unlimited -
Run the code that causes the crash to generate a
corefile. -
Load the core dump:
gdb --core=core --quiet `which ruby` -
Get a backtrace: Use
btto 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.
-
Install dependencies:
gem install ruby_memcheck
# Debian/Ubuntu
apt-get install valgrind -
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 -
Run the task:
bundle exec rake test:valgrind