Closed halajohn closed 3 hours ago
如果 ten_framework/.cargo/config.toml
存在, 内容如下:
[target.x86_64-unknown-linux-gnu]
rustflags = ["-Z", "sanitizer=address"]
[build]
target = "x86_64-unknown-linux-gnu"
同时, ten_rust/.cargo/config.toml
存在, 内容如下:
[target.x86_64-unknown-linux-gnu]
rustflags = ["-l", "ten_utils_static"]
测试结果显示, 在编译 ten_rust 时, rustflags 是合并了, 而不是 cargo 在找到当前 crate 下的 config.toml
中包含 rustflags 就不往上找了.
关于 config.toml
继承的原则:
如果同一个 key 在多个层级的 config.toml
中都有定义, 则会被 merge. 规则是:
$CARGO_HOME/config.toml
中的优先级最低.toml 不存在定义一个 值类型为 Object 的 key. 对于 Object 数据类型, 也是将 key 拼接, 平摊定义的, 如 {"net": {"http": {"a": "b"}}}, 对应是
[net.http] a = b
所以, 也适用前面两条规则.
The difference between GCC and Clang's ASan libraries has already been handled in TGN. When needed, the appropriate ASan shared library will be copied to the tests/standalone/
directory, and during testing, the presence of libasan.so
will be automatically detected, triggering the use of LD_PRELOAD
. This mechanism can and should be leveraged to avoid duplicating the same handling logic for the ASan shared library outside of the logic already managed by TGN.
./out/linux/x64/tests/standalone$ tree . -L 1
.
├── go_standalone_test
├── libasan.so <== here
├── ten_manager/
├── ten_runtime_smoke_test
└── ten_rust/
总结来说 (非 rust 场景):
参考: https://github.com/google/sanitizers/issues/1086.
对于 rust, 情况如下.
假设 crate type 的输出包括 staticlib, rlib, cdylib, 如下:
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
[lib]
name = "hello"
crate-type = ["staticlib", "rlib", "cdylib"]
test = true
使用 clang, 同时采用 static link:
config.toml 如下:
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C",
"linker=clang",
"-C",
"link-arg=-fuse-ld=lld",
"-Z",
"external-clangrt",
"-Z",
"sanitizer=address",
]
[build]
target = "x86_64-unknown-linux-gnu"
执行 cargo build, 输出的 staticlib, rlib, cdylib 中均不会包含 asan symbols. 如:
$ nm libhello.so | grep asan
0000000000053160 b ___asan_globals_registered
U __asan_init
0000000000011f20 t asan.module_ctor
0000000000011f50 t asan.module_dtor
U __asan_register_elf_globals
U __asan_unregister_elf_globals
U __asan_version_mismatch_check_v8
U __start_asan_globals
U __stop_asan_globals
rust 中的
rlib
就是 staticlib, 只是增加了 rust metadata. 所以也可以通过 nm 查询 symbols.
同时, 动态库中也不会包含对 asan runtime 的依赖. 如:
$ readelf -d libhello.so
Dynamic section at offset 0x4f7c8 contains 29 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]
0x000000000000000e (SONAME) Library soname: [libhello.so]
即默认情况下, clang asan 是在最终的 executable 中静态链接 asan runtime, 以此来提供 asan symbols.
使用 clang, 同时采用 dynamic link
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C",
"linker=clang",
"-C",
"link-arg=-fuse-ld=lld",
"-Z",
"external-clangrt",
"-Z",
"sanitizer=address",
"-C",
"link-args=-fsanitize=address -shared-libsan",
]
注意
- 只有
link-args=-fsanitize=address -shared-libsan
才生效. 使用link-args=-shared-libsan
或者link-arg=-shared-libsan
均不生效. 同时, 对于多个链接参数的情况下, 需要使用link-args
替代link-arg
, 否则会出错.- 这种情况下, 对于 staticlib 没区别.
- 开启 clang asan 动态依赖的方式, 是增加
-shared-libsan
链接参数, 写成-shared-libasan
也可以. 应该是 clang 历史遗留问题, 按照 clang --help 的输出, 应该是-shared-libsan
.
动态链接 asan runtime 生效的标志是, 在生成的 shared library 的 NEEDED 中会有对 libclang_rt.asan-x86_64.so
的直接依赖. 如:
$ readelf -d libhello.so
Dynamic section at offset 0x4f778 contains 29 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libclang_rt.asan-x86_64.so]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000e (SONAME) Library soname: [libhello.so]
使用 gcc, 同时采用 static link:
与 gcc 的默认行为 (gcc 默认情况下采用的是动态链接 asan runtime) 不同的是, 在 rust 中, 默认的配置下, gcc 依然是静态链接 asan runtime. 如:
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C",
"linker=gcc",
"-Z",
"external-clangrt",
"-Z",
"sanitizer=address",
]
这时, 生成的 shared library, 并没有显示对 libasan.so 有依赖:
$ readelf -d libhello.so
Dynamic section at offset 0x2d80 contains 29 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]
0x000000000000000e (SONAME) Library soname: [libhello.so]
使用 gcc, 同时采用 dynamic link:
首先, 无法像 clang 那种, 增加 -shared-libsan
, 因为 gcc ld 不具备该参数.
对于 gcc 设置 linker 参数, 是通过 -Wl 设置的. 所以, 在 gcc 下, 设置动态链接 asan runtime 的方式如下:
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C",
"linker=gcc",
"-Z",
"external-clangrt",
"-Z",
"sanitizer=address",
"-C",
"link-arg=-Wl,/usr/lib/gcc/x86_64-linux-gnu/13/libasan.so",
]
- 这种情况下, libasan.so 必须是绝对路径.
- 该链接参数, 不影响 staticlib 和 rlib.
可通过 readelf -d libhello.so
查看 NEEDED 信息.
然后, 在 gcc + lld 的情况下, 同时按照 clang 的方式开启 dynamic link asan runtime 的方式并 不 可用. 如:
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C",
"linker=gcc",
"-C",
"link-arg=-fuse-ld=lld",
"-Z",
"external-clangrt",
"-Z",
"sanitizer=address",
"-C",
"link-args=-faddress-sanitizer -shared-libsan",
]
默认情况下, rust executable 编译时, clang 和 gcc 默认都是 静态链接 asan runtime.
如果要开启 dynamic link asan runtime, 与上述配置方式相同. 如 clang:
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C",
"linker=clang",
"-C",
"link-arg=-fuse-ld=lld",
"-Z",
"external-clangrt",
"-Z",
"sanitizer=address",
"-C",
"link-args=-fsanitize=address -shared-libsan",
]
[build]
target = "x86_64-unknown-linux-gnu"
这种情况下, 可以配合 LD_PRELOAD 或者 设置 rpath 的方式在运行时查找 asan runtime.
如果 ten_manager 采用 dynamic link 的方式, ten_rust 采用 static link 的方式; ten_manager 以 rlib 的方式依赖 ten_rust, 测试可行.
rustc 支持 -C rpath
来设置 lib 或 executable 的 rpath, 但值只能是 on
, 并不是设置路径. 如果设置为 on
, 默认指向的是 toolchain 下的 lib, 如: Library runpath: [$ORIGIN/../../../../../../../../../../.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib]
.
Discourage the use of -fsanitize=address -libsan-static
是指不建议用 static link 的方式, 还是说 clang 默认是 static link 的方式, 不需要显式 -libsan-static
? 参考: https://bugs.llvm.org/show_bug.cgi?id=42177Static linking of executables is not supported
, 但测试下来并不影响. 参考: https://clang.llvm.org/docs/AddressSanitizer.html#id17TODO: 待验证.
应该不需要在 .cargo/config.toml
里面显示的指定 build
字段.
[build]
target = "x86_64-unknown-linux-gnu"
最终需要先有一个总结, 在 ten framework 内如何设置, 使得可以达成最初想要的目标.
enable_cargo_config_generated (需要该参数的目的见下述说明), 如果为 true, 则按第2步生成 cargo config; 如果为 false, 则在 rust.gni 中, 将第2步的 cargo config 转成环境变量.
在 TGN 中增加 template, 根据构建参数中的 os, cpu, type 来决定是否开启 rust asan. 同时, 如果开启 asan, 在 ten_framework 目录下生成 .cargo/config.toml 文件 (如果存在, 则更新内容). 如果不开启asan, 则删除该文件. 内容模板如下:
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C",
"linker=<compiler>",
"-C",
"link-arg=-fuse-ld=<linker>",
"-Z",
"external-clangrt",
"-Z",
"sanitizer=address",
"-C",
"link-args=<asan location>",
]
[build]
target = "<target-os>"
compiler
根据 构建参数--is_clang
来决定是 clang 或者 gcc.linker
根据 compiler 来决定, 如果是clang
, 则是lld
. 如果是 gcc, 则不包含这条 flag.asan location
根据 compiler 类型以及 liunx_asan_static_linking 参数判断. 具体值参考下述示例.target-os
根据 构建参数中的 cpu 和 os 来定, 按现有的 rust.gni 中的逻辑.
在构建 ten_rust_utils 时, 增加生成 cargo config 的依赖.
以执行 tgn gen linux x64 debug
为例.
构建参数中 is_clang
为 true
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C",
"linker=clang",
"-C",
"link-arg=-fuse-ld=lld",
"-Z",
"external-clangrt",
"-Z",
"sanitizer=address",
"-C",
"link-args=-fsanitize=address",
]
[build]
target = "x86_64-unknown-linux-gnu"
构建参数中 is_clang
为 false
[target.x86_64-unknown-linux-gnu]
rustflags = [
"-C",
"linker=gcc",
"-Z",
"external-clangrt",
"-Z",
"sanitizer=address",
"-l",
"asan"
]
[build]
target = "x86_64-unknown-linux-gnu"
- 对于 gcc, 不需要通过 -C link-arg 指定 asan 的路径, 由 gcc ld 自身控制. 获取 asan 路径也是通过
gcc -print-file-name=libasan.so
, 所以应该不存在 asan 路径不一致的问题. 同时, 需要指定-l asan
来指明使用的 external asan runtime.
因为 cargo 支持在 rust crate 源码目录之外触发编译, 通过 --manifest-path
参数指定编译源码路径即可. 如, 可以在 ten_framework 同级目录下执行如下命令来编译 ten_rust:
$ cargo build --tests --manifest-path=ten_framework/core/src/ten_rust/Cargo.toml
而 .cargo/config.toml
的作用机制是基于 cargo 命令的执行目录的. 也就是说, 这种场景下在 ten_framework 目录下创建上述 .cargo/config.toml 并不会生效.
所以可以通过 enable_cargo_config_generated 参数来决定是生成 .cargo/config.toml 文件, 还是直接增加 cargo 执行参数或者环境变量.
在 CI 构建时, 会 false. 这样也不会在源码目录下生成临时文件, 不用考虑执行结束后删除的问题.
在 TGN 中增加 template, 根据构建参数中的 os, cpu, type 来决定是否开启 rust asan.
这个应该要能直接使用 tgn 的 enable_sanitizer 即可, 不需要另外有新的逻辑判断. 也就是说, enable_sanitizer = true, 则开启 rust sanitizer, 反之则关闭.
同时, 如果开启 asan, 在 ten_framework 目录下生成 .cargo/config.toml 文件.
这个 .cargo/config.toml file 内, 一定需要 [build] 字段吗? 没有一定需要的话, 先拿掉这个字段.
[target.x86_64-unknown-linux-gnu] rustflags = [ "-C", "linker=clang", "-C", "link-arg=-fuse-ld=lld", "-Z", "external-clangrt", "-Z", "sanitizer=address", "-C", "link-args=-fsanitize=address -static-libsan", ]
以 clang 的 case 来说, clang 默认 的行为是 static asan, 这样有需要下 -static-libsan 的参数嘛? 如果不需要的话, 加个 comment 说明下即可.
[target.x86_64-unknown-linux-gnu] rustflags = [ "-C", "linker=gcc", "-Z", "external-clangrt", "-Z", "sanitizer=address", "-l", "asan", "-C", "link-arg=-Wl,-rpath=$ORIGIN:/usr/lib/gcc/x86_64-linux-gnu/13", ]
以 gcc 的 case 来说, tgn 已经处理了 asan.so 的 location 问题, tgn 会把它复制到 tests/standalone/ 下, 使用那个路径即可, 不然这边还会有一个散落在外的逻辑要判断 gcc 版本所搭配的 asan 路径, 所有这些 asan 版本跟路径, 都统一在 tgn 复制 asan.so 那边即可.
在 TGN 中增加 template, 根据构建参数中的 os, cpu, type 来决定是否开启 rust asan.
这个应该要能直接使用 tgn 的 enable_sanitizer 即可, 不需要另外有新的逻辑判断. 也就是说, enable_sanitizer = true, 则开启 rust sanitizer, 反之则关闭.
同时, 如果开启 asan, 在 ten_framework 目录下生成 .cargo/config.toml 文件.
这个 .cargo/config.toml file 内, 一定需要 [build] 字段吗? 没有一定需要的话, 先拿掉这个字段.
[target.x86_64-unknown-linux-gnu] rustflags = [ "-C", "linker=clang", "-C", "link-arg=-fuse-ld=lld", "-Z", "external-clangrt", "-Z", "sanitizer=address", "-C", "link-args=-fsanitize=address -static-libsan", ]
以 clang 的 case 来说, clang 默认 的行为是 static asan, 这样有需要下 -static-libsan 的参数嘛? 如果不需要的话, 加个 comment 说明下即可.
[target.x86_64-unknown-linux-gnu] rustflags = [ "-C", "linker=gcc", "-Z", "external-clangrt", "-Z", "sanitizer=address", "-l", "asan", "-C", "link-arg=-Wl,-rpath=$ORIGIN:/usr/lib/gcc/x86_64-linux-gnu/13", ]
以 gcc 的 case 来说, tgn 已经处理了 asan.so 的 location 问题, tgn 会把它复制到 tests/standalone/ 下, 使用那个路径即可, 不然这边还会有一个散落在外的逻辑要判断 gcc 版本所搭配的 asan 路径, 所有这些 asan 版本跟路径, 都统一在 tgn 复制 asan.so 那边即可.
背景
ten_framework 中会存在 C 和 Rust 依赖的问题, 依赖关系如下:
ten_rust
(Rust project) 会依赖ten_utils
(C project) 的静态库.ten_runtime
(C project) 会依赖ten_rust
(Rust project) 输出的 静态库.ten_utils 和 ten_runtime 是通过 TGN 工具链编译的, 通过 --enable_sanitizer 参数控制是否开启 asan. 同时, 对 libasan-rt 的依赖是 clang 或者 gcc 提供的.
而 ten_rust 和 ten_manager 作为 Rust 工程, 触发编译的场景有 3 个:
--enable_sanitizer
参数来判断是否开启 RUST 的 asan. 如果开启, 在 cargo build 的环境变量中添加需要的 RUSTFLAGS.在 Rust 中开启 ASAN 的方式有如下 3 种:
通过环境变量, RUSTFLAGS=-Zsanitizer=address.
通过在
.cargo/config.toml
中设置 rustflags. 如:在
build.rs
中通过设置 linker, 如println!("cargo::rustc-link-lib=asan"
). 但这种方式, 会有如下问题:rustc-link-lib
是作用于 rustc 的 ld 的, 意味着如果不指定rustc-link-search
的话, 找到的是系统下的 asan, 而不是 rustc toolchain 下的 asan (一般在~/.rustup/toolchains/<target_cpu>/lib/rustlib/<target_cpu>/lib/librustc-nightly_rt.asan.a
). 相当于是依赖外部的libasan-rt
, 对于 rust 来说, 需要搭配rustflags = ["-Z", "external-clangrt"]
使用.开启
asan.同时, 在 build.rs 中, 通过如下方式设置均不生效:
env::set_var("RUSTFLAGS", "-Zsanitizer=address")
println!("cargo:rustc-env=RUSTFLAGS=-Zsanitizer=address")
println!("cargo:rustc-flags=-Zsanitizer=address")
build.rs
的作用是 cargo 会将 build.rs 中的输出(即println!
) 保存到target/deps/<crate>/build/output
文件中, 作为 rustc 编译器的参数设置. 所以, 在build.rs
中设置环境变量, 并不会作用到 rustc 的执行链中.cargo 中查找
RUSTFLAGS
的顺序(参考 cargo 源码):CARGO_ENCODED_RUSTFLAGS
和RUSTFLAGS
系统环境变量..cargo/config.toml
中查找. 查找的优先级顺序为:target.\*.rustflags
target.cfg(..).rustflags
host.\*.rustflags
, 但依赖开启-Zhost-config
.build.rustflags
在开启 asan 时, 执行 cargo 相关的命令, 必须加上
--target
命令, 或者执行命令时, 带上-Zhost-config
参数.预期达到的目标 在使用 TGN 编译 ten_utils, 并且开启 ASAN 时, 依赖 ten_utils 的 ten_rust 和 ten_manager 可以同时满足以下开发场景:
实现方式
如上所述, 在 vscode 中开发时, 影响因素有两个:
--target
参数, 或者说, 指定默认的 target 参数.基于此, 最佳的实现方式是, 在使用 TGN 编译 ten_utils 时, 如果开启了 ASAN, 在 ten_framework 目录下生成
.cargo/config.toml
, 内容如下:具体 target 的值, 根据 os 和 cpu 判断.
在下次编译, 删除该配置.
cargo 中
config.toml
的作用范围如下:假如在 /projects/foo/bar/baz 目录下执行编译,
config.toml
的查找路径为:/projects/foo/bar/baz/.cargo/config.toml
/projects/foo/bar/.cargo/config.toml
/projects/foo/.cargo/config.toml
/projects/.cargo/config.toml
/.cargo/config.toml
$CARGO_HOME/config.toml
, 默认是: Windows:%USERPROFILE%\.cargo\config.toml
Unix:$HOME/.cargo/config.toml
所以, 在
ten_framework/.cargo/config.toml
中设置, 可以满足上述所有场景, 同时对开发者无感知.