kernel-roulette

RustでLinux kernel moduleが書ける…。噂の真相を突き止めるため、GitHubへと向かった。
ネタばれ。噂は本当だった。


まず最初に見つかったのが、rust.koです。
内容は、kernel moduleをロードすると、”Hello from Rust!++”をprintkで出力する、というものです。
ただ、3年前からメンテナンスされていません。早々に見切りをつけて次を探しました。

次に発見したのが、kernel-rouletteでした。
character device driverを実装しており、デバイスファイルをreadすると、一定確率でkernel panicを起こす、というdriverでした。
なんて迷惑なdriverでしょうか
こういう高度な技術を使って、無駄なことするの、好きです。
最終更新日が7ヶ月前なので、トラブルがあっても、これからなんとかなりそうです。

というわけで、少々トラブルはあったのですが、ビルドして動かしすことができました。

感想から言うと、RustでLinux kernel moduleが書ける、というより、Linux kernel moduleからRustを呼び出せる、という感じでした。
RustでLinux driverを書けるからHappy!という世界ではないです。(おそらくそういう世界はずっと来ないと思います)

ただ、細かいことは置いておいて、面白いので見ていきましょう。

まず、何はともあれ、動かすとどうなるか、を紹介します。
その後、ソースコードを解析していきます。

Linux kernel driverやRustの基礎事項については解説しません。別途参照をお願いします。

ビルド&動作確認環境

項目VersionなどLinux kernel4.4.0-137-genericdistributionUbuntu 16.04 (Virtual Box)rustc1.31.0-nightly (77af31408 2018-10-11)

動かしてみよう

前準備

kernel-roulette READMEのDependenciesにある通り、下記の前準備が必要です。

