Git Product home page Git Product logo

bugstalker's Introduction

BugStalker

Modern debugger for Linux x86-64. Written in Rust for Rust programs.

debugger-demo


Table of Contents


Supported rustc versions

  • 1.75
  • 1.76
  • 1.77
  • 1.78
  • 1.79
  • 1.80

Features

  • written in rust for rust language with simplicity as a priority goal
  • breakpoints, steps, signals, watchpoints
  • multithreaded application support
  • data query expressions
  • support for a rust type system (collections, smart pointers, thread locals and many others), not only for printing but also for interaction
  • two ui types: console and tui, switch available at any moment
  • oracle as an extension mechanism
  • builtin tokio oracle - like tokio_console but there is no need to make changes to the source codes
  • and much more!

Installation

First check if the necessary dependencies (pkg-config and libunwind-dev) are installed:

For example, ubuntu/debian:

apt install pkg-config libunwind-dev

For example, fedora:

dnf install pkg-config libunwind-devel

Now install debugger:

cargo install bugstalker

That's all, bs command is available now!

Problem with libunwind? If you have any issues with `libunwind`, you can try to install `bs` with native unwinder (currently, I don't recommend this method because libunwind is better :))
cargo install bugstalker --no-default-features

Distro Packages

Packaging status

Packaging status

Arch Linux

pacman -S bugstalker

Nix package manager

There's flake which you can use to start using it. Just enable flakes then you're able to use it with:

nix run github:godzie44/BugStalker

bugstalker also provides a package which you can include to your NixOS config. For example:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    bugstalker.url = "github:godzie44/BugStalker";
  };

  outpus = {nixpkgs, bugstalker, ...}: {
    nixosConfigurations.your_hostname = nixpkgs.lib.nixosSystem {
      modules = [
        ({...}: {
          environment.systemPackages = [
            # assuming your system runs on a x86-64 cpu
            bugstalker.packages."x86_64-linux".default
          ];
        })
      ];
    };
  };
}
Home-Manager

There's a home-manager module which adds programs.bugstalker to your home-manager config. You can add it by doing the following:

{
  description = "NixOS configuration";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    home-manager.url = "github:nix-community/home-manager";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
    bugstalker.url = "github:godzie44/BugStalker";
  };

  outputs = inputs@{ nixpkgs, home-manager, bugstalker, ... }: {
    nixosConfigurations = {
      hostname = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
          home-manager.nixosModules.home-manager
          {
            home-manager.sharedModules = [
              bugstalker.homeManagerModules.default
              ({...}: {
                programs.bugstalker = {
                  enable = true;
                  # the content of `keymap.toml`
                  keymap = {
                    common = {
                      up = ["k"];
                    }
                  };
                };
              })
            ];
          }
        ];
      };
    };
  };
}

Start debugger session

To start with program from binary file use:

bs my_cool_program

Or with arguments:

bs my_cool_program -- --arg1 val1 --arg2 val2

Or attach to program by its pid:

bs -p 123

Help

Print help for view all available commands.

Start and restart

demo

  • run - start or restart a program (alias: r)

Stopping and continuing

The Debugger stops your program when breakpoints are hit, or after watchpoint are hit, or after steps commands, or when the OS signal is coming. BugStalker always stops the whole program, meaning that all threads are stopped. Thread witch initiated a stop become a current selected thread.

Continue execution

demo

  • continue - resume a stopped program

Breakpoints

demo

  • break {file}:{line} - set breakpoint at line (alias: b {file}:{line})
  • break {function name} - set breakpoint at start of the function ( alias: b {function_name})
  • break {instruction address} - set breakpoint at instruction ( alias: b {instruction address})
  • break remove {number} - remove breakpoint by its number ( alias: b r {number})
  • break remove {file}:{line} - remove breakpoint at line ( alias: b r {file}:{line})
  • break remove {function name} - remove breakpoint at start of the function ( alias: b r {function name})
  • break info - print all breakpoints

Watchpoints

demo

