ci: support Rust 1.97 and modernize workflows (#210)

* Modernize workflow actions and permissions

* Test the fentry tutorial in CI

* Support Rust 1.97 symbol mangling

* Limit documentation writes to main

* Harden Rust symbol resolution

---------

Co-authored-by: LinuxDev9002 <i@littlefisher.me>
This commit is contained in:
云微
2026-07-14 03:58:41 -07:00
committed by GitHub
parent 3a722c03d5
commit 39e3f59119
7 changed files with 79 additions and 63 deletions

View File

@@ -2,6 +2,7 @@ name: Auto Generate Documentation
on: on:
push: push:
branches: [main]
paths: paths:
- 'src/**/README.md' - 'src/**/README.md'
- 'src/**/README.zh.md' - 'src/**/README.zh.md'
@@ -10,19 +11,22 @@ on:
- 'scripts/*.template' - 'scripts/*.template'
workflow_dispatch: workflow_dispatch:
permissions:
contents: write
jobs: jobs:
generate-docs: generate-docs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v6
with: with:
python-version: '3.x' python-version: '3.x'

View File

@@ -7,27 +7,27 @@ on:
branches: [ "main" ] branches: [ "main" ]
schedule: schedule:
- cron: '0 0 * * 0' - cron: '0 0 * * 0'
permissions:
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v7
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: Install Rust toolchain - name: Install Rust toolchain
uses: actions-rs/toolchain@v1 uses: dtolnay/rust-toolchain@1.97.0
with:
toolchain: stable
profile: minimal
override: true
- name: test 37 uprobe-rust helloworld - name: test 37 uprobe-rust helloworld
run: | run: |
cd src/37-uprobe-rust/helloworld cd src/37-uprobe-rust/helloworld
cargo build cargo build
# Verify the hello function symbol exists in debug build # Verify the hello function symbol exists in debug build
nm target/debug/helloworld | grep "_ZN10helloworld5hello" || (echo "Error: hello function symbol not found in debug build" && exit 1) nm -C target/debug/helloworld | grep -F "helloworld::hello" || (echo "Error: hello function symbol not found in debug build" && exit 1)
echo "✓ helloworld debug build has correct hello symbol" echo "✓ helloworld debug build has correct hello symbol"
- name: test 37 uprobe-rust args - name: test 37 uprobe-rust args
@@ -35,7 +35,7 @@ jobs:
cd src/37-uprobe-rust/args cd src/37-uprobe-rust/args
cargo build cargo build
# Verify the hello function symbol exists in debug build # Verify the hello function symbol exists in debug build
nm target/debug/helloworld | grep "_ZN10helloworld5hello" || (echo "Error: hello function symbol not found in debug build" && exit 1) nm -C target/debug/helloworld | grep -F "helloworld::hello" || (echo "Error: hello function symbol not found in debug build" && exit 1)
echo "✓ args debug build has correct hello symbol" echo "✓ args debug build has correct hello symbol"
# Run the binary to ensure it works # Run the binary to ensure it works
./target/debug/helloworld 1 2 3 | grep -q "Hello, world!" || (echo "Error: binary execution failed" && exit 1) ./target/debug/helloworld 1 2 3 | grep -q "Hello, world!" || (echo "Error: binary execution failed" && exit 1)
@@ -44,12 +44,15 @@ jobs:
- name: test 37 uprobe-rust bpftrace attach - name: test 37 uprobe-rust bpftrace attach
run: | run: |
cd src/37-uprobe-rust/args cd src/37-uprobe-rust/args
# Test that bpftrace can attach to the hello function with wildcard pattern # Rust 1.97 uses v0 mangling by default, so resolve the raw symbol dynamically.
# Run bpftrace with the binary and verify it can trace the function hello_address=$(nm -C target/debug/helloworld | awk '$2 ~ /^[tT]$/ && $3 ~ /^helloworld::hello(::h[[:xdigit:]]+)?$/ && !matched { found = $1; matched = 1 } END { if (matched) print found }')
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 test -n "$hello_address" || (echo "Error: demangled hello function symbol not found" && exit 1)
hello_symbol=$(nm target/debug/helloworld | awk -v address="$hello_address" '$1 == address && $2 ~ /^[tT]$/ && !matched { found = $3; matched = 1 } END { if (matched) print found }')
test -n "$hello_symbol" || (echo "Error: raw hello function symbol not found" && exit 1)
sudo timeout 3 bpftrace -e "uprobe:target/debug/helloworld:${hello_symbol} { printf(\"traced\\n\"); exit(); }" -c "./target/debug/helloworld 1" > /tmp/bpftrace_output.txt 2>&1 || true
# Check if bpftrace successfully attached and traced the function # Check if bpftrace successfully attached and traced the function
if grep -q "traced" /tmp/bpftrace_output.txt && grep -q "Attaching 1 probe" /tmp/bpftrace_output.txt; then if grep -q "traced" /tmp/bpftrace_output.txt && grep -q "Attaching 1 probe" /tmp/bpftrace_output.txt; then
echo "✓ bpftrace successfully attached to hello function with wildcard pattern" echo "✓ bpftrace successfully attached to hello function"
else else
echo "Error: bpftrace failed to attach or trace the hello function" echo "Error: bpftrace failed to attach or trace the hello function"
cat /tmp/bpftrace_output.txt cat /tmp/bpftrace_output.txt

View File

@@ -7,11 +7,15 @@ on:
branches: [ "main" ] branches: [ "main" ]
schedule: schedule:
- cron: '0 0 * * 0' - cron: '0 0 * * 0'
permissions:
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v7
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: install deps - name: install deps
@@ -30,8 +34,8 @@ jobs:
sudo timeout -s 2 3 ./ecli run src/2-kprobe-unlink/package.json || if [ $? = 124 ]; then exit 0; else exit $?; fi sudo timeout -s 2 3 ./ecli run src/2-kprobe-unlink/package.json || if [ $? = 124 ]; then exit 0; else exit $?; fi
- name: test 3 fentry - name: test 3 fentry
run: | run: |
./ecc src/2-kprobe-unlink/kprobe-link.bpf.c ./ecc src/3-fentry-unlink/fentry-link.bpf.c
sudo timeout -s 2 3 ./ecli run src/2-kprobe-unlink/package.json || if [ $? = 124 ]; then exit 0; else exit $?; fi sudo timeout -s 2 3 ./ecli run src/3-fentry-unlink/package.json || if [ $? = 124 ]; then exit 0; else exit $?; fi
- name: test 4 opensnoop - name: test 4 opensnoop
run: | run: |
./ecc src/4-opensnoop/opensnoop.bpf.c ./ecc src/4-opensnoop/opensnoop.bpf.c

View File

@@ -7,11 +7,15 @@ on:
branches: [ "main" ] branches: [ "main" ]
schedule: schedule:
- cron: '0 0 * * 0' - cron: '0 0 * * 0'
permissions:
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v7
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: install deps - name: install deps

View File

@@ -7,18 +7,21 @@ on:
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
permissions:
contents: read
jobs: jobs:
trigger: trigger:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout website repo - name: Checkout website repo
uses: actions/checkout@v3 uses: actions/checkout@v7
with: with:
repository: ${{ github.repository_owner }}/eunomia.dev repository: ${{ github.repository_owner }}/eunomia.dev
ref: main ref: main
path: ./. path: ./.
- uses: actions/setup-python@v4 - uses: actions/setup-python@v6
with: with:
python-version: 3.x python-version: 3.x
@@ -32,7 +35,7 @@ jobs:
- name: Trigger sync workflow - name: Trigger sync workflow
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: peter-evans/repository-dispatch@v2 uses: peter-evans/repository-dispatch@v4
with: with:
token: ${{ secrets.PAT }} token: ${{ secrets.PAT }}
repository: ${{ github.repository_owner }}/eunomia.dev repository: ${{ github.repository_owner }}/eunomia.dev

View File

@@ -43,27 +43,23 @@ Build and try to get the symbol:
```console ```console
$ cd helloworld $ cd helloworld
$ cargo build $ cargo build
$ nm helloworld/target/release/helloworld | grep hello $ nm -C target/debug/helloworld | grep 'helloworld::main'
0000000000008940 t _ZN10helloworld4main17h2dce92cb81426b91E 0000000000013ec0 t helloworld::main
``` ```
We find that the corresponding symbol has been converted to `_ZN10helloworld4main17h2dce92cb81426b91E`. This is because rustc uses [Symbol name mangling](https://en.wikipedia.org/wiki/Name_mangling) to encode a unique name for the symbols used in the code generation process. The encoded name will be used by the linker to associate the name with the content it points to. The -C symbol-mangling-version option can be used to control the handling of symbol names. Rustc uses [symbol name mangling](https://en.wikipedia.org/wiki/Name_mangling) to encode unique linker names. Rust 1.97 uses v0 mangling by default, while `nm -C` prints the stable demangled name. Resolve the raw symbol through its address before passing it to bpftrace:
We can use the [`rustfilt`](https://github.com/luser/rustfilt) tool to parse and obtain the corresponding symbol. This tool can be installed with `cargo install rustfilt`:
```console ```console
$ cargo install rustfilt $ main_address=$(nm -C target/debug/helloworld | awk '$2 ~ /^[tT]$/ && $3 ~ /^helloworld::main(::h[[:xdigit:]]+)?$/ && !matched { found = $1; matched = 1 } END { if (matched) print found }')
$ nm helloworld/target/release/helloworld > name.txt $ main_symbol=$(nm target/debug/helloworld | awk -v address="$main_address" '$1 == address && $2 ~ /^[tT]$/ && !matched { found = $3; matched = 1 } END { if (matched) print found }')
$ rustfilt _ZN10helloworld4main17h2dce92cb81426b91E $ echo "$main_symbol"
helloworld::main _RNvCsiXtUZV4ocyZ_10helloworld4main
$ rustfilt -i name.txt | grep hello
0000000000008b60 t helloworld::main
``` ```
Next we can try to use bpftrace to trace the corresponding function: The `-C symbol-mangling-version` rustc option can select a mangling scheme, but resolving the current binary avoids depending on a particular scheme. We can now trace `main`:
```console ```console
$ sudo bpftrace -e 'uprobe:helloworld/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called\n"); }' $ sudo bpftrace -e "uprobe:target/debug/helloworld:${main_symbol} { printf(\"Function hello-world called\n\"); }"
Attaching 1 probe... Attaching 1 probe...
Function hello-world called Function hello-world called
``` ```
@@ -103,17 +99,20 @@ First, we need to build in debug mode because the `hello` function gets inlined
```console ```console
$ cd args $ cd args
$ cargo build $ cargo build
$ nm target/debug/helloworld | grep hello $ nm -C target/debug/helloworld | grep 'helloworld::hello'
0000000000016250 t _ZN10helloworld4main17ha3594bca2af541f6E 0000000000016f90 t helloworld::hello
0000000000016540 t _ZN10helloworld5hello17h5f3a03dda56661e1E
``` ```
Note that in release mode (`cargo build --release`), only the `main` function symbol appears because `hello` gets inlined during optimization. Note that in release mode (`cargo build --release`), only the `main` function symbol appears because `hello` gets inlined during optimization.
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: Rust 1.97 uses v0 symbol mangling by default. bpftrace needs the raw symbol, so resolve it from the address of the stable demangled name:
```console ```console
$ sudo bpftrace -e 'uprobe:target/debug/helloworld:_ZN10helloworld5hello* { printf("Function hello called\n"); }' $ hello_address=$(nm -C target/debug/helloworld | awk '$2 ~ /^[tT]$/ && $3 ~ /^helloworld::hello(::h[[:xdigit:]]+)?$/ && !matched { found = $1; matched = 1 } END { if (matched) print found }')
$ hello_symbol=$(nm target/debug/helloworld | awk -v address="$hello_address" '$1 == address && $2 ~ /^[tT]$/ && !matched { found = $3; matched = 1 } END { if (matched) print found }')
$ echo "$hello_symbol"
_RNvCsiXtUZV4ocyZ_10helloworld5hello
$ sudo bpftrace -e "uprobe:target/debug/helloworld:${hello_symbol} { printf(\"Function hello called\n\"); }"
Attaching 1 probe... Attaching 1 probe...
Function hello called Function hello called
Function hello called Function hello called
@@ -135,10 +134,10 @@ Hello, world! 4 in 5
return value: 9 return value: 9
``` ```
We can also get the return value using Uretprobe. Again, we use a wildcard to match the symbol: We can also get the return value using Uretprobe and the same resolved symbol:
```console ```console
$ sudo bpftrace -e 'uretprobe:target/debug/helloworld:_ZN10helloworld5hello* { printf("Function hello returned: %d\n", retval); }' $ sudo bpftrace -e "uretprobe:target/debug/helloworld:${hello_symbol} { printf(\"Function hello returned: %d\n\", retval); }"
Attaching 1 probe... Attaching 1 probe...
Function hello returned: 6 Function hello returned: 6
Function hello returned: 7 Function hello returned: 7
@@ -146,7 +145,7 @@ Function hello returned: 8
Function hello returned: 9 Function hello returned: 9
``` ```
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. Resolving the raw symbol through its demangled name works with both legacy and v0 Rust mangling.
## References ## References

View File

@@ -41,27 +41,23 @@ fn main() {
```console ```console
$ cd helloworld $ cd helloworld
$ cargo build $ cargo build
$ nm helloworld/target/release/helloworld | grep hello $ nm -C target/debug/helloworld | grep 'helloworld::main'
0000000000008940 t _ZN10helloworld4main17h2dce92cb81426b91E 0000000000013ec0 t helloworld::main
``` ```
我们会发现,对应的符号被转换为了 `_ZN10helloworld4main17h2dce92cb81426b91E`,这是因为 rustc 使用 [Symbol name mangling](https://en.wikipedia.org/wiki/Name_mangling) 来为代码生成过程中使用的符号编码一个唯一的名称。编码后的名称会被链接器用于将名称与所指向的内容关联起来。可以使用 -C symbol-mangling-version 选项来控制符号名称的处理方法。 rustc 使用 [符号名称修饰](https://en.wikipedia.org/wiki/Name_mangling) 为链接器编码唯一的符号名称。Rust 1.97 默认使用 v0 名称修饰,而 `nm -C` 会显示稳定的反修饰名称。将符号交给 bpftrace 之前,可以先通过地址解析原始符号:
我们可以使用 [`rustfilt`](https://github.com/luser/rustfilt) 工具来解析和获取对应的符号。这个工具可以通过 `cargo install rustfilt` 安装:
```console ```console
$ cargo install rustfilt $ main_address=$(nm -C target/debug/helloworld | awk '$2 ~ /^[tT]$/ && $3 ~ /^helloworld::main(::h[[:xdigit:]]+)?$/ && !matched { found = $1; matched = 1 } END { if (matched) print found }')
$ nm helloworld/target/release/helloworld > name.txt $ main_symbol=$(nm target/debug/helloworld | awk -v address="$main_address" '$1 == address && $2 ~ /^[tT]$/ && !matched { found = $3; matched = 1 } END { if (matched) print found }')
$ rustfilt _ZN10helloworld4main17h2dce92cb81426b91E $ echo "$main_symbol"
helloworld::main _RNvCsiXtUZV4ocyZ_10helloworld4main
$ rustfilt -i name.txt | grep hello
0000000000008b60 t helloworld::main
``` ```
接下来我们可以尝试使用 bpftrace 跟踪对应的函数: `-C symbol-mangling-version` rustc 选项可以选择名称修饰方案,但从当前二进制解析可以避免依赖某一种方案。现在可以追踪 `main`:
```console ```console
$ sudo bpftrace -e 'uprobe:helloworld/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called\n"); }' $ sudo bpftrace -e "uprobe:target/debug/helloworld:${main_symbol} { printf(\"Function hello-world called\n\"); }"
Attaching 1 probe... Attaching 1 probe...
Function hello-world called Function hello-world called
``` ```
@@ -101,17 +97,20 @@ fn main() {
```console ```console
$ cd args $ cd args
$ cargo build $ cargo build
$ nm target/debug/helloworld | grep hello $ nm -C target/debug/helloworld | grep 'helloworld::hello'
0000000000016250 t _ZN10helloworld4main17ha3594bca2af541f6E 0000000000016f90 t helloworld::hello
0000000000016540 t _ZN10helloworld5hello17h5f3a03dda56661e1E
``` ```
注意,在 release 模式(`cargo build --release`)下,只会出现 `main` 函数符号,因为 `hello` 在优化过程中被内联了。 注意,在 release 模式(`cargo build --release`)下,只会出现 `main` 函数符号,因为 `hello` 在优化过程中被内联了。
现在我们可以使用符号来追踪 `hello` 函数。由于 Rust 会对符号名称进行混淆并加入每次编译都会变化的哈希值,我们使用通配符模式来匹配任何版本的 `hello` 函数: Rust 1.97 默认使用 v0 符号名称修饰。bpftrace 需要原始符号,因此先通过稳定的反修饰名称找到地址,再解析对应的原始符号:
```console ```console
$ sudo bpftrace -e 'uprobe:target/debug/helloworld:_ZN10helloworld5hello* { printf("Function hello called\n"); }' $ hello_address=$(nm -C target/debug/helloworld | awk '$2 ~ /^[tT]$/ && $3 ~ /^helloworld::hello(::h[[:xdigit:]]+)?$/ && !matched { found = $1; matched = 1 } END { if (matched) print found }')
$ hello_symbol=$(nm target/debug/helloworld | awk -v address="$hello_address" '$1 == address && $2 ~ /^[tT]$/ && !matched { found = $3; matched = 1 } END { if (matched) print found }')
$ echo "$hello_symbol"
_RNvCsiXtUZV4ocyZ_10helloworld5hello
$ sudo bpftrace -e "uprobe:target/debug/helloworld:${hello_symbol} { printf(\"Function hello called\n\"); }"
Attaching 1 probe... Attaching 1 probe...
Function hello called Function hello called
Function hello called Function hello called
@@ -133,10 +132,10 @@ Hello, world! 4 in 5
return value: 9 return value: 9
``` ```
我们也可以使用 Uretprobe 来获取返回值。同样,我们使用通配符来匹配符号: 我们也可以使用 Uretprobe 和同一个已解析的符号来获取返回值:
```console ```console
$ sudo bpftrace -e 'uretprobe:target/debug/helloworld:_ZN10helloworld5hello* { printf("Function hello returned: %d\n", retval); }' $ sudo bpftrace -e "uretprobe:target/debug/helloworld:${hello_symbol} { printf(\"Function hello returned: %d\n\", retval); }"
Attaching 1 probe... Attaching 1 probe...
Function hello returned: 6 Function hello returned: 6
Function hello returned: 7 Function hello returned: 7
@@ -144,7 +143,7 @@ Function hello returned: 8
Function hello returned: 9 Function hello returned: 9
``` ```
注意:通配符模式 `_ZN10helloworld5hello*` 可以匹配 `hello` 函数符号,无论 Rust 在编译时添加了什么哈希后缀。如果需要,你可以使用 `nm target/debug/helloworld | grep hello` 来查看确切的符号名称。 通过反修饰名称解析原始符号的方式同时适用于 Rust 的 legacy 和 v0 名称修饰方案。
## 参考资料 ## 参考资料