Linux kernel headers and build-essential (gcc, make, etc.)
Nightly Rust (install from https://rustup.rs)
Xargo and rust-src
Download the Rust library source with rustup component add rust-src
Install xargo with cargo install xargo

ビルド

レポジトリをcloneして、patchを当てます。

$ git clone https://github.com/souvik1997/kernel-roulette.git
$ cd kernel-roulette
$ wget https://raw.githubusercontent.com/tomoyuki-nakabayashi/blogs/master/Rust/kernel-roulette/0001-fix-build-error-in-the-latest-nightly-rust.patch
$ patch -p1 < 0001-fix-build-error-in-the-latest-nightly-rust.patch

READMEのBuildingに従って手順を実行します。
以下、少し冗長に書きますので、手っ取り早く動かしたい方は、READMEの方をご覧下さい。

kernel moduleをビルドします。build/roullete.koが生成されることを確認します。

$ make
$ ls build/
...  roulette.ko  ...

いよいよ、緊張の瞬間です。kernel moduleをロードしてみましょう。

$ sudo insmod build/roullete.ko

ロードできちゃいます!
ちゃんとロードされているか、確認してみましょう。

$ lsmod
Module                  Size  Used by
roulette              106496  0

おるやん!
dmesgにroulette driverがログを出力するので、確認します。Panicする確率は1/100のようです!ほとんど死なないですね!

$ dmesg | tail -n 10
...
[16837.958611] Registered kernel-roulette with major device number 246
[16837.958613] Run /bin/mknod /dev/kernel-roulette c 246 0
[16838.468004] Panic probability: 1/100

roulette driverの指示通り、キャラクタデバイスファイルを作成します。

$ sudo /bin/mknod /dev/kernel-roulette c 246 0

作成したデバイスファイルをreadすると、1/100の確率で死にます!
catして、その時が来るのを待ちます。

$ cat /dev/kernel-roulette
Survived... sampled value is 79, which is >= 1
$ cat /dev/kernel-roulette
Survived... sampled value is 1, which is >= 1

死ねない!
死ぬ確率は、driverロード時にランダムで決定されるので、面倒なのでロードし直します。

$ sudo rmmod build/roulette.ko
$ sudo insmod build/roulette.ko

さて。

$ dmseg | tail -n 10
...
[17574.935478] Registered kernel-roulette with major device number 246
[17574.935481] Run /bin/mknod /dev/kernel-roulette c 246 0
[17575.447283] Panic probability: 49/100

今度はすぐ死ねそうですね。

$ cat /dev/kernel-roulette
Survived... sampled value is 86, which is >= 49
$ cat /dev/kernel-roulette
Segmentation fault

お、2回目でセグメンテーションフォールトが発生しました。何が起こっているか、dmesgで確認してみましょう。

$ dmesg | tail -n 100
[17633.158768] Rust panic @ src/roulette.rs:33
[17633.158774] Boom!
[17633.158793] ------------[ cut here ]------------
[17633.158807] Kernel BUG at ffffffffc04bb24e [verbose debug info unavailable]
[17633.158822] invalid opcode: 0000 [#1] SMP 
# トレース省略
[17633.172695] fbcon_switch: detected unhandled fb_set_par error, error code -16
[17633.173673] fbcon_switch: detected unhandled fb_set_par error, error code -16
[17633.174673] ---[ end trace ff2742f73b763c6d ]---

まずRustでpanicが発生していて、その後は、おなじみのLinux kernelのトレース情報です。
KernelのBUG()が呼び出されて、invalid opcodeになっていますね。

kernel-roullete作者の恩情で、panic()ではなく、BUG()を呼ぶようにしてくれているため、Kernelがpanicしてシステムが止まるような事態は発生しません。

ちなみに、この後、roullete driverをrmmodできなくなります。
原因は解明できていないです。

$ sudo rmmod roulette
rmmod: ERROR: Module roulette is in use

ソースコード解析

さて、Linux kernel driverの一部として、Rustを使うにはどのようなトリックが必要なのでしょうか?
ここまでで動作を確認したkernel-rouletteのソースコードを解析してみましょう。
ソースファイルやMakefileに丁寧にコメントが添えられているため、非常に解析しやすいソースコードでした。作者の技術レベルの高さが伺えます。
紹介するソースコードは、1.31.0-nightlyでビルドできるように修正を加えたものです。

構成

まずは、ディレクトリ構成から全体像を把握していきます。shim.cとkbuild.mkがある以外は、普通のRustプロジェクト、という感じでしょうか。

$ tree .
.
├── Cargo.lock
├── Cargo.toml
├── kbuild.mk
├── LICENSE.txt
├── Makefile
├── README.md
├── src
│   ├── io
│   │   └── mod.rs
│   ├── lang.rs
│   ├── lib.rs
│   ├── mem
│   │   └── mod.rs
│   ├── roulette.rs
│   └── shim.c
├── x86_64-unknown-none-gnu.json
└── Xargo.toml

3 directories, 14 files

Cargo.tomlを見てみましょう。

[package]
name = "kernel-roulette"
version = "0.1.0"
authors = ["Souvik Banerjee <souvik@souvik.me>"]

[lib]
name = "roulette"
crate-type = ["staticlib"]

[dependencies]
lazy_static = { version = "1.0.0", features = ["spin_no_std"] }
spin = "0.4.9"
rand = { version = "0.4.2", default-features = false }
rlibc = "1.0"

crate-typeがstaticlibになっています。これはRustのアーカイブファイルを作って、C言語のオブジェクトファイルとリンクするアレですね、きっと。
検討がついたところで、Makefileを見ましょう。kernel module名はrouletteです。

# Configuration that will be passed to sub-make

# Name of the kernel module
export KERNEL_MODULE := roulette

# Path to Linux kernel headers
export KERNEL_BUILD_PATH := /lib/modules/$(shell uname -r)/build

# Find all C and Rust source files
export C_FILES := $(shell find src/ -type f -name "*.c")
export RUST_FILES := $(shell find src/ -type f -name "*.rs") Cargo.toml Cargo.lock

# Define the architecture; this will be used for the LLVM target specification
export UTS_MACHINE = x86_64

# The Rust compiler and cross-compiler
export CARGO=cargo
export XARGO=xargo

# A JSON file specifying an LLVM target
export LLVM_TARGET_SPEC=$(UTS_MACHINE)-unknown-none-gnu.json

# Top-level project directory
export BASE_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))

# The build directory
export BUILD_DIR := build

# The Makefile that is copied to $(BUILD_DIR)
export KBUILD := kbuild.mk

all: $(BUILD_DIR)/Makefile Makefile
# The kbuild makefile has been copied to $(BUILD_DIR), so now we can invoke kbuild from
# the kernel headers.
    $(MAKE) -C "$(KERNEL_BUILD_PATH)" M="$(BASE_DIR)/$(BUILD_DIR)" modules

$(BUILD_DIR)/Makefile : $(KBUILD)
    @mkdir -p "${BUILD_DIR}/src"
    cp "$(KBUILD)" "$(BUILD_DIR)/Makefile"

clean:
# cleanup is really simple, we just blow away the $(BUILD_DIR) and call `xargo clean`
    rm -rf "$(BUILD_DIR)"
    xargo clean

