2 个版本
0.1.1 | 2021 年 3 月 11 日 |
---|---|
0.1.0 | 2021 年 3 月 9 日 |
#423 in Unix API
49KB
559 行代码(不含注释)

sonde
sonde
是一个将 USDT 探针编译成 Rust 库的库,并为其生成友好的 Rust 风格 API。
用户定义静态跟踪探针(简称 USDT)是一种从 DTrace(参见 OpenDtrace 了解更多信息)继承的技术。它允许用户在他们的应用程序中定义静态跟踪探针,而传统上这些探针是在内核中声明的。
USDT 探针可以自然地与 DTrace 消费,也可以与 eBPF(例如 bcc
,bpftrace
等)一起使用。
轻量级探针设计
库和可执行文件中的 USDT 探针定义在相应应用程序二进制文件中的 ELF 部分。探针被转换为一个 nop
指令,其元数据存储在 ELF 的 .note.stapstd
部分。当注册探针时,USDT 工具(如 dtrace
,bcc
,bpftrace
等)将读取 ELF 部分,并将指令从 nop
修改为 breakpoint
,然后运行附加的跟踪事件。注销探针后,USDT 将从 breakpoint
恢复 nop
指令。
当没有工具监听探针时,使用 USDT 探针的开销几乎为零,否则可以观察到微小的开销。
工作流程
一切都是自动化的。不过,在编译时系统上必须有 dtrace
。让我们想象以下 sonde-test
这个虚构项目
/sonde-test
├── src
│ ├── main.rs
├── build.rs
├── Cargo.toml
├── provider.d
从最明显的事情开始:让我们在 Cargo.toml
文件中添加以下行
[build-dependencies]
sonde = "0.1"
现在,让我们看看 provider.d
文件里有什么。它不是针对 sonde
的特定供应商格式,而是声明 USDT 探针的规范方法(见 脚本)!
provider hello {
probe world();
probe you(char*, int);
};
它描述了一个名为 hello
的探针提供者,包含两个探针。
world
,you
探针有 2 个参数:char*
和int
。
请注意,D 类型的类型与 C 类型的类型不完全相同,即使它们看起来一样。
在这个步骤中,需要使用 dtrace -s
来编译探针到系统陷阱头文件或对象文件,但别担心,sonde
会为你处理。让我们看看 build.rs
脚本里有什么。
fn main() {
sonde::Builder::new()
.file("./provider.d")
.compile();
}
就是这样。这就是使它正常工作的最小代码。
最终,我们希望在代码中触发这个探针。那么让我们看看 src/main.rs
里面有什么。
// 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!");
}
这里有什么?tracing
模块包含一个 hello
模块,对应于 hello
提供者。并且这个模块包含一个 world
函数,对应于 world
探针。太棒了!
看看 SONDE_RUST_API_FILE
指向的文件内容。
/// 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) };
}
}
让我们看看它的实际效果。
$ cargo build --release
$ sudo dtrace -l -c ./target/release/sonde-test | rg sonde-test
123456 hello98765 sonde-test hello_probe_world world
太酷了!我们的 sonde-test
可执行文件包含了来自 hello
提供者的 world
探针!
$ # 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!
嘿,它工作了!现在让我们尝试使用 you
探针。
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!");
}
是时候展示一下了。
$ 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`
在 USDT 探针内部成功读取一个字符串!
使用 sonde
,您可以通过简单地编辑您的规范 .d
文件来添加尽可能多的探针到您的 Rust 库或二进制文件中。
额外功能:sonde
会自动为您的探针生成文档。运行 cargo doc --open
来检查。
可能的限制
类型
DTrace 有自己的类型系统(接近 C)(见 数据类型和大小)。sonde
尽可能地将它映射到 Rust 系统中,但某些类型可能无法匹配。以下类型被支持
D 语言中的类型名称 | 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* |
*mutT |
T** |
*mut *mut T (等等) |
解析器
..d
文件由 sonde
解析。目前,只解析了 provider
块,这些块声明了 probe
。目前忽略了所有的预处理器指令(#pragma
)。
许可证
BSD-3-Clause
,见 LICENSE.md
。
依赖项
~4–12MB
~155K SLoC