ethanpailes/debug-here

debug-here: a cross platform rust debugger hook

Debuggers are a great way to examine the state of a program

The Problem

you are trying to understand, but actually beginning to use one can have a bit of ceremony involved. You have to figure out the name of the executable you care about. This can be harder than it might initially seem if you are running your program through several layers of scripts that someone else wrote, or even that you wrote. Once you've got your executable name you have to start it with rust-gdb exe (if you use a wrapper script, you have to arrange for your script to do so). Now that you've started your program under gdb you have to set your break point. It's not that much work, but I find myself doing it a fair amount, so it can get annoying.

One alternative is to arrange for your program to enter some sort of sleep or looping state, rummage around for the PID on the command line, then attach with gdb -pid. Usually this ends up being a bit more work (again, not much work, but enough to be annoying), so people only do it if it is not convenient to launch the program in question from the terminal.

Making getting to the debugger easier in rust

This crate automates the process of convincing your program to wait around for a debugger to attach to it. Entering the debugger should be just as easy as writing a println! statement. This crate makes it so.

Setup

Linux Specific Setup

If you are using linux and you want to use xterm for your terminal emulator, you need to install the debugger wrapper (which is called debug-here-gdb-wrapper for historical reasons). Not all terminal emulators allow you to pass extra arguments to the program you invoke as your shell, so debug-here-gdb-wrapper will arrange for your debugger backend to execute all the right commands. Note that you never need to call debug-here-gdb-wrapper directly, it just needs to be somewhere on your path, so that the debug_here!() macro can call it.

cargo install debug-here-gdb-wrapper

Alternatively, you could use alacritty as your terminal emulator, in which case you don't need to bother with the debug-here-gdb-wrapper. If you have alacritty on your path, debug-here will automatically chose to use it over xterm, so there is no special setup required after you've installed it.

Windows Specific Setup

debug-here uses windows Just-In-Time Debugging to allow you to use visual studio to debug your code, so you have to have a working visual studio installation.

General Setup

Just add debug-here to the dependencies of a crate you want to work on.

debug-here = "0.2"

Usage

Drop the usual #[macro_use] extern crate debug_here; somewhere in your lib.rs or main.rs, then just write debug_here!() wherever you want to get dropped into the debugger. When your program reaches that point for the first time, it will launch a debugger appropriate for your platform attached to your process right after the debug_here!() macro. You can poke around and start stepping through your program. If you reach another debug_here!() macro invocation, you don't have to worry about more debugger terminals spawning left and right. debug_here!() only fires once per program.

Windows Usage Notes

Visual Studio will drop you into the debugger right at the end of the debug_break_wrapper function. You can just mash F10 a few times to get back to your code where the debug_here!() macro was invoked.

Visual Studio is the only debugger supported on windows.

Unixy Usage Notes

On linux and macos you can choose to use either rust-gdb or rust-lldb as debugger backends. If you plan to leave debug_here!() macros in your code, you should avoid forcing a particular backend because not all backends work well on all platforms. Windows will not work with gdb or lldb for example.

Supported Terminal Emulators

Currently debug-here supports alacritty and xterm on linux, and Terminal.app on macos. If you have alacritty on your path, it will use that, on the theory that you would rather use a less standard terminal emulator if you went to all the trouble of installing it. If you don't have alacritty on your path, it will fall back on xterm.

Platforms

Right now debug-here only works on linux, macos and windows. debug-here defaults to using rust-gdb on linux, rust-lldb on macos, and Visual Studio on windows. The primary reason for defaulting to rust-lldb on macos is to avoid the pain of getting a properly code-signed gdb.

debug-here probably won't grow support for any more platforms, though it's possible that windows will grow support for gdb and lldb. I'm happy to take patches for more exotic platforms, though testing may be an issue.

An Example: Bad Factorials

I have a very important rust program called debug-me which computes the factorial of 5. Or at least its supposed to. Right now it is telling me that the factorial of 5 is 0, which doesn't seem right. Here is my main.rs.

fn factorial(n: usize) -> usize {
    let mut res = 1;
    for i in 0..n {
        res *= i;
    }
    res
}

fn main() {
    println!("The factorial of 5 is {}!", factorial(5));
}

You can probably see the problem, but I can't because I'm helpless without a debugger. In order to figure out what is going on, I'm going to pull in debug-here to help me out. First, I'll make sure that the debug-here debugger shim is installed with cargo install debug-here-gdb-wrapper. Now, I'll add debug-here to my factorial crate's Cargo.toml.

[dependencies]
debug-here = "0.1"

And I'll add the line

#[macro_use] extern crate debug_here;

to my source file. Now it looks like this:

#[macro_use] extern crate debug_here;

fn factorial(n: usize) -> usize {
    let mut res = 1;
    for i in 0..n {
        res *= i;
    }
    res
}

fn main() {
    println!("The factorial of 5 is {}!", factorial(5));
}

My loop is definitely counting up and multiplying the result variable by bigger and bigger numbers. I feel like it should work, but I'm going to step through the loop a few times to see what's going on. Time to set my breakpoint with debug-here.

#[macro_use] extern crate debug_here;

fn factorial(n: usize) -> usize {
    let mut res = 1;
    debug_here!();
    for i in 0..n {
        res *= i;
    }
    res
}