Watchpoint is a "data breakpoint". This means that the program stops when the variable (or expression, or just raw memory region) observed by watchpoint is changed. Currently, watchpoints feature based on x86-64 hardware breakpoints. Therefore, there are two limitations:

  • only 4 watchpoints are possible at one time
  • watchpoint "observe" memory region of 1/2/4/8 bytes size

You can set watchpoint at variables (global or locals), or at expression based on variables. Watchpoints for local variables will be removed automatically, when variable out of scope. If watchpoint observes a global variable, then it will live as long as the debugger is running.

Lets look at examples:

  • watch my_var - stop when variable value is rewriting (alias: w my_var)
  • watch +rw my_var - stop when variable value is reading or rewriting
  • watch my_vector[0] - stop when first vector element is rewriting
  • watch (~my_vector).len - stop when vector length is changed
  • watch 0x100:4 - stop when writing to memory region [0x100:0x103]

Steps

demo

  • stepi - step a single instruction
  • step - step a program until it reaches a different source line ( alias: stepinto)
  • next - step a program, stepping over subroutine (function) calls ( alias: stepover)
  • finish - execute a program until selected stack frame returns ( alias: stepout)

Signals

demo

BugStalker will catch signals sent from OS to debugee program and stop execution. For example, try to send SIGINT (ctrl+c) to the debugee program to stop it.

Change current selected thread

demo

  • thread info - print list with information about threads
  • thread current - prints current selected thread
  • thread switch {number} - switch selected thread

Examining the stack

When your program has stopped, the first thing you need to know is where it stopped and how it got there.

Each time your program performs a function call, the information about where in your program the call was made from is saved in a block of data called a stack frame. The frame also contains the arguments of the call and the local variables of the function that was called. All the stack frames are allocated in a region of memory called the call stack.

Stack frames

The call stack is divided up into contiguous pieces called stack frames. Each frame is the data associated with one call to one function. The frame contains the arguments given to the function, the function's local variables, and the address at which the function is executed.

Backtrace

demo

  • backtrace - print backtrace of current stopped thread (alias: bt). Backtrace contains information about thread (number, pid, address of instruction where thread stopped) and all frames starting with the currently executing frame (frame zero), followed by its caller (frame one), and on up the stack.
  • backtrace all - print backtraces of all active threads (alias: bt all).

Select a frame

demo

Most commands for examining the stack and other data in your program works on whichever stack frame is selected at the moment.

  • frame info - print information about current selected frame.
  • frame switch {num} - change current selected frame.

Examining source files

demo

BugStalker can print parts of your program's source code. When your program stops, the debugger spontaneously prints the line where it stopped. There is source commands for print more.

  • source fn - print current selected function
  • source {num} - print lines range [current_line-num; current_line+num]
  • source asm - print assembly representation of current selected function

Examining data

demo

Of course, you need a way to examine data of your program.

  • var {expression}|locals command for print local and global variables
  • arg {expression}|all command for print a function arguments

These commands accept expressions as input or have a special mode (var locals print all local variables, args all print all arguments).

Expression

BugStalker has a special syntax for explore program data. You can dereference references, get structure fields, slice arrays or get elements from vectors by its index (and much more!).

Operator available in expressions:

  • select variable by its name (ex. var a)
  • dereference pointers/references/smart pointers (ex. var *ref_to_a)
  • take a structure field (ex. var some_struct.some_field)
  • take an element by index or key from arrays, slices, vectors, hashmaps ( ex. var arr[1] or even var hm[{a: 1, b: 2}])
  • slice arrays, vectors, slices (ex. var some_vector[1..3] or var some_vector[1..])
  • cast constant address to a pointer of a concrete type ( ex. var (*mut SomeType)0x123AABCD)
  • take address (ex. var &some_struct.some_field)
  • show canonic representation (for example, show vector header instead of vector data var ~myvec)
  • parentheses for control an operator execution ordering

