Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/hywan/sonde-rs
A library to compile USDT probes into a Rust library
https://github.com/hywan/sonde-rs
dtrace ebpf probe rust rust-lang rust-library usdt
Last synced: about 1 month ago
JSON representation
A library to compile USDT probes into a Rust library
- Host: GitHub
- URL: https://github.com/hywan/sonde-rs
- Owner: wasmerio
- License: bsd-3-clause
- Created: 2021-03-08T16:17:46.000Z (almost 4 years ago)
- Default Branch: master
- Last Pushed: 2024-08-27T13:51:21.000Z (4 months ago)
- Last Synced: 2024-11-13T04:08:41.258Z (about 1 month ago)
- Topics: dtrace, ebpf, probe, rust, rust-lang, rust-library, usdt
- Language: Rust
- Homepage:
- Size: 65.4 KB
- Stars: 45
- Watchers: 5
- Forks: 4
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
sonde[![crates.io](https://img.shields.io/crates/v/sonde)](https://crates.io/crates/sonde)
[![documentation](https://img.shields.io/badge/doc-sonde-green)](https://docs.rs/sonde)`sonde` is a library to compile USDT probes into a Rust library, and
to generate a friendly Rust idiomatic API around it.[Userland Statically Defined Tracing][usdt] probes (USDT for short) is
a technique inherited from [DTrace] (see [OpenDtrace] to learn
more). It allows user to define statically tracing probes in their own
application; while they are traditionally declared in the kernel.USDT probes can be naturally consumed with DTrace, but also with
[eBPF] (`bcc`, `bpftrace`…).## Lightweight probes by design
USDT probes for libraries and executables are defined in an ELF
section in the corresponding application binary. A probe is translated
into a `nop` instruction, and its metadata are stored in the ELF's
`.note.stapstd` section. When registering a probe, USDT tool (like
`dtrace`, `bcc`, `bpftrace` etc.) will read the ELF section, and
instrument the instruction from `nop` to `breakpoint`, and after that,
the attached tracing event is run. After deregistering the probe, USDT
will restore the `nop` instruction from `breakpoint`.The overhead of using USDT probes is almost zero when no tool is
listening the probes, otherwise a tiny overhead can be noticed.## The workflow
Everything is automated. `dtrace` must be present on the system at
compile-time though. Let's imagine the following `sonde-test`
fictitious project:```
/sonde-test
├── src
│ ├── main.rs
├── build.rs
├── Cargo.toml
├── provider.d
```Start with the obvious thing: let's add the following lines to the
`Cargo.toml` file:```toml
[build-dependencies]
sonde = "0.1"
```Now, let's see what is in the `provider.d` file. It's _not_ a `sonde`
specific vendor format, it's the canonical way to declare USDT probes
(see [Scripting][scripting])!```d
provider hello {
probe world();
probe you(char*, int);
};
```It describes a probe provider, `hello`, with two probes:
1. `world`,
2. `you` with 2 arguments: `char*` and `int`.Be careful, D types aren't the same as C types, even if they look like
the same.At this step, one needs to play with `dtrace -s` to compile the probes
into systemtrap headers or an object file, but forget about that,
`sonde` got you covered. Let's see what's in the `build.rs` script:```rust
fn main() {
sonde::Builder::new()
.file("./provider.d")
.compile();
}
```That's all. That's the minimum one needs to write to make it
work.Ultimately, we want to fire this probe from our code. Let's see what's
inside `src/main.rs` then:```rust
// Include the friendly Rust idiomatic API automatically generated by
// `sonde`, inside a dedicated module, e.g. `tracing`.
mod tracing {
include!(env!("SONDE_RUST_API_FILE"));
}fn main() {
tracing::hello::world();println!("Hello, World!");
}
```What can we see here? The `tracing` module contains a `hello` module,
corresponding to the `hello` provider. And this module contains a
`world` function, corresponding to the `world` probe. Nice!See what's contained by the file pointed by
SONDE_RUST_API_FILE
:```rust
/// Bindings from Rust to the C FFI small library that calls the
/// probes.use std::os::raw::*;
extern "C" {
#[doc(hidden)]
fn hello_probe_world();#[doc(hidden)]
fn hello_probe_you(arg0: *mut c_char, arg1: c_int);
}/// Probes for the `hello` provider.
pub mod r#hello {
use std::os::raw::*;/// Call the `world` probe of the `hello` provider.
pub fn r#world() {
unsafe { super::hello_probe_world() };
}/// Call the `you` probe of the `hello` provider.
pub fn r#you(arg0: *mut c_char, arg1: c_int) {
unsafe { super::hello_probe_you(arg0, arg1) };
}
}
```Let's see it in action:
```sh
$ cargo build --release
$ sudo dtrace -l -c ./target/release/sonde-test | rg sonde-test
123456 hello98765 sonde-test hello_probe_world world
```Neat! Our `sonde-test` binary contains a `world` probe from the
`hello` provider!```sh
$ # Let's execute `sonde-test` as usual.
$ ./target/release/sonde-test
Hello, World!
$
$ # Now, let's execute it with `dtrace` (or any other tracing tool).
$ # Let's listen the `world` probe and prints `gotcha!` when it's executed.
$ sudo dtrace -n 'hello*:::world { printf("gotcha!\n"); }' -q -c ./target/release/sonde-test
Hello, World!
gotcha!
```Eh, it works! Let's try with the `you` probe now:
```rust
fn main() {
{
let who = std::ffi::CString::new("Gordon").unwrap();
tracing::hello::you(who.as_ptr() as *mut _, who.as_bytes().len() as _);
}println!("Hello, World!");
}
```Time to show off:
```sh
$ cargo build --release
$ sudo dtrace -n 'hello*:::you { printf("who=`%s`\n", stringof(copyin(arg0, arg1))); }' -q -c ./target/release/sonde-test
Hello, World!
who=`Gordon`
```Successfully reading a string from Rust inside a USDT probe!
With `sonde`, you can add as many probes inside your Rust library or
binary as you need by simply editing your canonical `.d` file.Bonus: `sonde` generates documentation for your probes
automatically. Run `cargo doc --open` to check.## Possible limitations
### Types
DTrace has its own type system (close to C) (see [Data Types and
Sizes][data-types]). `sonde` tries to map it to the Rust system as
much as possible, but it's possible that some types could not
match. The following types are supported:| Type Name in D | Type Name in Rust |
|-|-|
| `char` | `std::os::raw::c_char` |
| `short` | `std::os::raw::c_short` |
| `int` | `std::os::raw::c_int` |
| `long` | `std::os::raw::c_long` |
| `long long` | `std::os::raw::c_longlong` |
| `int8_t` | `i8` |
| `int16_t` | `i16` |
| `int32_t` | `i32` |
| `int64_t` | `i64` |
| `intptr_t` | `isize` |
| `uint8_t` | `u8` |
| `uint16_t` | `u16` |
| `uint32_t` | `u32` |
| `uint64_t` | `u64` |
| `uintptr_t` | `usize` |
| `float` | `std::os::raw::c_float` |
| `double` | `std::os::raw::c_double` |
| `T*` | `*mut T` |
| `T**` | `*mut *mut T` (and so on) |### Parser
The `.d` files are parsed by `sonde`. For the moment, only the
`provider` blocks are parsed, which declare the `probe`s. All the
pragma (`#pragma`) directives are ignored for the moment.## License
`BSD-3-Clause`, see `LICENSE.md`.
[usdt]: https://illumos.org/books/dtrace/chp-usdt.html
[DTrace]: https://en.wikipedia.org/wiki/DTrace
[OpenDtrace]: https://github.com/opendtrace/opendtrace
[eBPF]: http://www.brendangregg.com/blog/2019-01-01/learn-ebpf-tracing.html
[data-types]: https://illumos.org/books/dtrace/chp-typeopexpr.html#chp-typeopexpr-2
[`std::os::raw`]: https://doc.rust-lang.org/std/os/raw/index.html
[scripting]: https://illumos.org/books/dtrace/chp-script.html#chp-script