fn main() {
    println!("The factorial of 5 is {}!", factorial(5));
}

As easy as println!! Now I run my program with cargo run. An terminal window pops up with a gdb shell that says:

debug_me::factorial (n=5) at debug-me/src/main.rs:6
6           for i in 0..n {
(gdb)

Looking at my source code, it seems like res is an interesting variable to keep track of, so I'll ask gdb to keep me informed.

(gdb) disp res
1: res = 1

Now, I'll step through the loop a few times.

(gdb) n
7               res *= i;
1: res = 1
(gdb) n
6           for i in 0..n {
1: res = 0
(gdb) n
7               res *= i;
1: res = 0

It looks like res went to 0 in the first loop iteration. Looking back at the source, I can see that this is because the counter starts at 0. I'll quit out of the debugger and fix it.

If you want to play with this example yourself, you can just clone the repo at https://github.com/ethanpailes/debug-here and run the following commands:

 > cargo install debug-here-gdb-wrapper # if you are on linux
 > cd debug-here/debug-me
 > cargo run

You should see a terminal pop up with a debugger shell ready to go. There is another bug in the factorial routine. Try finding it.

Issues

Collection of the latest Issues

inevity

inevity

Comment Icon0

Use example code, exec rust-lldb -p 43329 -o 'expression looping = 0' -o finish ...... (lldb) type category enable Rust (lldb) process attach --pid 43329 Process 43329 stopped

  • thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00000001040105fd testdebughere`debug_here::internal::debug_here_unixy_impl::hf6d1cf537d18380d(debugger=Option<&str> @ 0x00007ffeebbfca40) at internal.rs:111:5 108 109 // Now we enter an infinite loop and wait for the debugger to come to 110 // our rescue -> 111 while looping {} 112 } 113 114 /// Pop open a debugger on windows. Target 0: (testdebughere) stopped.

Executable module set to "/Users/xxxx/Downloads/testdebughere/target/debug/testdebughere". Architecture set to: x86_64h-apple-macosx-. (lldb) expression looping = 0 (bool) $0 = false (lldb) finish here hung!

Need two ctl +C,and only breaking at the 111 line.

LoganDark

LoganDark

Comment Icon1

I use iTerm2, which is in many ways better than the default Terminal.app that comes with macOS. However, your library seems hardcoded to use the default Terminal.app (according to the source code).

Is it possible for you to look into iTerm2 support? Perhaps this could be generalized to add support for any custom terminal emulator (within reason), but iTerm2 is the most popular alternative AFAIK.

hassek

hassek

Comment Icon0

When running debug_me within a test two things happen currently:

1.- The debugger stops at the point but it's broken throwing:

image

2.- debug_me needs to be installed on [dependencies] instead of [dev-dependencies]. I am not sure if there is anything to be done with that though.

ethanpailes

ethanpailes

Comment Icon0

A little copy-paste is better than a little dependency. It would be really nice if I could cut my dependencies down to just lazy-static and winapi.

ethanpailes

ethanpailes

Comment Icon0

Right now the program to be debugged will just spin silently if the terminal emulator we have chosen to launch fails. It would be a better user experience if the program to be debugged just slept for a second before complaining that the terminal emulator failed to launch. I can't think of a way to do this that is compatible with the old debug-here-gdb-wrapper, so we might need to cut a new version of that, which is a little unfortunate.

Information - Updated Nov 07, 2021

Stars: 60
Forks: 3
Issues: 5

rhex ASCII terminal game coded in Rust

Learn how to code your own terminal based game in Rust

rhex ASCII terminal game coded in Rust

KDash - A fast and simple dashboard for Kubernetes

A simple terminal dashboard for Kubernetes built with Rust

KDash - A fast and simple dashboard for Kubernetes

A dead simple ANSI terminal color painting library for Rust

See the ansi_term, term_painter, to name a few), begging the question: why yet another? Here

A dead simple ANSI terminal color painting library for Rust
Linux

1.3K

In terminal graphical metrics for your *nix system written in Rust

Optional CPU, Memory, Network, and Disk usage charts

In terminal graphical metrics for your *nix system written in Rust

gobang is currently in alpha

A cross-platform terminal database tool written in Rust

gobang is currently in alpha
CLI

1.6K

From source (recommended)

A small command-line application to view images from the terminal written in Rust

From source (recommended)

ranger-like terminal file manager written in Rust

wiki/Configuration for details

ranger-like terminal file manager written in Rust
CLI

249

Ttyper is a terminal-based typing test built with Rust and tui-rs

For usage instructions, you can run ttyper --help

Ttyper is a terminal-based typing test built with Rust and tui-rs

Terminal plotting library for using in Rust CLI applications

Should work well in any unicode terminal with monospaced font

Terminal plotting library for using in Rust CLI applications

A 'Space Invader' clone made with rust and made for the terminal

Inspired by Apache License (Version 2

A 'Space Invader' clone made with rust and made for the terminal

A minimal 1-on-1 terminal messenger cli written purely in rust

This is an extremely minimalistic version of any sort of messenger piece of software, except it's only meant for 1 on 1 sessions and everything...

A minimal 1-on-1 terminal messenger cli written purely in rust
Facebook Instagram Twitter GitHub Dribbble
Privacy