Write expressions is simple, and you can do it right now! Some examples:

  • var *some_variable - dereference and print value of some_variable
  • var hm[{a: 1, b: *}] - print value from hashmap corresponding to the key. Literal {a: 1, b: *} matches to any structure with field a equals to 1 and field b equals to any value
  • var some_array[0][2..5] - print three elements, starts from index 2 from zero element of some_array
  • var *some_array[0] - print dereferenced value of some_array[0]
  • var &some_array[0] - print address of some_array[0]
  • var (~some_vec).len - print len field from vector header
  • var (*some_array)[0] - print a zero element of *some_array
  • var *(*(var1.field1)).field2[1][2] - print dereferenced value of element at index 2 in element at index 1 at field field2 in dereferenced value of field field1 at variable var1 🀑

Other commands

Of course, the debugger provides many more commands:

  • symbol {name or regex} - print symbol kind and address
  • memory read {addr} - read debugged program memory (alias: mem read)
  • memory write {addr} {value} - write into debugged program memory ( alias: mem write)
  • register read {reg_name} - print value of register by name (x86_64 register name in lowercase) (alias: reg read)
  • register write {reg_name} {value} - set new value to register by name ( alias: reg write)
  • register info - print list of registers with it values (alias: reg info)
  • sharedlib info - show list of shared libraries
  • quit - exit the BugStalker (alias: q)

Tui interface

demo

One of the most funny BugStalker features is switching between old school terminal interface and pretty tui at any moment.

  • tui - switch too terminal ui (in tui use Esc for switch back)

Configuration

There is a keymap.toml file with tui keybindings configuration. You can find the default configuration files at https://github.com/godzie44/BugStalker/tree/master/src/ui/tui/config/preset/keymap.toml.

To override any of the defaults, begin by creating the corresponding file (from the file linked above) to: ~/.config/bs/keymap.toml. You can change keybindings configuration file by exporting the KEYMAP_FILE environment variable.

Oracles

demo console

demo tui

Oracle is a module that expands the capabilities of the debugger. Oracles can monitor the internal state of a program to display interesting information. For example, tokio oracle is able to provide information about tokio runtime during program debugging without the need to change the source code. You must run the debugger with enabled oracle, for example, for tokio oracle:

bs --oracle tokio ...

Then use oracle command for view oracle information:

  • oracle {oracle name} {subcommands} - run oracle (ex. oracle tokio)

Oracles also available in tui. Currently, there is only one builtin oracle - tokio oracle.

Contributing

Feel free to suggest changes, ask a question or implement a new feature. Any contributions are very welcome.

How to contribute.

bugstalker's People

Contributors

boolpurist avatar foorack avatar godzie44 avatar orhun avatar tornaxo7 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bugstalker's Issues

TUI improvements

There much more improvements that can be maded for TUI mode:

  • Resize tabs (the ability to expand interested window)
  • Better tab switching
  • active line must glued to the middle of render area (thanks @jan-ferdinand)
  • colorize data in variables tab

debug c code

Hi! I've got a little question regarding debugging c with bugstalker instead of gdb.
Assuming we've got the following file:
/tmp/main.c

#include <stdio.h>

int main() {
    puts("Hello there!");
    return 0;
}

and if I execute the following commands:

cd /tmp
gcc main.c
bs a.out

now if I do b main I'm getting:

Add deferred breakpoint for future shared library load?

is that expected? Do you have an suggestion how to fix this so that I can debug c code?

Question: How to debug tests?

Hi! May I ask how I can debug a test? Would be cool if you could debug a test with bs mod1::mod2::mod3::name_of_test_function.

EDIT1: Ok, they are stored in target/debug/deps but... I can't find my integration tests there.

Feature: watchpoints

Add watchpoints (data breakpoint) for memory regions, variables and arguments

async backtrace - like `backtrace` command but shows futures await-stack

Feature description

Create async backtrace and async backtrace all command.

  • async backtrace - print all futures of currently executed async tasks (current executed tasks count <= async workers count), by decomposing what, exactly, it’s waiting for
  • async backtrace all - print all async workers state (all workers with all tasks with full future stack)

Example (async backtrace):

Async worker #2 (pid: 388235)
Current task: 3
#0 async fn tokioticker2::new_ticker_task
        suspended at await point 0
