C2Rust Linux Kernel Module尝试【译】
Table of contents
- 切换到相应的目录并清理make:
- asm gotos,gcc的内联汇编语法的一个新扩展,Linux在5.0版本中开始使用,但在LLVM/clang的9.0版本中才添加
- 在对C2Rust进行一些修复和添加并修复随后的编译错误后,我们用以下命令编译了生成的Rust代码:
- 第一个问题是:所有修复都在之后,我们成功地编译了模块,但是仍然需要修复一些问题。首先,Cargo生成了一个名为libbareflank_rs.a的静态库,但是内核构建系统只支持对象文件,而不支持静态库。我们通过从我们的内核模块Makefile调用系统链接器将静态库转换为对象文件
- 第二个问题是,我们最初还将内核构建系统根据输入对象文件自动生成的bareflank.mod.c文件也进行了转译。我们生成了一个已经包含了这个自动生成文件转译副本的libbareflank_rs.o,然后将其传递给内核构建系统,该系统将生成第二个bareflank.mod.c并将其与libbareflank_rs.o链接,生成bareflank.ko。拥有两份bareflank.mod.c的内容导致了崩溃。一旦我们从构建中移除转译后的bareflank.mod.rs文件,内核模块就能够成功加载和运行。
- 接下来是一些比较难看的部分:
我们将Bareflank的内核模块(包括少量的C文件:common.c、entry.c和platform.c)转译成了Rust。首先,我们需要提取内核的编译标志,并将它们传递给转译器,以便我们可以完美地复制内核构建的配置设置和宏扩展。我们使用Bear工具对原始内核模块进行伪构建,并生成一个包含编译数据库的compile_commands.json文件:
切换到相应的目录并清理make:
cd .../bareflank/bfdriver/src/platform/linux
make clean
bear make CC=clang
# bear -- make -j40 CC=clang
asm gotos,gcc的内联汇编语法的一个新扩展,Linux在5.0版本中开始使用,但在LLVM/clang的9.0版本中才添加
当我们初次进行这个实验(2019年4月)时,这个版本的clang还没有发布,所以我们被迫回退到一个没有使用asm gotos的旧4.x内核,只是为了让clang解析C代码。现在clang已经支持asm gotos,但是转译器还不支持它们。我们首先需要调查Rust的内联汇编(无论是旧版本还是最近添加的重设计)是否现在就支持这个特性。
# Remove compiler flags we can't handle
sed -i -e 's/"-Werror.*",//' ./compile_commands.json
c2rust transpile ./compile_commands.json --emit-no-std --emit-modules -o bareflank-rs "$@"
在对C2Rust进行一些修复和添加并修复随后的编译错误后,我们用以下命令编译了生成的Rust代码:
cargo build --release --target x86_64-linux-kernel -Zbuild-std=core
我们原来使用的是cargo-xbuild来实现这一点,而无需Rust编译器的支持,但是Rust团队在2019年8月底将内核模块目标添加到Cargo中。一旦发生这种情况,我们就可以直接使用cargo build来构建我们的内核模块。
我们必须对Rust文件进行一些小的手动更改:
将
use libc;
替换为use crate::libc;
,因为我们在内核模块内部使用来自crates.io的libc crate时遇到了一些问题。C2Rust使用这个crate中的基本类型定义,我们必须手动重新定义它们。这可能现在已经不再是问题了。手动删除一些重复的
current_stack_pointer
定义。这个变量在C的一个头文件中定义,转译器在每个转译的.rs文件中都会将其作为#[no_mangle]进行发射,这会导致Rust链接错误。我们必须手动删除除一个之外的所有这个符号的定义(这个符号甚至没有在任何地方被使用,所以我们本来可以删除所有的定义,但是并不需要这么做)
第一个问题是:所有修复都在之后,我们成功地编译了模块,但是仍然需要修复一些问题。首先,Cargo生成了一个名为
libbareflank_rs.a
的静态库,但是内核构建系统只支持对象文件,而不支持静态库。我们通过从我们的内核模块Makefile调用系统链接器将静态库转换为对象文件
$(M)/libbareflank_rs.o: target/x86_64-linux-kernel/release/bareflank_rs.a
$(LD) -r -o $@ --whole-archive $^
第二个问题是,我们最初还将内核构建系统根据输入对象文件自动生成的
bareflank.mod.c
文件也进行了转译。我们生成了一个已经包含了这个自动生成文件转译副本的libbareflank_rs.o
,然后将其传递给内核构建系统,该系统将生成第二个bareflank.mod.c
并将其与libbareflank_rs.o
链接,生成bareflank.ko
。拥有两份bareflank.mod.c
的内容导致了崩溃。一旦我们从构建中移除转译后的bareflank.mod.rs
文件,内核模块就能够成功加载和运行。
我们遇到了许多内核头文件使用的C特性和gcc特定的扩展,我们必须添加到C2Rust:
一系列的gcc属性:内联、冷、别名、已使用、区段等等
对C2Rust位字段crate的更新:支持no_std和布尔值
一些内核内存函数使用的gcc内置函数:
__builtin_object_size
C2Rust中内存只读、读写和早期污染操作数的内联汇编支持
同时打包和对齐的结构体,主要是xregs_state。这种特殊的组合目前在Rust中还不支持,所以我们不得不在C2Rust中实现一个变通方法,通过发射一对嵌套结构体(对齐的外部结构体,打包的内部结构体)来实现:
#[repr(C, align(64))] pub struct xregs_state(pub xregs_state_Inner); #[repr(C,packed)] pub struct xregs_state_Inner { // ... }
接下来是一些比较难看的部分:
- C2Rust将C类型映射到libc crate的定义,但我们在我们的内核模块中无法使用这个crate。相反,我们手动重新定义了一些类型:
pub mod libc {
pub type c_char = i8;
pub type c_schar = i8;
pub type c_ulong = u64;
pub type c_uint = u32;
// ...all the others
}
(cty crate也提供了必要的类型别名。)
- 我们还必须实现一些编译器依赖的libc函数:
pub unsafe fn memset(s: *mut c_void, c: c_int, n: size_t) -> *mut c_void {
core::ptr::write_bytes(s as *mut u8, c as u8, n as usize);
s
}
pub unsafe fn memcpy(dest: *mut c_void, src: *const c_void, n: size_t) -> *mut c_void {
core::ptr::copy_nonoverlapping(src as *const u8, dest as *mut u8, n as usize);
dest
}
最后,我们为一些缺失的内核函数和Rust panic处理器(我们决定在以后的日期实现它)添加了一些桩:
#[no_mangle]
pub extern "C" fn __bad_size_call_parameter() -> ! {
unreachable!("__bad_size_call_parameter")
}
#[no_mangle]
pub extern "C" fn __bad_percpu_size() -> ! {
unreachable!("__bad_percpu_size")
}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
// FIXME: call into the kernel
loop {}
}
主要剩下的“难看之处”是转译器会完全展开C宏,所以转译后的代码并不总是好看:
match ::core::mem::size_of::<libc::c_int>() as libc::c_ulong {
1 => {
pscr_ret__ = ({
let mut pfo_ret__: libc::c_int = 0;
match ::core::mem::size_of::<libc::c_int>() as libc::c_ulong {
1 => asm!("movb %gs:$1,$0" :
"=q" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
2 => asm!("movw %gs:$1,$0" :
"=r" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
4 => asm!("movl %gs:$1,$0" :
"=r" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
8 => asm!("movq %gs:$1,$0" :
"=r" (pfo_ret__) :
"*m(&cpu_number)
: : "volatile"),
_ => {
::rust_extras::unreachable();
}
}
pfo_ret__
})
},
2 => {
pscr_ret__ = ({
let mut pfo_ret__: libc::c_int = 0;
match ::core::mem::size_of::<libc::c_int>() as libc::c_ulong {
1 => asm!("movb %gs:$1,$0" :
"=q" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
2 => asm!("movw %gs:$1,$0" :
"=r" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
4 => asm!("movl %gs:$1,$0" :
"=r" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
8 => asm!("movq %gs:$1,$0" :
"=r" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
_ => {
::rust_extras::unreachable();
}
}
pfo_ret__
})
},
4 => {
pscr_ret__ = ({
let mut pfo_ret__: libc::c_int = 0;
match ::core::mem::size_of::<libc::c_int>() as libc::c_ulong {
1 => asm!("movb %gs:$1,$0" :
"=q" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
2 => asm!("movw %gs:$1,$0" :
"=r" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
4 => asm!("movl %gs:$1,$0" :
"=r" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
8 => asm!("movq %gs:$1,$0" :
"=r" (pfo_ret__) :
"*m" (&cpu_number)
: : "volatile"),
_ => {
::rust_extras::unreachable();
}
}
pfo_ret__
})
},
_ => {
::rust_extras::unreachable();
}
}
这个宏定义了一个为每个可能的类型大小生成不同汇编代码的函数。在转译后的Rust代码中,这个宏被完全展开,生成了一大堆重复的代码。
即使有这么多的问题,这个转译过程仍然是成功的。我们已经能够加载和运行一个复杂的Linux内核模块,而这个模块完全用Rust编写,并且没有任何C代码。这对于验证C2Rust和我们的目标——将现有的C代码库迁移到Rust——都是一次大胜。