kbuild.mkをMakefileにリネームした上で、buildディレクトリに移動して、kbuild.mkをmakeコマンドで叩いています。
kbuildで引き継ぐ環境変数も多数定義していますね。
kbuild.mkを見てみます。

# Define the Rust target. Xargo will create an ar archive file with the
# following name in the target folder
RUST_TARGET := lib$(KERNEL_MODULE).a

# Enumerate the object files that the C files will compile to
C_OBJECTS := $(patsubst %.c,%.o,$(C_FILES))

# Tell kbuild which files to build
obj-m := $(KERNEL_MODULE).o

# Tell kbuild where the source files are
src := $(BASE_DIR)

# The kernel module will be linked from the C object files and the Rust archive
# The order is important: C objects must come first
$(KERNEL_MODULE)-objs := $(C_OBJECTS) $(RUST_TARGET)

# Strip unused symbols from the input object file
EXTRA_LDFLAGS += --gc-sections --entry=init_module --undefined=cleanup_module
EXTRA_LDFLAGS += $(if $(RELEASE),--strip-all)

# Fix file paths (since this script will be run from the kbuild's working directory)
C_FILES    := $(foreach filepath,$(C_FILES)   ,$(BASE_DIR)/$(filepath))
RUST_FILES := $(foreach filepath,$(RUST_FILES),$(BASE_DIR)/$(filepath))
LLVM_TARGET_SPEC := $(foreach filepath,$(LLVM_TARGET_SPEC),$(BASE_DIR)/$(filepath))

# Determine target directory of cargo's module build
CARGO_BUILD_DIR := $(BASE_DIR)/target/$(UTS_MACHINE)-unknown-none-gnu/$(if $(RELEASE),release,debug)

$(obj)/$(RUST_TARGET): $(RUST_FILES) $(LLVM_TARGET_SPEC) $(BASE_DIR)/$(KBUILD)
# We set RUST_TARGET_PATH because of a bug in cargo/xargo/rustc: the --target flag is relative to the current working
# directory but subsequent invokations of cargo/xargo/rustc might change their working directory. Setting
# RUST_TARGET_PATH ensures that the compiler can find the LLVM target specification.
# We also have to `cd` into $(BASE_DIR) since we are currently in the kernel headers directory.
    (cd $(BASE_DIR); env RUST_TARGET_PATH=$(BASE_DIR) $(XARGO) rustc $(if $(RELEASE),--release) $(if $(VERBOSE),--verbose) --target $(UTS_MACHINE)-unknown-none-gnu -- -C code-model=kernel -C relocation-model=static -C panic=abort)
# After the archive is compiled, copy it to the build directory
    cp "$(CARGO_BUILD_DIR)/$(RUST_TARGET)" $(obj)

$(obj)/%.c : $(BASE_DIR)/%.c $(BASE_DIR)/$(KBUILD)
# KBUILD_CFLAGS is automatically generated by kbuild
    $(CC) $(KBUILD_CFLAGS) -c $(BASE_DIR)/$*.c -o $*.o

そこそこボリュームがあるのですが、下を見ると、先ほどの予測が的中していることが分かります。

# The kernel module will be linked from the C object files and the Rust archive
# The order is important: C objects must come first
$(KERNEL_MODULE)-objs := $(C_OBJECTS) $(RUST_TARGET)

kernel moduleを作るためのオブジェクトファイルは、C言語とRust、それぞれ個別で作られています。
C言語、普通にkernel moduleのオブジェクトファイル作っています。

$(obj)/%.c : $(BASE_DIR)/%.c $(BASE_DIR)/$(KBUILD)
# KBUILD_CFLAGS is automatically generated by kbuild
    $(CC) $(KBUILD_CFLAGS) -c $(BASE_DIR)/$*.c -o $*.o

Rust、relocation-modelをstaticにする、panicをabortにする、以外は普通のクロスビルドでしょうか。

    (cd $(BASE_DIR); env RUST_TARGET_PATH=$(BASE_DIR) $(XARGO) rustc $(if $(RELEASE),--release) $(if $(VERBOSE),--verbose) --target $(UTS_MACHINE)-unknown-none-gnu -- -C code-model=kernel -C relocation-model=static -C panic=abort)

RustとC言語ソースファイルの構成をざっと見てみましょう。

