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 kernel | 4.4.0-137-generic |
distribution | Ubuntu 16.04 (Virtual Box) |
rustc | 1.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.c | Linux kernel driver。init()やopen()でRust関数を呼び出す |
io/mod.rs | printkのラッパーでprint!マクロを定義 |
mem/mod.rs | kmallocのラッパーでヒープアロケータを実装 |
lang.rs | panic_handlerおよびalloc_error_handlerを定義 |
lib.rs | init(), 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<u8>
でバッファリングします。
#[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<u8>
の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言語でした。