#1 async fn tokioticker2::new_ticker_task_inner1
        just created

Async worker #3 (pid: 388236)
Current task: 7
#0 async fn tokioticker2::new_ticker_task
        suspended at await point 0
#1 async fn tokioticker2::new_ticker_task_inner1
        suspended at await point 0
#2 future tokio::time::sleep::Sleep

Implementation notes

Currently we will focus on tokio async runtime.

"Root" of our batcktrace is a tokio tasks. Any suspended task must wait some future, that future wait another and so on. The place where we can find root futures (tasks) is the local queues (TODO may be not only local queues, need rnd) of each worker (worker is a system thread). Place where we can find current task - is a frame where run_task function executed.

Unfortunately Tokyo does not provide some sort of explicit metadata exposed for debuggers. So there is no easy way to get tasks-queues addresses in memory. Lets describe possible solutions:

1. Set breakpoints in task constructors/destructors and store task information at these points

This solution good for tokio oracle, because give us "real-time" information. Visualization of this information we can use in tui interface, and it's pretty good. But this solution give us a big overhead - we stop whole program when task created or drop'd.

2. Set breakpoints at tokio runtime initialization process and save pointers task queues

This solution is better then previous one cause we set breakpoint only once, so overhead is minimal. But what if debugee program not using a tokio runtime? What should be the reaction to an error when setting a breakpoint? It looks like we need to check some characters before starting the program to answer the question - "is tokio runtime used?".

3. Move down the call stack at every async backtrace command and find pointers into task queues

When user enter async backtrace command we can to move down the call stack until the worker loop acting as the scheduler is reached (currently this is a worker::Context::run function). Then we can observe local queue of the worker. Then we do the same for all threads. Point to locals queues can be cached.

It looks like we should stick with solution number 3; it allows us to perform computations only in response to a
async backtrace command and does not have any additional overhead.

Async support

Now BugStalker can provide a some of new features for support async debuging.

Problem

Debug of async program in rust very hard for a lot of reason. Compiler dont generate good DWARF for async programs, and debugger dont know nothing about futures, about state machine generated by compiler and other tricks of rust async code.

What to do

We can create a some tools for work with async code. It may be just information tools (like tokio oracle) and some asynchronous analogues of regular commands. Asynchronous analogues - is a regular commands (like step, or backtrace) but prefixed with async.

Runtimes support

Currently we only declare support for tokio runtime.

Sub issues

  • tokio oracle - like tokio console
  • #27
  • async step - like regular steps, but step in selected future
  • async var locals - like var locals but for future captured data
  • ...

Build fails on my Fedora 39

I tried running the project with

cargo run

Unfortunately it fails with the following build error

  --- stderr
  thread 'main' panicked at /home/phil/.cargo/registry/src/index.crates.io-6f17d22bba15001f/unwind-sys-0.1.3/build.rs:16:55:
  called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I figured that this is due to my libunwind version which is 1.7-rc1. The build.rs of unwind-sys wasn't prepared to deal with release candidates. It got fixed with following commit

The issue can be solved by updating unwind crate

-unwind = { version = "0.4.1", features = ["ptrace"], optional = true }
+unwind = { version = "0.4.2", features = ["ptrace"], optional = true }

Commands instruction?

While I haven't tried BugStalker yet (just discovered it) I have noticed the absence of one gdb command that would probably be quite easy to implement and has proved useful in gdb in the past for me.

That is the commands instruction which simply sets a list of commands to be executed automatically when a breakpoint is hit. This can be useful to e.g. print all the backtraces for a given breakpoint or some specific pieces of data at every occurence relatively quickly.

Awesome work!

Hi! I just tried the debugger out and damn it's pretty neat! Just wanted to say that I like this project :) Keep it up c(^-^)c

F8 - step over leads to libunwind error

single stepping with F7 works.

However using the step over function fails with following libunwind error

grafik

I reproduced this behaviour with the current HEAD revision. In order to be able to build I had to update the unwind crate. See my other issue #1.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.