├── src
│   ├── io
│   │   └── mod.rs
│   ├── lang.rs
│   ├── lib.rs
│   ├── mem
│   │   └── mod.rs
│   ├── roulette.rs
│   └── shim.c
ファイル説明shim.cLinux kernel driver。init()やopen()でRust関数を呼び出すio/mod.rsprintkのラッパーでprint!マクロを定義mem/mod.rskmallocのラッパーでヒープアロケータを実装lang.rspanic_handlerおよびalloc_error_handlerを定義lib.rsinit(), exit()で呼ばれる関数を定義roulette.rs一定確率でpanicする処理を定義

ソースコード

では、コードを見ていきましょう。

初期化/終了処理

driverロード

まずは、初期化を追っていきます。
kernel driverのエントリポイントなので、C言語からです。
最後の一行以外は、普通のkernel driverです。
キャラクタデバイスを登録しています。これは後ほど改めて解説します。

/*
 * The entry points in C
 */
static int _mod_init(void) {

  // Register a character device
  rl_dev_major_num = register_chrdev(0 /* allocate a major number */, rl_device_name, &rl_driver_fops);

  if (rl_dev_major_num < 0) {
    printk(KERN_ALERT "failed to register character device: got major number %d\n", rl_dev_major_num);
    return rl_dev_major_num;
  }

  printk(KERN_INFO "Registered %s with major device number %d\n", rl_device_name, rl_dev_major_num);
  printk(KERN_INFO "Run /bin/mknod /dev/%s c %d 0\n", rl_device_name, rl_dev_major_num);

  return rust_mod_init();
}
init(_mod_init);

rust_mod_init()は、Rustの関数呼び出しです。shim.c内でexternされています。

/*
 * The entry points for the kernel module in Rust. We define
 * entry points in C for the module_init and module_exit macros.
 */
extern int rust_mod_init(void);

Rust側の実装を見てみましょう。
no_mangleでマングルされないようにしている以外は、普通のRustに見えますね。

// Entry points
#[no_mangle]
pub extern "C" fn rust_mod_init() -> i32 {
    print!("Panic probability: {}/{}\n", CONFIG.lock().chance, MAX_RAND);
    0
}

ただ、裏では色々と動いています。
順番に見ていきましょう。
print!マクロ、ヒープアロケータ、CONFIG、の順番に見ていきます。

print!マクロ

まずは、print!マクロです。
標準ライブラリを使っていないため、printk()をラップする仕組みを自作しています。

/// Like the `print!` macro in the standard library, but calls printk
#[allow(unused_macros)]
macro_rules! print {
    ($($arg:tt)*) => ($crate::io::print(format_args!($($arg)*)));
}

このcrateのioモジュールのprint()を読んでいますね。
つまり、同io.rs内のprint()関数です。

pub fn print(args: fmt::Arguments) {
    use core::fmt::Write;
    let mut writer = PRINTK_WRITER.lock();
    writer.write_fmt(args).unwrap();
    writer.flush();
}

PRINTK_WRITERのロックを取得し、writerが実装しているWrite traitのwrite_fmt()を呼び出しています。
このPRINTK_WRITERは、Mutex付きのKernelDebugWriterです。
lazy_static!マクロを利用しているので、最初に使われるときに初期化されます。

lazy_static! {
    /// Used by the `print!` and `println!` macros.
    pub static ref PRINTK_WRITER: Mutex<KernelDebugWriter> = Mutex::new(KernelDebugWriter::default());
}

KernelDebugWriterは、最終的に、C言語のprintk()関数を呼び出します。
printk()関数に渡す文字列は、Vecでバッファリングします。

#[derive(Default)]
pub struct KernelDebugWriter {
    buffer: Vec<u8>,
}

extern "C" {
    // printk()のラッパー
    fn puts_c(len: u64, c: *const u8);
}

// `buffer`を拡張して、`buffer`に文字列を追加する。
impl fmt::Write for KernelDebugWriter {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        // Add the bytes from `s` to the buffer
        self.buffer.extend(s.bytes());
        Ok(())
    }
}

impl KernelDebugWriter {
    // 実際に文字を出力する。出力が終わると、`buffer`をクリアする。
    fn flush(&mut self) {
        // Call c_puts with the string length and a pointer to its contents
        unsafe { puts_c(self.buffer.len() as u64, self.buffer.as_ptr() as *const u8) };
        self.buffer.clear();
    }
}

Vecを使っているので、ヒープアロケータが必要です。これは後で説明します。
write_str()でbufferに文字列のデータを追加し、flush()でC言語のput_c()を呼び出します。

C言語の世界に戻ります。put_c()はprintk()のラッパーですね。

/*
 * A utility function to print a string.
 * NOTE: `str` is not necessarily NULL-terminated.
 */
