在 Rust 中,字符串是一个胖指针,包括具有所有权的 String 以及 &'a str,并且为 utf-8 编码

而在 C 中,字符串只是一个单纯的指针,以有符号的 i8 为单元,以 \0 作为结尾,长度不包括结尾 ‘\0’

在使用 Rust 编写 initrpoc 和 系统调用的时候,由于测例所要求的的接口所使用的的字符串相关参数均为 C 标准,所以我们需要将 Rust 和 C 之间的字符串进行转换

CString

Rust 中提供了 alloc::ffi:CString 来处理两者之间的转换

创建 CString

一般可以通过 CString::new 来创建一个 CString

pub fn new<T: Into<Vec<u8>>>(t: T) -> Result<CString, NulError>

具体如下:

#[macro_use]
extern crate alloc;

use alloc::ffi::CString;

fn main() {
    let c_string = CString::new("Hello, world!").unwrap();
    // ...
}

这里的 new 方法会检查传入的值是否是以 \0 结尾的,该方法本身会将传入的值自动加上 \0,所以要求传入的参数所表示的字符串不能以 \0 结尾

也可以通过 unsafe 方法 CString::from_vec_unchecked 等忽略这种检查

CString 转换需注意

Rust 是一门强调安全的语言,所有的值都需要在超出各自的声明周期后释放其所占有的内存

文档中提供了一个 pub fn into_raw(self) -> *mut c_char 方法,该方法的实际意义和它的签名并不相同: 其返回的字符串必须不可变!

因为从 Rust 向 C 传递的字符串本质上还是当前 Rust 程序分配的内存(在堆上,本质还是一个 Vec<u8>,对,是 u8,而不是 i8,估计是为了防止由于溢出而导致的编译失败,rustc dddd),如果在 C 程序中改变了当前字符串的值,比如删去结尾 \0,或者将 \0 提前,都会带来内存安全隐患

所以,这里的 *mut c_char 实际对应 C 中的 char *

及时回收 CString 防止内存泄露

前面提到,CString 底层其实是 Vec<u8>,其实际上是分配在 Rust 的堆上的

在将 CString leek 成传给 C 的指针后,CString 底层的 Vec<u8> 就脱离了 Rust 所有权机制的监管,如果不重构裸指针的所有权,那将会导致内存泄露

要想回收 CString 所使用的内存,可以按以下操作

#[macro_use]
extern crate alloc;

use alloc::ffi::CString;

fn main() {
    let initproc = CString::new("/busybox").unwrap();
    let initproc_raw = initproc.into_raw();

    // ...

    // Execute syscall, which uses C ABI.
    execve(iniproc, /* argv, envs */);

    // Release memory of `initproc_raw`.
    {
        unsafe {
            CString::from_raw(initproc_raw);
        }
    }
}