内容简介:Earlier this year, we used the C2Rust framework to translate applications such asQuake 3 to Rust. In this post, we’ll show you that it is also possible to translate privileged software such as modules that are loaded by the Linux kenel. We’ll use a small,
Earlier this year, we used the C2Rust framework to translate applications such asQuake 3 to Rust. In this post, we’ll show you that it is also possible to translate privileged software such as modules that are loaded by the Linux kenel. We’ll use a small, 3-file kernel module which is part of the Bareflank Hypervisor SDK developed by Assured Information Security but you can use the same techniques to translate other kernel modules. The basic steps we’ll cover are:
- translating the C files into Rust,
- compiling generated Rust, and
- linking everything into a loadable kernel module.
The Good
We transpiled the kernel module of Bareflank which consists of a small number
of C files: common.c
, entry.c
and platform.c
. For the first step, we
needed to extract the kernel’s compilation flags and pass them to the
transpiler, so we would perfectly replicate the configuration settings and
macro expansions from the kernel build. We used the Bear
tool to pseudo-build the original
kernel module and produce a compile_commands.json
file containing the
compilation database:
$ cd .../bareflank/bfdriver/src/platform/linux $ make clean $ bear make CC=clang
The kernel build system compiles C files differently (enabling or disabling some language features) depending on the compiler used. Since the C2Rust transpiler uses clang as its front-end, we had to use clang for the pseudo-build as well. One complication we ran into were asm gotos , which are a new extension to gcc’s inline assembly syntax that Linux started using in the 5.0 release, but were added to LLVM/clang in version 9.0. When we initially performed this experiment (April 2019), that version of clang hadn’t come out so we were forced to revert to an older 4.x kernel that did not use asm gotos just to get clang to parse the C code. Asm gotos are now supported by clang, but the transpiler doesn’t support them yet. We need to first investigate whether Rust’s inline assembly (either the legacy version or the recently added redesign ) even supports this feature right now.
After producing the compilation database, we transpiled the C files into 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 "<a href="/cdn-cgi/l/email-protection" data-cfemail="0c284c">[email protected]</a>"
After making some fixes and additions to C2Rust and fix the subsequent compilation errors, we compiled the resulting Rust code with:
# cargo build --release --target x86_64-linux-kernel -Zbuild-std=core
We had originally used
cargo-xbuild
to accomplish
this without support from the Rust compiler, but the Rust team added the
kernel module target to Cargo at the end of August 2019. Once that happened,
we could use cargo build
directly to build our kernel module.
We had to make a few minor manual changes to the Rust files:
-
Replace
use libc;
withuse crate::libc;
since we had some problems using the libc crate from crates.io inside a kernel module. C2Rust uses the basic C type definitions from this crate, and we had to redefine them manually. This may not be a problem anymore. -
Manually remove a couple of duplicate
current_stack_pointer
definitions. This variable is defined in a header in C and the transpiler emits it in every transpiled.rs
file as#[no_mangle]
, which is a Rust error. We had to manually remove all but one definitions of this symbol (the symbol wasn’t even used anywhere, so we could have removed all of them, but didn’t need to).
Once all the fixes were in, we successfully compiled the module but still had
to fix a couple of issues. First, Cargo produces a static library called libbareflank_rs.a
, but the kernel build system only supports object files and
not static libraries. We turned one into the other by calling the system linker
from our kernel module Makefile
:
$(M)/libbareflank_rs.o: target/x86_64-linux-kernel/release/bareflank_rs.a $(LD) -r -o <a href="/cdn-cgi/l/email-protection" data-cfemail="d1f591">[email protected]</a> --whole-archive $^
The second issue was that we were initially also transpiling the bareflank.mod.c
file that the kernel build system auto-generates from the input
object files. We were producing a libbareflank_rs.o
that already included a copy of it,
then passing that to the kernel build system that would produce a second bareflank.mod.c
and link that together with libbareflank_rs.o
to produce bareflank.ko
. Having two copies of the contents of bareflank.mod.c
was
causing crashes. Once we removed the transpiled bareflank.mod.rs
file from the build,
the kernel module loaded and ran successfully.
The Bad
We encountered many C features and gcc-specific extensions that the kernel headers use and we had to add to C2Rust:
-
A series of gcc attributes:
inline
,cold
,alias
,used
,section
and more -
Updates to the C2Rust bitfields crate: support for
no_std
and booleans -
A gcc intrinsic used by some kernel memory functions:
__builtin_object_size
-
Inline assembly support in C2Rust for memory-only, read-write and early-clobber operands
-
Structures that are both packed and aligned, mainly
xregs_state
. This particular combination is not currently supported in Rust, so we had to implement a work-around in C2Rust by emitting a pair of nested structures (aligned outer structure, packed inner structure) instead:
#[repr(C, align(64))] pub struct xregs_state(pub xregs_state_Inner); #[repr(C, packed)] pub struct xregs_state_Inner { // ... }
All of these issues have been implemented in C2Rust and should work for all kernel modules, but different modules may expose new unimplemented C features.
The Ugly
C2Rust maps C types to their definitions in the libc crate, but we were unable (at the time) to use this crate in our kernel module. Instead, we redefined some of its types manually:
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 }
We also had to implement a couple of libc functions that the compiler relies on:
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 }
Finally, we added stubs for a few missing kernel functions, and for the Rust panic handler (we decided to implement it properly at a later date):
#[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 {} }
The main remaining “ugliness” is that the transpiler fully expands C macros, so the transpiled code is not always pleasant to look at:
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"), _ => { __bad_percpu_size(); } } pfo_ret__ }) }
C2Rust uses clang as a front-end which currently expands macros very early before parsing, so modifying it to produce an AST with unexpanded macros would be a significant undertaking. We are currently considering other alternatives, but it is unclear whether any of them would be simpler. For the foreseeable future, C2Rust will not be able to transpile non-trivial function-like C macros to Rust macros.
We’d love to hear what you want to see translated next. Drop us a line at [email protected] and let us know! If you have legacy C/C++ code you need modernized, translated or integrated with Rust, we are here to help.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。