Skip to content

Commit 1d99584

Browse files
Copilotyunwei37Copilot
authored
Fix Rust uprobe tutorial and add CI tests in dedicated workflow (#197)
* Initial plan * Fix issue #173: Update Rust uprobe documentation to use debug build and correct function symbol Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> * Add CI tests for Rust uprobe tutorial (issue #173) Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> * Update src/37-uprobe-rust/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 云微 <1067852565@qq.com> * Update src/37-uprobe-rust/README.zh.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 云微 <1067852565@qq.com> * Use wildcard patterns for Rust symbol matching in bpftrace examples Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> * Add CI test to verify bpftrace can attach to Rust uprobe Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> * Separate bpftrace tests into new test-bpftrace.yml workflow Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> --------- Signed-off-by: 云微 <1067852565@qq.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> Co-authored-by: 云微 <1067852565@qq.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 9537760 commit 1d99584

3 files changed

Lines changed: 115 additions & 50 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Test bpftrace examples
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
schedule:
9+
- cron: '0 0 * * 0'
10+
jobs:
11+
build:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v3
15+
with:
16+
submodules: 'recursive'
17+
18+
- name: Install Rust toolchain
19+
uses: actions-rs/toolchain@v1
20+
with:
21+
toolchain: stable
22+
profile: minimal
23+
override: true
24+
25+
- name: test 37 uprobe-rust helloworld
26+
run: |
27+
cd src/37-uprobe-rust/helloworld
28+
cargo build
29+
# Verify the hello function symbol exists in debug build
30+
nm target/debug/helloworld | grep "_ZN10helloworld5hello" || (echo "Error: hello function symbol not found in debug build" && exit 1)
31+
echo "✓ helloworld debug build has correct hello symbol"
32+
33+
- name: test 37 uprobe-rust args
34+
run: |
35+
cd src/37-uprobe-rust/args
36+
cargo build
37+
# Verify the hello function symbol exists in debug build
38+
nm target/debug/helloworld | grep "_ZN10helloworld5hello" || (echo "Error: hello function symbol not found in debug build" && exit 1)
39+
echo "✓ args debug build has correct hello symbol"
40+
# Run the binary to ensure it works
41+
./target/debug/helloworld 1 2 3 | grep -q "Hello, world!" || (echo "Error: binary execution failed" && exit 1)
42+
echo "✓ args binary runs successfully"
43+
44+
- name: test 37 uprobe-rust bpftrace attach
45+
run: |
46+
cd src/37-uprobe-rust/args
47+
# Test that bpftrace can attach to the hello function with wildcard pattern
48+
# Run bpftrace with the binary and verify it can trace the function
49+
sudo timeout 3 bpftrace -e 'uprobe:target/debug/helloworld:_ZN10helloworld5hello* { printf("traced\n"); exit(); }' -c "./target/debug/helloworld 1" > /tmp/bpftrace_output.txt 2>&1 || true
50+
# Check if bpftrace successfully attached and traced the function
51+
if grep -q "traced" /tmp/bpftrace_output.txt && grep -q "Attaching 1 probe" /tmp/bpftrace_output.txt; then
52+
echo "✓ bpftrace successfully attached to hello function with wildcard pattern"
53+
else
54+
echo "Error: bpftrace failed to attach or trace the hello function"
55+
cat /tmp/bpftrace_output.txt
56+
exit 1
57+
fi

src/37-uprobe-rust/README.md

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ Attaching 1 probe...
6969
Function hello-world called
7070
```
7171

72-
## A strange phenomenon: multiple calls, getting parameters
72+
## Tracing function calls with multiple invocations and getting return values
7373

74-
For a more complex example, which includes multiple calls and parameter fetching:
74+
For a more complex example, which includes multiple calls and retrieving return values:
7575

7676
```rust
7777
use std::env;
@@ -99,18 +99,33 @@ fn main() {
9999
}
100100
```
101101

102-
We repeat a similar operation and notice a strange phenomenon:
102+
First, we need to build in debug mode because the `hello` function gets inlined in release mode and won't have its own symbol:
103103

104104
```console
105-
$ sudo bpftrace -e 'uprobe:args/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called\n"); }'
105+
$ cd args
106+
$ cargo build
107+
$ nm target/debug/helloworld | grep hello
108+
0000000000016250 t _ZN10helloworld4main17ha3594bca2af541f6E
109+
0000000000016540 t _ZN10helloworld5hello17h5f3a03dda56661e1E
110+
```
111+
112+
Note that in release mode (`cargo build --release`), only the `main` function symbol appears because `hello` gets inlined during optimization.
113+
114+
Now we can trace the `hello` function using its symbol. Since Rust mangles symbol names and includes a hash that changes with each compilation, we use a wildcard pattern to match any version of the `hello` function:
115+
116+
```console
117+
$ sudo bpftrace -e 'uprobe:target/debug/helloworld:_ZN10helloworld5hello* { printf("Function hello called\n"); }'
106118
Attaching 1 probe...
107-
Function hello-world called
119+
Function hello called
120+
Function hello called
121+
Function hello called
122+
Function hello called
108123
```
109124

110-
At this point we expect the hello function to run several times, but bpftrace only prints out one call:
125+
When we run the program with multiple arguments, bpftrace correctly catches all calls to the `hello` function:
111126

112127
```console
113-
$ args/target/release/helloworld 1 2 3 4
128+
$ ./target/debug/helloworld 1 2 3 4
114129
Hello, world! 1 in 5
115130
return value: 6
116131
Hello, world! 2 in 5
@@ -121,29 +136,18 @@ Hello, world! 4 in 5
121136
return value: 9
122137
```
123138

124-
And it appears that bpftrace cannot correctly get the parameter:
139+
We can also get the return value using Uretprobe. Again, we use a wildcard to match the symbol:
125140

126141
```console
127-
$ sudo bpftrace -e 'uprobe:args/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called %d\n"
128-
, arg0); }'
142+
$ sudo bpftrace -e 'uretprobe:target/debug/helloworld:_ZN10helloworld5hello* { printf("Function hello returned: %d\n", retval); }'
129143
Attaching 1 probe...
130-
Function hello-world called 63642464
144+
Function hello returned: 6
145+
Function hello returned: 7
146+
Function hello returned: 8
147+
Function hello returned: 9
131148
```
132149

133-
The Uretprobe did catch the return value of the first call:
134-
135-
```console
136-
$ sudo bpftrace -e 'uretprobe:args/tar
137-
get/release/helloworld:_ZN10helloworld4main17h2dce92
138-
cb81426b91E { printf("Function hello-world called %d
139-
\n", retval); }'
140-
Attaching 1 probe...
141-
Function hello-world called 6
142-
```
143-
144-
This may due to Rust does not have a stable ABI. Rust, as it has existed so far, has reserved the right to order those struct members any way it wants. So the compiled version of the callee might order the members exactly as above, while the compiled version of the programming calling into the library might think its actually laid out like this:
145-
146-
TODO: Further analysis (to be continued)
150+
Note: The wildcard pattern `_ZN10helloworld5hello*` matches the `hello` function symbol regardless of the hash suffix that Rust adds during compilation. You can use `nm target/debug/helloworld | grep hello` to see the exact symbol names if needed.
147151

148152
## References
149153

src/37-uprobe-rust/README.zh.md

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ Attaching 1 probe...
6767
Function hello-world called
6868
```
6969

70-
## 一个奇怪的现象:多次调用、获取参数
70+
## 追踪多次调用的函数并获取返回值
7171

72-
对于一个更复杂的例子,包含多次调用和获取参数
72+
对于一个更复杂的例子,包含多次调用并演示如何获取返回值
7373

7474
```rust
7575
use std::env;
@@ -97,18 +97,33 @@ fn main() {
9797
}
9898
```
9999

100-
我们再次进行类似的操作,会发现一个奇怪的现象
100+
首先,我们需要使用 debug 模式构建,因为 `hello` 函数在 release 模式下会被内联,不会有自己的符号
101101

102102
```console
103-
$ sudo bpftrace -e 'uprobe:args/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called\n"); }'
103+
$ cd args
104+
$ cargo build
105+
$ nm target/debug/helloworld | grep hello
106+
0000000000016250 t _ZN10helloworld4main17ha3594bca2af541f6E
107+
0000000000016540 t _ZN10helloworld5hello17h5f3a03dda56661e1E
108+
```
109+
110+
注意,在 release 模式(`cargo build --release`)下,只会出现 `main` 函数符号,因为 `hello` 在优化过程中被内联了。
111+
112+
现在我们可以使用符号来追踪 `hello` 函数。由于 Rust 会对符号名称进行混淆并加入每次编译都会变化的哈希值,我们使用通配符模式来匹配任何版本的 `hello` 函数:
113+
114+
```console
115+
$ sudo bpftrace -e 'uprobe:target/debug/helloworld:_ZN10helloworld5hello* { printf("Function hello called\n"); }'
104116
Attaching 1 probe...
105-
Function hello-world called
117+
Function hello called
118+
Function hello called
119+
Function hello called
120+
Function hello called
106121
```
107122

108-
这时候我们希望 hello 函数运行多次,但 bpftrace 中只输出了一次调用
123+
当我们使用多个参数运行程序时,bpftrace 正确地捕获了所有对 `hello` 函数的调用
109124

110125
```console
111-
$ args/target/release/helloworld 1 2 3 4
126+
$ ./target/debug/helloworld 1 2 3 4
112127
Hello, world! 1 in 5
113128
return value: 6
114129
Hello, world! 2 in 5
@@ -119,29 +134,18 @@ Hello, world! 4 in 5
119134
return value: 9
120135
```
121136

122-
而且看起来 bpftrace 并不能正确获取参数
137+
我们也可以使用 Uretprobe 来获取返回值。同样,我们使用通配符来匹配符号
123138

124139
```console
125-
$ sudo bpftrace -e 'uprobe:args/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called %d\n"
126-
, arg0); }'
140+
$ sudo bpftrace -e 'uretprobe:target/debug/helloworld:_ZN10helloworld5hello* { printf("Function hello returned: %d\n", retval); }'
127141
Attaching 1 probe...
128-
Function hello-world called 63642464
142+
Function hello returned: 6
143+
Function hello returned: 7
144+
Function hello returned: 8
145+
Function hello returned: 9
129146
```
130147

131-
Uretprobe 捕捉到了第一次调用的返回值:
132-
133-
```console
134-
$ sudo bpftrace -e 'uretprobe:args/tar
135-
get/release/helloworld:_ZN10helloworld4main17h2dce92
136-
cb81426b91E { printf("Function hello-world called %d
137-
\n", retval); }'
138-
Attaching 1 probe...
139-
Function hello-world called 6
140-
```
141-
142-
这可能是由于 Rust 没有稳定的 ABI。 Rust,正如它迄今为止所存在的那样,保留了以任何它想要的方式对这些结构成员进行排序的权利。 因此,被调用者的编译版本可能会完全按照上面的方式对成员进行排序,而调用库的编程的编译版本可能会认为它实际上是这样布局的:
143-
144-
TODO: 进一步分析(未完待续)
148+
注意:通配符模式 `_ZN10helloworld5hello*` 可以匹配 `hello` 函数符号,无论 Rust 在编译时添加了什么哈希后缀。如果需要,你可以使用 `nm target/debug/helloworld | grep hello` 来查看确切的符号名称。
145149

146150
## 参考资料
147151

0 commit comments

Comments
 (0)