Debugging

Techniques and best practices for debugging Rust plugins and handling errors in your language module development process.

Debugging is an essential part of developing Rust plugins for Plugify. This guide provides comprehensive techniques and tools for debugging Rust plugins effectively.

Prerequisites

Before debugging your Rust plugins, ensure you have the following:

  • Rust toolchain (rustc, cargo) installed
  • A debugger: GDB (Linux), LLDB (macOS), or MSVC debugger (Windows)
  • IDE with Rust support: VS Code with rust-analyzer, CLion, or IntelliJ IDEA
  • Plugify Core Library (built and available)
  • Rust Language Module installed and configured

Debug vs Release Builds

Debug Build

Debug builds include debugging information and are unoptimized:

cargo build

Features:

  • Full debug symbols
  • No optimization
  • Faster compilation
  • Easier to debug
  • Larger binary size
  • Slower execution

Release Build

Release builds are optimized but harder to debug:

cargo build --release

Features:

  • Optimized code
  • May inline functions
  • Harder to step through
  • Smaller binary size
  • Faster execution

Release with Debug Info

Best of both worlds for debugging production issues:

Cargo.toml
[profile.release]
debug = true
strip = false
cargo build --release

Debugging Tools

1. Using rust-gdb (Linux)

Build with Debug Info

cargo build

Attach Debugger

rust-gdb target/debug/libplugin_name.so

# Or attach to running process
rust-gdb -p <plugify_pid>

Set Breakpoints

(gdb) break plugin_name::on_plugin_start
(gdb) break src/lib.rs:42

Run and Debug

(gdb) run
(gdb) continue
(gdb) next
(gdb) step
(gdb) print variable_name
(gdb) backtrace

2. Using rust-lldb (macOS/Linux)

Build with Debug Info

cargo build

Start Debugger

rust-lldb target/debug/libplugin_name.so

Common Commands

(lldb) breakpoint set --name on_plugin_start
(lldb) breakpoint set --file lib.rs --line 42
(lldb) run
(lldb) next
(lldb) step
(lldb) frame variable
(lldb) bt

3. Using Visual Studio Code

Install Extensions

  • rust-analyzer: Rust language support
  • CodeLLDB: LLDB debugger integration

Configure launch.json

.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug Plugin",
            "cargo": {
                "args": [
                    "build",
                    "--lib"
                ]
            },
            "program": "${workspaceFolder}/target/debug/libplugin_name.so",
            "cwd": "${workspaceFolder}",
            "sourceLanguages": ["rust"]
        },
        {
            "type": "lldb",
            "request": "attach",
            "name": "Attach to Plugify",
            "program": "/path/to/plugify",
            "pid": "${command:pickProcess}"
        }
    ]
}

Set Breakpoints

Click in the left margin next to line numbers in your source files.

Start Debugging

Press F5 or click "Run and Debug" → "Debug Plugin"

4. Using CLion/IntelliJ IDEA

Open Project

Open the Cargo.toml file as a project.

Configure Debugger

  1. Go to Run → Edit Configurations
  2. Add Rust Cargo Command
  3. Set command: build
  4. Set working directory to project root

Set Breakpoints

Click in the gutter next to line numbers.

Start Debugging

Click the debug icon or press Shift+F9.

Logging and Tracing

Using println! for Basic Logging

fn on_plugin_start() {
    println!("Plugin starting...");
    println!("Debug: variable = {:?}", some_variable);
}

Using dbg! Macro

fn process_data(value: i32) -> i32 {
    dbg!(value);  // Prints: [src/lib.rs:42] value = 42
    let result = value * 2;
    dbg!(result)  // Returns result while printing it
}

Using log Crate

Cargo.toml
src/lib.rs
[dependencies]
plugify = { git = "https://github.com/untrustedmodders/rust-plugify" }
log = "0.4"
env_logger = "0.11"

Set log level via environment variable:

RUST_LOG=debug cargo run
RUST_LOG=plugin_name=trace cargo run

Using tracing Crate

For more advanced logging and instrumentation:

