rust加载不进去服务器eac_用Rust重写Linux内核模块体验

最近,我用Rust重写了一个2W+行C代码的linux内核模块。在此记录一点经验。我此前没写过内核模块,认识比较疏浅,有错误欢迎指正。

为什么要重写?

这个模块2W+行代码量看起来不多,却在线上时常故障,永远改不完。十多年的老代码,经手了无数程序员,没人能解决其中的内存安全问题。拿过来一看,代码中的确有不少会产生UB的写法,线上的故障从core来看都飘得太远,难以定位根本原因在哪里。所以我没有把握(没有能力)在原代码基础上能将所有线上故障修复。 而Rust是一个现代的、高性能、无GC、内存安全的编程语言,我想它非常适合用来重写这个内核模块。

Hello World

首先来介绍下如何用Rust写linux内核模块吧。也可以参考这里, 该项目正在尝试写一个safe的rust内核框架,目前的状态还不实用,我没使用该框架,仅参考了其基本编译配置。

基本思路就是分别建立一个linux内核c工程和rust的hello world工程,把它们放到一块儿(不放到一块儿也行),文件分布如下:

├── Cargo.toml
├── Makefile
├── mydriver.c
└── src
    └── lib.rs

然后在linux内核模块的入口和出口函数分别调用rust中实现的入口和出口函数,rust中将入口、出口函数标记为extern "C",所有业务逻辑在Rust中完成。

// mydriver.c
// ... include headers

extern int my_drv_init(void); // defined in rust
extern void my_drv_exit(void); // defined in rust

static int _my_drv_init(void)
{
    
    printk("loading my drivern");
    return my_drv_init();
}

static void _my_drv_exit(void)
{
    
        printk("exiting my drivern");
    my_drv_exit();
}

module_init(_my_drv_init);
module_exit(_my_drv_exit);
// lib.rs
#[no_mangle]
pub extern "C" fn my_drv_init() -> i32 {
    
      KLogger::install();
      info!("loading my driver in rust");
      0
}

#[no_mangle]
pub extern "C" fn my_drv_exit() {
    
      info!("exiting my driver in rust");
}

Cargo.toml中需要配置输出staticlib

[lib]
name = "mydriver"
crate-type = ["staticlib", "rlib"]

模块的Makefile调用cargo编译rust库,然后将其和c一块儿链接成ko,大概这个样子:

MODNAME = mydriver

KDIR ?= /lib/modules/$(shell uname -r)/build
BUILD_TYPE = release
LIB_DIR = target/$(ARCH)-linux-kernel/$(BUILD_TYPE)

all:
    $(MAKE) -C $(KDIR) M=$(CURDIR)

clean:
    $(MAKE) -C $(KDIR) M=$(CURDIR) clean
    rm -rf target

rlib:
    # 目前需要nightly才能编译core和alloc.
    cargo +nightly build --$(BUILD_TYPE) -Z features=dev_dep,build_dep -Z build-std=core,alloc --target=$(ARCH)-linux-kernel


obj-m := $(MODNAME).o

$(MODNAME)-objs := mydriver.o mydriver.rust.o

.PHONY: $(src)/lib$(MODNAME).a
$(src)/lib$(MODNAME).a:
    cd $(src); make rlib
    cd $(src); cp $(LIB_DIR)/lib$(MODNAME).a .

%.rust.o: lib%.a
    $(LD) -r -o $@.tmp --whole-archive $<
    $(src)/plt2pc.py $@.tmp $@

可行性评估

用Rust写linux内核模块还是有些担忧,目前还没看到Rust内核模块相关的严肃开源项目,Demo倒是有两个。动手之前,咱们还是尽可能评估一下可行性。之前有了解到有工具C2Rust可以将C代码转换成Rust代码,所以,我的想法是先用C2Rust将原有C代码转成Rust,看能不能编译跑起来,各功能是否正常,看看有没有什么硬伤。如果能正常使用,则可以在转出的代码的基础上逐渐将unsafe rust重构为safe rust。

0ec602e82b29512a6aeda7e31ab23ce0.png
C2Rust工作流

按照C2Rust相关文档操作下来,遇到几个问题:

  1. 转换时内核头文件的时候报错。
/usr/src/kernels/.../arch/x86/include/asm/jump_label.h:16:2: error: 'asm goto' constructs are not supported yet
        asm_volatile_goto("1:"
        ^
include/linux/compiler-gcc4.h:79:43: note: expanded from macro 'asm_volatile_goto'
# define asm_volatile_goto(x...)        do { asm goto(x); asm (""); } while (0)

据C2Rust文档介绍,需要最新的libclang才能支持此语法。

2. 转换后的代码编译报错。

编译错误大致分为memcpy宏、内联汇编错误、依赖libc crate几类。

以上错误中,libc的依赖仅仅使用了libc中定义的一些C语言基本类型,因此,可以写一个简单的libc crate替代。其它错误均通过临时修改内核头文件,将不支持的语法define成其他替代品规避。

3. 编译成功后的ko文件加载报错。

加载ko报如下错误:

insmod: ERROR: could not insert module mp.ko: Invalid module format

dmesg显示:

Unknown rela relocation: 4

这是由于Rust编译器(LLVM)生成的二进制中对于extern “C

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值