Transpiling A Kernel Module to Rust: The Good, the Bad and the Ugly

栏目: IT技术 · 发布时间: 4年前

内容简介: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; with use 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.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

通灵芯片

通灵芯片

Daniel Hillis / 崔良沂 / 上海世纪出版集团 / 2009-1 / 19.80元

本书深入浅出地阐述了计算机科学中许多基本而重要的概念,包括布尔逻辑、有限自动机、编程语言、图灵机的普遍性、信息论、算法、并行计算、量子计算、神经网络、机器学习乃至自组织系统。 作者高屋建瓴式的概括,既不失深度,又妙趣横生,相信读者读后会有很多启发。 目录: 序言:石的奇迹 第一章 通用件 第二章 万能积木 第三章 程序设计 第四章 图灵机的普适性 第......一起来看看 《通灵芯片》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具