Cargo.toml
src/lib.rs
[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"

Common Debugging Scenarios

1. Plugin Crashes

Symptoms: Plugin crashes or causes segfault.

Debugging steps:

# Run with backtrace
RUST_BACKTRACE=1 cargo run

# Full backtrace
RUST_BACKTRACE=full cargo run

# Use debugger
rust-gdb target/debug/libplugin_name.so

Check for:

  • Null pointer dereferences
  • Out of bounds access
  • Unsafe code issues
  • FFI boundary problems

2. Plugin Not Loading

Symptoms: Plugify doesn't load the plugin.

Debugging steps:

  1. Check Plugify logs
  2. Verify manifest file (.pplugin)
  3. Ensure library is in correct location
  4. Check library dependencies:
    # Linux
    ldd target/release/libplugin_name.so
    
    # macOS
    otool -L target/release/libplugin_name.dylib
    
    # Windows
    dumpbin /dependents target/release/plugin_name.dll
    

3. Memory Issues

Symptoms: Memory leaks or corruption.

Use Valgrind (Linux):

valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         /path/to/plugify

Use AddressSanitizer:

Cargo.toml
[profile.dev]
opt-level = 1

[profile.dev.package."*"]
opt-level = 3
RUSTFLAGS="-Z sanitizer=address" cargo build --target x86_64-unknown-linux-gnu

4. Performance Issues

Use profiling tools:

# Install flamegraph
cargo install flamegraph

# Generate flamegraph
cargo flamegraph

# Or use perf (Linux)
cargo build --release
perf record --call-graph=dwarf /path/to/plugify
perf report

Debugging Techniques

1. Conditional Compilation

#[cfg(debug_assertions)]
fn debug_info() {
    println!("Debug mode only");
}

fn on_plugin_start() {
    #[cfg(debug_assertions)]
    debug_info();

    #[cfg(not(debug_assertions))]
    println!("Release mode");
}

2. Assert and Debug Assert

fn process_value(value: i32) {
    assert!(value >= 0, "Value must be non-negative");

    // Only in debug builds
    debug_assert!(value < 1000, "Value too large");
}

3. Custom Debug Output

#[derive(Debug)]
struct Config {
    name: String,
    value: i32,
}

let config = Config {
    name: "test".to_string(),
    value: 42,
};
println!("{:?}", config);      // Debug format
println!("{:#?}", config);     // Pretty debug format

4. Panic Hooks

use std::panic;

fn on_plugin_start() {
    panic::set_hook(Box::new(|panic_info| {
        eprintln!("Plugin panic: {:?}", panic_info);
        // Log to file, send to monitoring service, etc.
    }));
}

Advanced Debugging

1. Debugging Macros

macro_rules! debug_println {
    ($($arg:tt)*) => {
        #[cfg(debug_assertions)]
        println!($($arg)*);
    };
}

debug_println!("Debug: value = {}", 42);

2. Debugging Async Code

use tracing::instrument;

#[instrument]
async fn async_operation() {
    tracing::info!("Starting async operation");
    // Async work here
}

3. Remote Debugging

For debugging on remote systems:

# On remote system
gdbserver :1234 /path/to/plugify

# On local system
rust-gdb
(gdb) target remote remote-host:1234

Troubleshooting

Debugger Not Stopping at Breakpoints

Solutions:

  • Ensure debug symbols are enabled: debug = true in Cargo.toml
  • Rebuild: cargo clean && cargo build
  • Check breakpoint location is reachable code
  • Verify debugger is attached to correct process

Can't See Variable Values

Solutions:

  • Variables may be optimized away in release builds
  • Use debug = true in release profile
  • Add #[inline(never)] to prevent inlining
  • Check variable is in scope

Symbols Not Loading

Solutions:

  • Install Rust debugger scripts: rust-gdb, rust-lldb
  • Check debug info is present: objdump -h target/debug/libplugin_name.so | grep debug
  • Ensure matching Rust versions between debugger and code

Best Practices

  1. Build with debug symbols for development
  2. Use logging instead of println! for production
  3. Enable all compiler warnings: #![warn(clippy::all)]
  4. Use Rust's testing framework: cargo test
  5. Enable backtrace in development: RUST_BACKTRACE=1
  6. Profile before optimizing: Measure don't guess
  7. Use static analysis: cargo clippy
  8. Check for memory issues: Use sanitizers
  9. Document debugging steps: Maintain troubleshooting docs
  10. Test error paths: Don't just test happy paths

Useful Resources

For more information on debugging Rust applications, refer to:

Conclusion

Effective debugging is crucial for developing reliable Rust plugins. By leveraging Rust's excellent tooling, comprehensive error messages, and safety guarantees, you can quickly identify and fix issues. The combination of Rust's compile-time checks and runtime debugging tools provides a powerful environment for building robust Plugify plugins.