Rust 交叉编译问题记录
cargo build 命令和 build.rs 脚本的关系?
a. 位于项目根目录下
b. 如果存在 cargo build 命令执前自动编译build.rs 脚本并执行
c. 脚本可以使用 println! 的方式跟 Cargo 进行通信:通信内容是以 cargo: 开头的格式化字符串
d. 如果需要传递参数则需要使用环境变量进行传递,例如: MY_CUSTOM_ARG=42 cargo build
e. 编译脚本中所有的 println 打印都将输出到 ~/target/debug/build/test-1-ba40594839af4be9/output
f. 作用:
- 构建 C 依赖库
- 在操作系统中寻找指定的 C 依赖库
- 根据某个说明描述文件生成一个 Rust 模块
- 执行一些平台相关的配置
build.rs 中 println! 为什么都输入到了 target/debug/build/<pkg>/output
文件?
在项目构建过程中,build.rs 脚本使用 println 的方式跟 Cargo 进行通信:以 cargo: 开头的格式化字符串将会被传递给 Cargo,其他则会忽略。构建脚本打印输出的所有内容将保存在文件 target/debug/build/
rust 中如何引用 C 函数?
对于库中的函数,如果需要在rust 中使用,首先需要在 rust 中对函数的参数类型,返回类型进行重新定义,然后需要在extern "C" { } 块中对C/C++的函数以rust风格进行重新申明,申明完成后就可以在rust 项目中使用此函数了,在函数使用时由于C/C++的函数来自外部属于不安全范围,所以在使用时必须要将C函数放在 unsafe {} 块中,一般的做法是将C函数使用unsafe {}重新封装为 rust 函数。
例如在rust 中引用C库函数 int get_sum(int a, int b):
pub type Int32Rs = i32;
extern "C" {
pub fn get_sum(a:Int32Rs, b:Int32Rs) -> Int32Rs;
}
fn main() {
unsafe { let sum = get_sum(100, 20);
println!("sum {}", sum);
}
}
rust 项目中如何将存在的 C 文件编译成库?
如果需要在rust项目中编译C/C++的文件,则必须要使用到 build.rs 脚本,在然后在脚本中使用 cc::Build::new()来编译C文件,并打包成对应的库。
Cargo.toml 文件中添加一下内容:
[package]
# ...
build = "build.rs"
# 为 build.rs 编译脚本添加依赖
[build-dependencies]
# ...
cc = "1.0.52"
build.rs 脚本:
use cc::Build;
fn main() {
// 指明需要连接的外表库
println!("cargo:rustc-link-lib=m");
// 这里无需显示指明hello库,因为c源码在项目中被编译会自动添加到rust执行文件的编译链接中
// println!("cargo:rustc-link-lib=static=hello");
// 以下代码告诉 Cargo ,一旦指定的文件 `src/hello.c` 发生了改变,就重新运行当前的构建脚本
println!("cargo:rerun-if-changed=src/hello.c");
// 使用 `cc` 来构建 C 文件,然后进行静态链接,打包成 libhello.a
Build::new()
.opt_level(2)
.file("src/hello.c")
.try_compile("hello").unwrap();
}
rust 文件如何与 C 库一起编译?
对于 rust 项目中存在C文件,并且在项目构建时打包成C库被使用的情况,则在 build.rs 中无需使用 rustc-link-lib 显示指明链接库,因为C源码在项目中被编译会自动添加到rust执行文件的编译链接中。
对于引入的外部 C/C++ 库则需要在 build.rs 中使用 rustc-link-lib 显示的指明需要连接的库名称。
例如链接 winmm 库。
println!("cargo:rustc-link-lib=static=winmm");
rust 和 C 项目混合编译代码大小优化?
优化 rust 和 C的混合项目需要从三部分进行优化:
- 首先需要对C部分设置编译优化等级
- 设置rust 编译时的优化等级
- 去除可执行文件的符号表,这样转换成 Hex 文件的体积才会真正降下来
C语言编译优化
在build.rs 中 cc Build 使用opt_level() 设置优化等级。
cc::Build::new()
.opt_level(2)
.file("src/hello.c")
.try_compile("hello").unwrap();
Rust语言编译优化
在 Cargo.toml 中设置 release 时的代码优化情况。
[profile.release]
codegen-units = 1 # better optimizations
lto = true # better optimizations
编译时使用使用 --release 选项则会默认使用以上设置进行优化。
cargo build --package freertos-rust-examples --example stm32-cortex-m3 --target thumbv7m-none-eabi --release
去除符号表
在编译出ELF可移植性文件后对可执行文件去除符号表后生成最终hex文件。
cargo strip --target thumbv7m-none-eabi --example stm32-cortex-m3 --release
cargo build --package freertos-rust-examples --example stm32-cortex-m3 --target thumbv7m-none-eabi --release
rust 如何生成 rlib 库?
如果 cargo.toml 文件中已经定义了 [lib]
部分,或者如果项目中有一个 src/lib.rs 文件,cargo 会默认将项目作为库 crate 来构建,并生成 .rlib 文件。如果没有 src/lib.rs,则可以在 [lib]
项下定义程序文件入口,然后通过 path 申明。如果有多个源文件,则可以在 src/lib.rs 或指明的入口文件中做配置来决定启用哪些模块。这里的模块使用 mod 关键字 + src下的文件/文件夹名称组成,例如:mod delays;
[lib]
name = "test" # name
path = "src/lib.rs" # src file
lib.rs 文件中
#[cfg(feature = "time")]
mod delays;
Rust 和C代码大小对比?
Tokio v1.5.0 中tokio模块(高性能异步运行时框架)的代码(NBNC)有36,473行,使用tokei工具进行统计的结果。
使用 cargo build --release 编译后删除 strip 和 .rustc 段后剩余大小:为 1341680 大小为1.3M 左右。表格中C采用-O2优化等级。
.gcc_except_table
该段和 try-catch-finally 控制流块的异常处理相关,用于处理异常,包括栈回溯时堆栈展开,清理异常处理过程中的资源。
.dynsym 符号表
这一节存储的是关于动态链接的符号表,每一个表项占24字节,tokio总共有2099个动态符号,相比较于C,Rust会存在更多的库函数、数据结构和异常处理等。
Rust dynsym符号表:
Symbol table '.dynsym' contains 2099 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZN3std3net3tcp9TcpStream
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZN3std2fs8DirEntry9file_
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZN4core3fmt3num53_$LT$im
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZN4core3fmt3num53_$LT$im
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZN51_$LT$$RF$std..fs..Fi
6: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND _ZN3std10std_detect6detec
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZN3std3sys4unix6thread6T
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZN4core3fmt3num52_$LT$im
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pipe2@GLIBC_2.9 (2)
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZN91_$LT$std..io..cursor
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZN74_$LT$std..fs..DirEnt
...
C dynsym 符号表
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __isoc99_fscanf@GLIBC_2.7 (3)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND clock_gettime@GLIBC_2.17 (4)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fclose@GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __assert_fail@GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND feof@GLIBC_2.2.5 (2)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcpy@GLIBC_2.14 (5)
...
.dynstr
dynstr段用来存储dysym符号表中的符号,本次测试使用的是 rustc 1.48.0,组名规则为legacy,类似于C++的组名规则,符号名中间会加上crate、mod、struct等信息,想比于C语言的组名要大很多。
//Rust 字符串表
String dump of section '.dynstr':
[ 1] libstd-f14aca24435a5414.so
[ 1c] _ITM_deregisterTMCloneTable
[ 38] __gmon_start__
[ 47] _Jv_RegisterClasses
[ 5b] _ITM_registerTMCloneTable
[ 75] _ZN58_$LT$std..io..error..Error$u20$as$u20$core..fmt..Debug$GT$3fmt17heb882e9e5723aaeaE
[ cd] _ZN244_$LT$std..error..$LT$impl$u20$core..convert..From$LT$alloc..string..String$GT$$u20$for$u20$alloc..boxed..Box$LT$dyn$u20$std..error..Error$u2b$core..marker..Send$u2b$core..marker..Sync$GT$$GT$..from..StringError$u20$as$u20$core..fmt..Display$GT$3fmt17h0381a183d16c0bdbE
[ 1e0] _ZN3std2rt19lang_start_internal17h73711f37ecfcb277E
C 字符串表
String dump of section '.dynstr':
[ 1] libc.so.6
[ b] fopen
[ 11] puts
[ 16] __assert_fail
[ 24] printf
[ 2b] feof
[ 30] __isoc99_fscanf
[ 40] memcpy
.text段
.text段大概也是 C的3倍左右大小,通过汇编指令打开查看,Rust比C多出点在 异常处理、调用栈、析构函数、泛型实例化、Vec,Result,Box,String,Map等结构的处理、运行时边界校验等,这些也是保证rust 可用性和内存安全性的保证。
总结
Rust 项目代码相对于C的代码体积膨胀约为 3倍左右,增加的代码主要是为rust 的内存安全、灵活和可用性提供保证,在非资源受限上,相对来说还是可以接受的。