void puts_c(u64 length, char* str) {
  printk(KERN_DEBUG "%.*s", (int)length, str);
}

これで、Rustからkernelログを出力できる仕組みがわかりました。

ヒープアロケータ

kmalloc()をラップして、Vecの利用を解禁します。

Rustのベアメタルヒープアロケータについては、こちらの記事で書きました。詳細に興味があればご覧下さい。
Redox Slab Allocatorで学ぶRustベアメタル環境のヒープアロケータ

print!マクロでは、文字列をVecのbufferでバッファリングして、printk()に出力を委譲していました。
Vecを使うためには、ヒープ領域を確保する必要があります。ヒープ領域を使うためには、GlobalAlloc traitを実装したアロケータを#[global_allocator]アトリビュートを付けて定義します。
アロケータは次のように定義されています。

// Set up the global allocator
#[global_allocator]
static ALLOCATOR: mem::KernelAllocator = mem::KernelAllocator::new();

KernelAllocatorの内部を追っていきます。重要なのはGlobalAlloc traitの実装です。

#[derive(Default)]
pub struct KernelAllocator;

impl KernelAllocator {
    pub const fn new() -> Self {
        Self {}
    }
}

// Use shim functions to avoid hardcoding the GFP_KERNEL constant
#[allow(dead_code)]
extern "C" {
    fn kmalloc_c(size: usize) -> *mut u8;
    fn kfree_c(ptr: *mut u8);
    fn krealloc_c(ptr: *mut u8, size: usize) -> *mut u8;
}

unsafe impl GlobalAlloc for KernelAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        // A side effect of the buddy allocator is that allocations are aligned to
        // the power-of-two that is larger than the allocation size. So if the
        // request needs to be aligned to something larger than the allocation size,
        // we can just pass max(size, align) to kmalloc to get something reasonable
        // at the cost of a few extra wasted bytes.
        use core::cmp::max;
        let p = kmalloc_c(max(layout.size(), layout.align()));
        if p.is_null() {
            0 as *mut u8            
        } else {
            p
        }
    }

    unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
        kfree_c(ptr);
    }
}

alloc()では、kmalloc_c()を呼び出しています。これはC言語のkmallocのラッパーです。

/*
 * Re-exported memory management functions
 */
void* kmalloc_c(size_t size) {
  return kmalloc(size, GFP_KERNEL);
}

この薄いラッパー1枚を作るだけで、VecやBoxなどの便利機能が解禁となるわけです。これは朗報ですね!

CONFIG

Rust側の初期化処理を再掲載します。

// Entry points
#[no_mangle]
pub extern "C" fn rust_mod_init() -> i32 {
    print!("Panic probability: {}/{}\n", CONFIG.lock().chance, MAX_RAND);
    0
}

CONFIG.lock().chanceはkernel panicが発生する確率です。どのように初期化されているか見ていきましょう。

lazy_static! {
    // CONFIGはRouletteConfigをMutexで包んでいます
    pub static ref CONFIG: Mutex<RouletteConfig> = Mutex::new(RouletteConfig::new());
}

#[derive(Default)]
pub struct RouletteConfig {
    pub chance: u8,
}

impl RouletteConfig {
    pub fn new() -> Self {
        // MIN_RAND(0)からMAX_RAND(100)の範囲で乱数を生成します
        let chance = RNG.lock().gen_range(MIN_RAND, MAX_RAND);
        RouletteConfig { chance: chance }
    }
}

正確には、0~99の範囲で乱数を生成します。APIの解説は次の通りです。

Generate a random value in the range [low, high), i.e. inclusive of low and exclusive of high.

RNGは次のように定義されています。JitterRngという乱数生成器をMutexで包んでいます。

lazy_static! {
    static ref RNG: Mutex<rand::JitterRng> = Mutex::new(rand::JitterRng::new_with_timer(|| unsafe { nanosecond_timer_c() }));
}

JitterRngはCPU実行時間のジッタとメモリアクセスのジッタとを使った、乱数ジェネレータ、とのことです。
new_with_timer()でインスタンスを生成しています。これは、no_std環境でJitterRngを使う場合のAPIです。

pub fn new_with_timer(timer: fn() -> u64) -> JitterRng

Create a new JitterRng. A custom timer can be supplied, making it possible to use JitterRng in no_std environments.

new_with_timer()で渡しているクロージャでは、C言語のnanosecond_timer_c()を呼んでいます。
この中ではjiffiesを取得します。

/*
 * This isn't really a nanosecond timer but its close enough.
 */
