## 簡述 > 這份筆記是 [C2Rust for Linux Kernel](/IR2_2mL7QTe_riQJzbPl-g) 的一部份。 這份筆記延伸了 [C2Rust 無法處理的核心標頭檔](/SDMcAZuSQSCo8gihRnnLug) 的實驗結果,研究 RFL 如何處理以下問題: ```c ./include/linux/bottom_half_toy.h:19:24: warning: c2rust: Cannot translate GNU address of label expression 19 | __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET); | ^~~~~~~~~ ./include/linux/kernel.h:58:64: note: expanded from macro '_THIS_IP_' 58 | #define _THIS_IP_ ({ __label__ __here; __here: (unsigned long)&&__here; }) | ^~ ``` 實驗發現 RFL 不需要處理這個問題,因為它直接透過 bindgen 使用 C 語言提供的功能。另外也探究了 SeKVM 的 Rust 版本 KrustVM 如何處理該問題,結果和 RFL 一樣,它直接呼叫 C 語言的 FFI 介面。因此 C2Rust 轉譯失敗的原因是它會嘗試轉譯直到最底層的所有程式,而 RFL 與 KrustVM 都將最底層交給 C 語言。 ## 實驗前想法 使用 C2Rust 轉譯 SeKVM 時在 `<linux/kernel.h>` 中遇到一段無法處理的程式碼,然而 RFL 與 KrustVM 都可以成功編譯,我認為問題的原因可能有: 1. SeKVM 其實不會用到 `<kernel.h>` 中那一段錯誤的程式碼,所以 KrustVM 也不需要實作該段程式碼的 Rust 版本。但我沒有辦法測試這一點,如果我把那一段程式碼從 `<kernel.h>` 刪掉,在轉譯時還是會有標頭需要用到那一段程式碼,儘管 SeKVM 不需要用到。 2. RFL 我認為比較有可能會用到那段程式碼,因為 RFL 是更全面的函式庫,需要提供更完整的功能,更可能包括那段程式碼所提供的功能。 3. RFL 與 KrustVM 都有使用 bindgen,我猜測它們可能都沒有直接使用到 `<linux/kernel.h>`,而是直接呼叫 bindgen 所提供的功能。 ## 無法處理的程式碼在哪裡被使用? 找看看 C2Rust 無法處理的程式碼是在哪裡被使用的,根據 cross referencer 以及之前從 `hypsec.h` 找到此錯誤的路徑,找到了 `<linux/bottom_half.h>`: ```c=18 static inline void local_bh_disable(void) { __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET); } ``` 在 `kernel/locking/spinlock.c` 中使用了 `local_bh_disable()`: ```c=67 #define BUILD_LOCK_OPS(op, locktype) \ void __lockfunc __raw_##op##_lock(locktype##_t *lock) \ { \ ... } \ \ unsigned long __lockfunc __raw_##op##_lock_irqsave(locktype##_t *lock) \ { \ ... } \ \ void __lockfunc __raw_##op##_lock_irq(locktype##_t *lock) \ { \ ... } \ \ void __lockfunc __raw_##op##_lock_bh(locktype##_t *lock) \ { \ unsigned long flags; \ \ /* */ \ /* Careful: we must exclude softirqs too, hence the */ \ /* irq-disabling. We use the generic preemption-aware */ \ /* function: */ \ /**/ \ flags = _raw_##op##_lock_irqsave(lock); \ local_bh_disable(); \ local_irq_restore(flags); \ } ``` ```c=126 BUILD_LOCK_OPS(spin, raw_spinlock); ``` 也就是說在 `kernel/locking/spinlock.c` 中定義了以下四個關於 spinlock 的函式: ```c void __lockfunc __raw_spin_lock(raw_spinlock_t *lock); unsigned long __lockfunc __raw_spin_lock_irqsave(raw_spinlock_t *lock); void __lockfunc __raw_spin_lock_irq(raw_spinlock_t *lock); void __lockfunc __raw_spin_lock_bh(raw_spinlock_t *lock); ``` 這些函式是 spinlock 會間接使用到的函式,因此可以得知包含 spinlock 的程式碼會讓 C2Rust 無法轉換。 ## RFL 如何解決此問題? 在 RFL 中 `spin_lock()` 的實作方式是直接呼叫 bindgen 產生的 binding,在 `rust/kernel/sync/lock/spinlock.rs` 中: ```rs=106 unsafe fn lock(ptr: *mut Self::State) -> Self::GuardState { // SAFETY: The safety requirements of this function ensure that `ptr` points to valid // memory, and that it has been initialised before. unsafe { bindings::spin_lock(ptr) } } ``` 在 `rust/bindings/bindings_helpers_generated.rs` 中: ```rs=100 extern "C" { #[link_name="rust_helper_spin_lock"] pub fn spin_lock(lock: *mut spinlock_t); } ``` ## KrustVM 如何解決? `sekvmrs/src/spinlock.rs`: ```rs=59 impl<T: ?Sized> SpinLock<T> { /// Locks the spinlock and gives the caller access to the data protected by it. Only one thread /// at a time is allowed to access the protected data. pub fn lock(&mut self) -> Guard<'_, Self> { // SAFETY: We'll acquire the spinlock. unsafe { <Self as Lock>::lock(self); Guard::new(self) } } } // SAFETY: The underlying kernel `spinlock_t` object ensures mutual exclusion. unsafe impl<T: ?Sized> Lock for SpinLock<T> { type Inner = T; unsafe fn lock(&mut self) { unsafe { acquire_lock(&mut self.spin_lock as *const b_arch_spinlock_t); } } unsafe fn unlock(&mut self) { unsafe { release_lock(&mut self.spin_lock as *const b_arch_spinlock_t) }; } unsafe fn locked_data(&self) -> &UnsafeCell<T> { &self.data } } ``` KrustVM 中的 spinlock 也是將底層的實作交給用 C 語言: ```rs=6 extern "C" { fn acquire_lock(lock: *const b_arch_spinlock_t); fn release_lock(lock: *const b_arch_spinlock_t); } ``` 這兩個 C 函式定義於 `arch/arm64/sekvm/hypsec_for_rust.c`: ```c=49 /* lock related op */ void __hyp_text inline acquire_lock(b_arch_spinlock_t* lock) { stage2_spin_lock(lock); } void __hyp_text inline release_lock(b_arch_spinlock_t *lock) { stage2_spin_unlock(lock); } ``` ### 小結 RFL 將 spinlock 實作交給 C 語言程式,因此不需要處理特殊的 C 語言語法。 ## 實驗小結 Linux 核心的 spinlock 會間接使用到以下 C2Rust 無法轉譯的程式碼: ```c ./include/linux/bottom_half_toy.h:19:24: warning: c2rust: Cannot translate GNU address of label expression 19 | __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET); | ^~~~~~~~~ ./include/linux/kernel.h:58:64: note: expanded from macro '_THIS_IP_' 58 | #define _THIS_IP_ ({ __label__ __here; __here: (unsigned long)&&__here; }) | ^~ ``` 而 RFL 與 KrustVM 採取的方式並不是將 spinlock 重新用 Rust 實作,而是直接在 Rust 程式中呼叫 C binding。之所以 RFL 與 KrustVM 可以運行而 C2Rust 無法轉譯是因為前兩者不需要處理 `<linux/kernel.h>` 標頭,而 C2Rust 會處理所有相關的標頭。 我的想法是 C2Rust 要解決這個問題有三種方法: 1. C2Rust 目前會轉換所有 C 程式碼,可以嘗試和 RFL 一樣使用 bindgen 生成的 FFI,不過一個自動轉譯器很難分析要從哪一層開始呼叫 FFI。 2. 讓 C2Rust 使用 RFL,但 C2Rust 不是為 Linux 設計的所以不實際。 3. C2Rust 克服困難轉譯那段 C 程式碼,但是不確定理論上可不可行。 我認為 C2Rust 轉譯時仍有可以改進的點,像是在轉換 `BootAux.c` 時其實不需要這一段無法轉換的程式碼,C2Rust 不必處理它沒關係。