Posts Rust 笔记
Post
Cancel

Rust 笔记

1. 设置代理

Windows 命令行代理设置

1
2
3
4
5
set http_proxy=http://example.com:80
set https_proxy=http://example.com:80
:: 查看
set http_proxy
set https_proxy

Windows PowerShell 代理设置

1
2
3
4
5
$env:http_proxy="http://example.com:80"
$env:https_proxy="http://example.com:80"
# 查看
ls env:http_proxy
ls env:https_proxy

Linux 终端代理设置:

1
2
3
4
5
export http_proxy=example.com:80
export https_proxy=example.com:80
# 查看
echo $http_proxy
echo $https_proxy

也可以修改 cargo 的配置文件 ~/.cargo/config

1
2
3
4
5
[http]
proxy = "http://example.com:80"

[https]
proxy = "http://example.com:80"

2. 安装

2.1 从源码编译安装

以 CORTEX-A53 大端 CPU 为例:

  • CPU: CORTEX-A53(ARMv8)
  • toolchain: armeb-unknown-linux-gnueabi-
  • endian: big
  • 用户态程序是 32 位的

步骤:

(1) 添加目标板文件

仿照 src/librustc_target/spec/arm_unknown_linux_gnueabi.rs 创建目标板的描述文件(修改 max_atomic_width,target_endian,data_layout,features)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ cat src/librustc_target/spec/armeb_unknown_linux_gnueabi.rs
use crate::spec::{LinkerFlavor, Target, TargetOptions, TargetResult};

pub fn target() -> TargetResult {
    let mut base = super::linux_base::opts();
    base.max_atomic_width = Some(32);
    Ok(Target {
        llvm_target: "armeb-unknown-linux-gnueabi".to_string(),
        target_endian: "big".to_string(),
        target_pointer_width: "32".to_string(),
        target_c_int_width: "32".to_string(),
        data_layout: "E-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64".to_string(),
        arch: "arm".to_string(),
        target_os: "linux".to_string(),
        target_env: "gnu".to_string(),
        target_vendor: "unknown".to_string(),
        linker_flavor: LinkerFlavor::Gcc,

        options: TargetOptions {
            features: "+strict-align,+v8".to_string(),
            abi_blacklist: super::arm_base::abi_blacklist(),
            target_mcount: "\u{1}__gnu_mcount_nc".to_string(),
            ..base
        },
    })
}