u64 nanosecond_timer_c(void) {
  return get_jiffies_64();
}

少し整理すると、RNGはMutexで包まれた乱数ジェネレータです。
RNG.gen_range(MIN_RAND, MAX_RAND)を呼び出すと、クロージャでjiffiesを取得し、0から99の範囲で乱数を生成します。
CONFIGはインスタンス生成時に、RNG.get_range()を呼び出し、panic確率であるchanceを初期化します。

終了処理は特に難しいことをしていないので、省略します。

ロシアンルーレット実行

それでは、肝心のロシアンルーレット実行部分を見ていきます。
記事の最初の方でやった通り、/dev/kernel-rouletteをcatしたとき(より正確にはopen()呼び出し時)に乱数を生成し、chanceより小さな値が出るとpanicします。

$ cat /dev/kernel-roulette
Survived... sampled value is 86, which is >= 49
$ cat /dev/kernel-roulette
Segmentation fault

残念なことに、デバイスファイルの操作はほとんどC言語で処理しています。
まず、キャラクタデバイスですが、_mod_init()で登録していました。

static int _mod_init(void) {

  // Register a character device
  rl_dev_major_num = register_chrdev(0 /* allocate a major number */, rl_device_name, &rl_driver_fops);
...

キャラクタデバイスドライバには、open(), release(), read()が実装されています。

static int rl_device_open(struct inode* inode, struct file* filp);
static int rl_device_release(struct inode* inode, struct file* filp);
static ssize_t rl_device_read(struct file* filp,    /* see include/linux/fs.h   */
                              char __user* buffer,  /* buffer to fill with data */
                              size_t length,    /* length of the buffer  */
                              loff_t* offset /* the file offset */);
static struct file_operations rl_driver_fops = {
  .read = rl_device_read,
  .open = rl_device_open,
  .release = rl_device_release,
};
static int rl_dev_major_num = 0;
static const char rl_device_name[] = "kernel-roulette";

release()とread()からはRustを呼んでいないため、open()だけ解説します。
Rustを呼んでいるのは、sample()の部分です。

static int rl_device_open(struct inode* inode, struct file* filp) {
  u8 sampled;
  struct rl_state* state;
...
  // Get a sample from Rust
  sampled = sample(); // may panic!
...
  // read()で読みだすデータをここで作成します
  // Format data and store in string
  snprintf(state->data, MAX_BUFFER_SIZE, "Survived... sampled value is %d, which is >= %d\n", sampled, get_chance());
...
  // Set private_data to the state we allocated
  filp->private_data = state;
  return SUCCESS;
}

Rustのsample()は次の通りです。

#[no_mangle]
pub extern "C" fn sample() -> u8 {
    let sampled = RNG.lock().gen_range(MIN_RAND, MAX_RAND);
    if sampled < CONFIG.lock().chance {
        panic!("Boom!");
    } else {
        sampled
    }
}

新たに生成した乱数が、chanceより小さければ、”Boom”を出力して、panicします。
panic_handlerは次のようになっています。

#[panic_handler]
#[no_mangle]
pub fn rust_begin_panic(info: &PanicInfo) -> ! {
    // Print the file and line number
    if let Some(location) = info.location() {
        println!("Rust panic @ {}:{}",
            location.file(), location.line());
    }

    // Print the message and a newline
    if let Some(message) = info.message() {
        println!("{}", message);
    }

    unsafe {
        // In a real kernel module, we should use abort() instead of panic()
        abort() // replace with panic_c() if you want
    }
}

最終的にC言語のabort()を呼んでいます。abort()はBUG()を呼び出します。

/*
 * Define the abort() function. We use the BUG() macro, which generates a ud2 instruction.
 */
void abort(void) {
  BUG();
}

ちなみに、abort()の代わりに、panic_c()を呼び出すと…

void panic_c(void) {
  panic("Panic!");
}

本当にフリーズします。

以上で主要な処理の解析は完了です。
いかがでしたでしょうか。

まとめ

    • Linux kernel moduleからRustを呼べます。Rustをライブラリでビルドしておき、C言語のkernel module objectとリンクするだけです。

 

    • kmalloc()のラッパーを用意することで、Vecなど、ヒープ領域を使う機能が使えます。

 

    • printk()のラッパーを用意することで、print!マクロを作って、標準ライブラリのように使えます。

 

    残念ながらデバイスファイルの操作はほとんどC言語でした。

参考

kernel-roulette

广告
将在 10 秒后关闭
bannerAds