Crates.io | bugstalker |
lib.rs | bugstalker |
version | 0.2.4 |
source | src |
created_at | 2024-02-29 21:55:52.481247 |
updated_at | 2024-10-20 19:53:56.940594 |
description | BugStalker is a modern and lightweight debugger for rust applications. |
homepage | |
repository | https://github.com/godzie44/BugStalker |
max_upload_size | |
id | 1158231 |
size | 1,361,993 |
Modern debugger for Linux x86-64. Written in Rust for Rust programs.
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!
cargo install bugstalker --no-default-features
pacman -S bugstalker
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
];
})
];
};
};
}
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"];
}
};
};
})
];
}
];
};
};
};
}
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
Print help
for view all available commands.
run
- start or restart a program (alias: r
)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
- resume a stopped programbreak {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 breakpointsWatchpoint 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:
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 rewritingwatch my_vector[0]
- stop when first vector element is rewritingwatch (~my_vector).len
- stop when vector length is changedwatch 0x100:4
- stop when writing to memory region [0x100:0x103]stepi
- step a single instructionstep
- 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
)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.
thread info
- print list with information about threadsthread current
- prints current selected threadthread switch {number}
- switch selected threadWhen 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.
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
- 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
).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.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 functionsource {num}
- print lines range [current_line-num; current_line+num]source asm
- print assembly representation of current selected functionOf course, you need a way to examine data of your program.
var {expression}|locals
command for print local and global variablesarg {expression}|all
command for print a function argumentsThese commands accept expressions as input or have a special mode
(var locals
print all local variables, args all
print all arguments).
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:
var a
)var *ref_to_a
)var some_struct.some_field
)var arr[1]
or even var hm[{a: 1, b: 2}]
)var some_vector[1..3]
or var some_vector[1..]
)var (*mut SomeType)0x123AABCD
)var &some_struct.some_field
)var ~myvec
)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 valuevar 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 headervar (*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 🤡Of course, the debugger provides many more commands:
symbol {name or regex}
- print symbol kind and addressmemory 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 librariesquit
- exit the BugStalker (alias: q
)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)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.
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.
Feel free to suggest changes, ask a question or implement a new feature. Any contributions are very welcome.