(2)添加 target triple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git diff src/librustc_target/spec/mod.rs
diff --git a/src/librustc_target/spec/mod.rs b/src/librustc_target/spec/mod.rs
index 8f3097a..bd4cb84 100644
--- a/src/librustc_target/spec/mod.rs
+++ b/src/librustc_target/spec/mod.rs
@@ -348,6 +348,9 @@ supported_targets! {
     ("sparc-unknown-linux-gnu", sparc_unknown_linux_gnu),
     ("sparc64-unknown-linux-gnu", sparc64_unknown_linux_gnu),
     ("arm-unknown-linux-gnueabi", arm_unknown_linux_gnueabi),
+
+    ("armeb-unknown-linux-gnueabi", armeb_unknown_linux_gnueabi),
+
     ("arm-unknown-linux-gnueabihf", arm_unknown_linux_gnueabihf),
     ("arm-unknown-linux-musleabi", arm_unknown_linux_musleabi),
     ("arm-unknown-linux-musleabihf", arm_unknown_linux_musleabihf),

(3)修改 config.toml 文件,包括增加 [target.armeb-unknown-linux-gnueabi] 的配置,将 target 指定为 [“armeb-unknown-linux-gnueabi”]等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
target = ["armeb-unknown-linux-gnueabi"]
docs = false
compiler-docs = false
extended = true
tools = ["cargo", "rls", "clippy", "rustfmt", "analysis", "src"]
prefix = "usr"
sysconfdir = "etc"
docdir = "share/doc/rust"
bindir = "bin"
libdir = "lib"
mandir = "share/man"
datadir = "share"
infodir = "share/info"
localstatedir = "var/lib"
channel = "stable"
[target.armeb-unknown-linux-gnueabi]
cc = "armeb-unknown-linux-gnueabi-gcc"
ar = "armeb-unknown-linux-gnueabi-ar"
linker = "armeb-unknown-linux-gnueabi-gcc"

(4)将 armeb-unknown-linux-gnueabi 工具链路径添加到环境变量中

(5)编译 rust 并安装(时间可能长达数小时)

1
$ ./x.py build && ./x.py install

(6)将 bin 和 lib 添加到环境变量中:

1
2
export PATH=$PATH:<path/to/rust/usr/bin>
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:<path/to/rust/usr/lib>

编译 cc crate 时,需要指定交叉编译时用到的编译器,否则会出现文件格式无法使别的情况:

1
export CC_armeb_unknown_linux_gnueabi=armeb-unknown-linux-gnueabi-gcc

2.2 使用 rustup 安装

安装:

1
2
3
rustup install 1.44.0-x86_64-unknown-linux-gnu
# 切换
rustup default 1.44.0

卸载:

1
rustup uninstall 1.44.0

2.3 创建 rustup 工具链

添加自己编译的工具链(在 .rustup 下创建软链接):

1
2
# 参考[3]
rustup toolchain link my_toolchain <path/to/rust/usr/local>

添加之后可以看到对应的工具链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[~]$ rustup show
Default host: x86_64-unknown-linux-gnu
rustup home:  /home/wsl/.rustup

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu
my_toolchain (default)

active toolchain
----------------

my_toolchain (default)
rustc 1.43.0 (4fb7144ed 2020-04-20)
[~]$ ls .rustup/toolchains/
my_toolchain  stable-x86_64-unknown-linux-gnu

cargo 和 rustc 版本不匹配时,可能存在问题(新增的选项无法被 rustc 识别)。例如,cargo 版本为 1.5,rustc 版本为 1.43.0,传给 rustc 的选项可能无法被 rustc 识别:

1
2
3
4
[~/Code/test/hello_world]$ cargo build --target=mips-unknown-linux-uclibc --verbose
   Compiling hello_world v0.1.0 (/media/B/Code/test/hello_world)
     Running `rustc --crate-name hello_world --edition=2018 src/main.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C embed-bitcode=no -C debuginfo=2 -C metadata=e4b5d2b0d82a47b5 -C extra-filename=-e4b5d2b0d82a47b5 --out-dir /media/B/Code/test/hello_world/target/mips-unknown-linux-uclibc/debug/deps --target mips-unknown-linux-uclibc -C linker=mips-openwrt-linux-uclibc-gcc -C incremental=/media/B/Code/test/hello_world/target/mips-unknown-linux-uclibc/debug/incremental -L dependency=/media/B/Code/test/hello_world/target/mips-unknown-linux-uclibc/debug/deps -L dependency=/media/B/Code/test/hello_world/target/debug/deps`
error: unknown codegen option: `embed-bitcode`

安装与 rustc 版本号相同的 cargo 即可,比如,使用编译好的 cargo 替换 ~/.cargo/bin/cargo

3. 所有权

(1) 借用规则

1
2
3
frame: T,
frames: std::collections::VecDeque<T>,
pub fn send_frame(&mut self, id: u8, payload: &[u8], len: u8) -> Result<u8, Error> {...}

send_frame 中的 self 是一个结构体 S,有一个类型为 std::collections::VecDeque<T> 的成员,即 frames,类型 Tframe 的真实类型,也是一个结构体。

1
2
3
4
// 这个地方有点疑惑,为什么必须是 `&mut frame`,去掉 `&mut` 会因两次可变借用而编译失败,进一步改为 `get` 后,会因可变借用和不可变借用同时发生而编译失败
if let Some(&mut frame) = self.transport.frames.get_mut(window_size as usize) {
    self.send_frame(frame.min_id, &frame.payload[0..frame.payload_len as usize], frame.payload_len).unwrap_or(0);
}

去掉 &mut 会因两次可变借用而编译失败:

1
2
3
4
5
6
if let Some(frame) = self.transport.frames.get_mut(window_size as usize) {
                     --------------------- first mutable borrow occurs here
    self.send_frame(frame.min_id, &frame.payload[0..frame.payload_len as usize], frame.payload_len).unwrap_or(0);
    ^^^^            ------------ first borrow later used here
    |
    second mutable borrow occurs here

进一步改为 get 后,会因可变借用和不可变借用同时发生而编译失败:

1
2
3
4
5
6
7
if let Some(frame) = self.transport.frames.get(window_size as usize) {
                     --------------------- immutable borrow occurs here
    self.send_frame(frame.min_id, &frame.payload[0..frame.payload_len as usize], frame.payload_len).unwrap_or(0);
    ^^^^^----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |    |
    |    immutable borrow later used by call
    mutable borrow occurs here

4. 格式化输出

有时候使用 print! 进行格式化输出时,屏幕上没有任何输出信息,原因是 stdout 默认是 line-bffered 的,即行缓冲,遇到换行符或者缓冲区满的时候才会冲刷缓冲区。为了让其立马可以显示,需要在输出语句后面主动冲刷缓冲区:

1
2
3
4
use std::io::{self, Write};

print!("Hello");
io::stdout().flush().unwrap();

5. 字符串处理

[u8]String

1
2
3
let buf: [u8; 5] = [0x48, 0x65, 0x6c, 0x6c, 0x6f];	// "Hello"
let hello = String::from_utf8(buf.to_vec()).unwrap();
assert_eq!(hello, String::from("Hello"));

String[u8]

1
2
3
let hello = String::from("Hello");
let buf = hello.as_bytes();
assert_eq!(buf, [0x48, 0x65, 0x6c, 0x6c, 0x6f]);

移除换行符:

1
2
3
4
5
6
7
8
9
10
let mut recv = String::new();
io::stdin()
    .read_line(&mut recv)
    .expect("Failed to read line");
if recv.ends_with('\n') {
    recv.pop();
    if recv.ends_with('\r') {
        recv.pop();
    }
}

6. 链接问题记录

1
2
3
4
5
6
7
= note: /mnt/f/wsl/project/iot_gw/target/arm-unknown-linux-gnueabihf/release/deps/liblibsqlite3_sys-6e13660e481f9b38.rlib(sqlite3.o): In function `unixDlOpen':
    sqlite3.c:(.text.unixDlOpen+0x8): warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
      /mnt/f/wsl/tool/raspberrypi-tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/../lib/gcc/arm-linux-gnueabihf/4.9.3/../../../../arm-linux-gnueabihf/bin/ld: BFD (crosstool-NG crosstool-ng-1.22.0-88-g8460611) 2.25.1 assertion fail /home/dom/projects/crosstool-ng/install/bin/.build/src/binutils-2.25.1/bfd/elflink.c:2508
      /mnt/f/wsl/tool/raspberrypi-tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/../lib/gcc/arm-linux-gnueabihf/4.9.3/../../../../arm-linux-gnueabihf/bin/ld: BFD (crosstool-NG crosstool-ng-1.22.0-88-g8460611) 2.25.1 assertion fail /home/dom/projects/crosstool-ng/install/bin/.build/src/binutils-2.25.1/bfd/elflink.c:2510
      /mnt/f/wsl/tool/raspberrypi-tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/../lib/gcc/arm-linux-gnueabihf/4.9.3/../../../../arm-linux-gnueabihf/bin/ld: BFD (crosstool-NG crosstool-ng-1.22.0-88-g8460611) 2.25.1 assertion fail /home/dom/projects/crosstool-ng/install/bin/.build/src/binutils-2.25.1/bfd/elflink.c:2508
      /mnt/f/wsl/tool/raspberrypi-tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/../lib/gcc/arm-linux-gnueabihf/4.9.3/../../../../arm-linux-gnueabihf/bin/ld: BFD (crosstool-NG crosstool-ng-1.22.0-88-g8460611) 2.25.1 assertion fail /home/dom/projects/crosstool-ng/install/bin/.build/src/binutils-2.25.1/bfd/elflink.c:2510
      collect2: error: ld returned 1 exit status

GCC 静态链接的程序不能调用 dl 库。这个问题是 libsqlite3-sys 会调用 dl 库、项目的 cargo 配置文件设置了静态链接标志导致的,最后的解决办法是删除静态链接标志。

1
2
3
4
[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
ar = "arm-linux-gnueabihf-ar"
# rustflags = ["-C", "target-feature=+crt-static"]

7. FFI(C)

(1) 0 长度数组的处理

C 语言中结构体中长度为 0 的数组,在 Rust 中表示时可以将其定义为 *const *const 类型的指针,原因是对该内存区域内数据的解释方式要一致,定义为其他类型会造成解释方式不一致,引发乱七八糟的问题。关于 C 语言中 0 长度数组的解释,可以参照《浅析长度为0的数组》。

例如 libusb-1.0.24 中 BOS 描述符的结构体,由于能力描述符的大小无法在编译时确定,从而整个结构体的大小也无法在编译时确定,因此使用了长度为 0 的数组占位,后续使用中根据 bNumDeviceCaps 来分配内存空间,后面的内存是当指针数据来使用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// BOS 描述符结构体
struct libusb_bos_descriptor {
	uint8_t  bLength;
	uint8_t  bDescriptorType;
	uint16_t wTotalLength;
	uint8_t  bNumDeviceCaps;
	struct libusb_bos_dev_capability_descriptor *dev_capability[ZERO_SIZED_ARRAY];
};

// 代码片段
static int parse_bos(struct libusb_context *ctx,
	struct libusb_bos_descriptor **bos,
	const uint8_t *buffer, int size)
{
	struct libusb_bos_descriptor *_bos;
	/* ... */
	/* 这个地方根据描述符中的设备能力数来为 BOS 描述符分配内存空间 */
	_bos = calloc(1, sizeof(*_bos) + bos_desc->bNumDeviceCaps * sizeof(void *));
	if (!_bos)
		return LIBUSB_ERROR_NO_MEM;
	/* ... */
    /* dev_capability 数组保存每个能力描述符所在内存的地址 */
    _bos->dev_capability[i] = malloc(header->bLength);
}

libusb 的 Rust 绑定,libusb-sys v0.2.3 放弃了对能力描述符的解释,能力描述符中的 Capability-Dependent 字段的数据长度也一并放弃了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#[allow(non_snake_case)]
#[repr(C)]
pub struct libusb_bos_dev_capability_descriptor {
    pub bLength: u8,
    pub bDescriptorType: u8,
    pub bDevCapabilityType: u8,
    // 这个位置应该是 Capability-Dependent 字段的数据
}

#[allow(non_snake_case)]
#[repr(C)]
pub struct libusb_bos_descriptor {
    pub bLength: u8,
    pub bDescriptorType: u8,
    pub wTotalLength: u16,
    pub bNumDeviceCaps: u8,
    // 这个位置应该是说有的能力描述符开始的内存区域
}

按照前面说的,在 Rust 中可以将 C 中 0 长度数组的结构体成员定义为 *const *const 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#[allow(non_snake_case)]
#[repr(C)]
pub struct libusb_bos_dev_capability_descriptor {
    pub bLength: u8,
    pub bDescriptorType: u8,
    pub bDevCapabilityType: u8,
    pub dev_capability_data: *const *const u8,
}

#[allow(non_snake_case)]
#[repr(C)]
pub struct libusb_bos_descriptor {
    pub bLength: u8,
    pub bDescriptorType: u8,
    pub wTotalLength: u16,
    pub bNumDeviceCaps: u8,
    pub dev_capability: *const *const libusb_bos_dev_capability_descriptor,
}

在调用该结构体的地方,先使用 std::ptr::addr_of!std::ptr::addr_of_mut! 宏将定义的 *const *const T 类型的成员转换为指针,然后在进行两次解引用。对于数组的处理方式,可以调用指针的 .offset() 方法完成类似数组索引的操作。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub fn dev_capability(&self) -> Vec<BosDevCapabilityDescriptor> {
    unsafe {
        let mut v: Vec<BosDevCapabilityDescriptor> = Vec::new();
        for i in 0..self.num_device_caps() {
            // 先转换成指针
            let point = std::ptr::addr_of!((*self.descriptor).dev_capability).offset(i as _);
            // 在将指针转换为 *const *const libusb_bos_dev_capability_descriptor,然后解引用两次
            let dev_cap = &(*(*(point as * const *const libusb_bos_dev_capability_descriptor)));
            let cap = BosDevCapabilityDescriptor {
                addr: point as *const *const u8,
                bLength: dev_cap.bLength,
                bDescriptorType: dev_cap.bDescriptorType,
                bDevCapabilityType: dev_cap.bDevCapabilityType,
            };
            v.push(cap);
        }
        v
    }
}

要确保两边的地址是一样的,因此,添加打印的方式很有用。Rust 打印地址的控制符是 {:p},和 C 的 %p 类似,打印指针的内容,即指针所指向的区域的地址,可以参考《rust:打印变量地址》。

Rust 中指针相关的操作,可以参考《Interacting with data from FFI in Rust》。

关于,Rust FFI(C) 中复杂类型数据的处理,可以参考《Rust与C交互(FFI)中复杂类型的处理》。

改用 [*const libusb_bos_dev_capability_descriptor] 类型的话,Windows 上会引发 STATUS_HEAP_CORRUPTIONSTATUS_ACCESS_VIOLATION 错误,原因可能是 Rust 的数组和 C 中的数组在内存的使用上有区别,导致对同一块内存数据的解读方式不同。

例如,如下程序,能取到正确的数据,但是程序会崩溃,错误码为(exit code: 0xc0000374, STATUS_HEAP_CORRUPTION):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
pub struct libusb_bos_descriptor {
    pub bLength: u8,
    pub bDescriptorType: u8,
    pub wTotalLength: u16,
    pub bNumDeviceCaps: u8,
    // 错误是这一行引起的
    pub dev_capability: [*const libusb_bos_dev_capability_descriptor],
}

pub fn dev_capability(&self) -> Vec<libusb_bos_dev_capability_descriptor> {
    unsafe {
        let mut v: Vec<libusb_bos_dev_capability_descriptor> = Vec::new();
        for i in 0..self.num_device_caps() {
            println!("&dev_capability[{}]: {:p}", i, &(*self.descriptor).dev_capability[i as usize]);
            println!("dev_capability[{}]: {:p}", i, &(*((*self.descriptor).dev_capability)[i as usize]));
            let dev_cap = &(*(*self.descriptor).dev_capability[i as usize]);

            let cap = libusb_bos_dev_capability_descriptor {
                bLength: dev_cap.bLength,
                bDescriptorType: dev_cap.bDescriptorType,
                bDevCapabilityType: dev_cap.bDevCapabilityType,
                //dev_capability_data: dev_cap.dev_capability_data,
            };
            v.push(cap);
        }
        v
    }
}

for cap in bos_desc.dev_capability() {
    println!("      bLength:                 {}", cap.bLength);
    println!("      bDescriptorType:         {}", cap.bDescriptorType);
    println!("      bDevCapabilityType:      {}", cap.bDevCapabilityType);
    //println!("      dev_capability_data:     {}", cap.dev_capability_data);
}

下面的错误码是 (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pub struct libusb_bos_descriptor {
    pub bLength: u8,
    pub bDescriptorType: u8,
    pub wTotalLength: u16,
    pub bNumDeviceCaps: u8,
    // 错误是这一行引起的
    pub dev_capability: [*const libusb_bos_dev_capability_descriptor],
}

pub fn dev_capability(&self) -> &[*const libusb_bos_dev_capability_descriptor] {
    unsafe {
        &(*self.descriptor).dev_capability
    }
}

for cap in bos_desc.dev_capability() {
    unsafe {
        println!("      bLength:                 {}", (*(*cap)).bLength);
        println!("      bDescriptorType:         {}", (*(*cap)).bDescriptorType);
        println!("      bDevCapabilityType:      {}", (*(*cap)).bDevCapabilityType);
        //println!("      dev_capability_data:     {}", cap.dev_capability_data);
    }
}

参考

[1] https://rust-embedded.github.io/embedonomicon/custom-target.html#use-the-target-file

[2] https://docs.rust-embedded.org/faq.html#compilation-target-support

[3] https://rustc-dev-guide.rust-lang.org/building/build-install-distribution-artifacts.html

This post is licensed under CC BY 4.0 by the author.

Rust 串口编程

嵌入式系统程序占用空间大小分析方法