實(shí)戰(zhàn):基于 aarch64-rt 編寫 AArch64 異常處理器(Exception Handlers))
comprehensive-rust 裸機(jī)實(shí)戰(zhàn)基于 aarch64-rt 編寫 AArch64 異常處理器Exception Handlers【免費(fèi)下載鏈接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust本篇技術(shù)指南來自 Google Android 團(tuán)隊(duì)的 Rust 課程項(xiàng)目 comprehensive-rust 的裸機(jī)bare-metal實(shí)驗(yàn)部分聚焦于aarch64-rtcrate 提供的異常處理抽象它用一個(gè)ExceptionHandlerstrait 和一個(gè)exception_handlers!宏替代手工編寫異常向量表的匯編代碼讓開發(fā)者可以純 Rust 地定義同步異常Synchronous、IRQ、FIQ 與 SError 的處理邏輯。讀完本文你將掌握 AArch64 異常向量表的 16 項(xiàng)布局、aarch64-rt的 trait 宏使用范式、RegisterStateRef的底層含義以及如何在 QEMU 上運(yùn)行完整可驗(yàn)證的示例。背景AArch64 的 16 項(xiàng)異常向量表在動(dòng)手使用aarch64-rt之前需要先理解它背后封裝的硬件模型。AArch64 架構(gòu)定義了一張包含16 個(gè)條目的異常向量表由4 種異常類型×4 種來源狀態(tài)組合而成異常類型同步異常synchronous、IRQ、FIQ、SError來源狀態(tài)當(dāng)前異常級(jí)別current EL使用 SP0當(dāng)前異常級(jí)別使用 SPx當(dāng)前棧指針低異常級(jí)別lower EL使用 AArch64低異常級(jí)別使用 AArch32。這一模型在課程文檔 src/bare-metal/aps/exceptions.md 中作了明確說明。課程示例全部運(yùn)行在EL1Exception Level 1為了保持簡單示例并未區(qū)分當(dāng)前 EL 下的 SP0 與 SPx也沒有區(qū)分低 EL 下的 AArch32 與 AArch64——這意味著 16 個(gè)條目最終收斂到 8 個(gè)不同的處理函數(shù)見下文手工實(shí)現(xiàn)。異常發(fā)生時(shí)CPU 會(huì)跳轉(zhuǎn)到向量表中對(duì)應(yīng)條目硬件上下文切換需要保存通用寄存器與elr_el1、spsr_el1等系統(tǒng)寄存器隨后才能安全地進(jìn)入 Rust 代碼。手工實(shí)現(xiàn)回顧匯編 no_mangle函數(shù)在引入aarch64-rt之前課程示例用手寫匯編構(gòu)建了完整的 EL1 異常向量表。匯編文件 src/bare-metal/aps/examples/src/exceptions.S 展示了其核心結(jié)構(gòu)向量表位于.text.vector_table_el1段符號(hào)vector_table_el1通過.balign 0x800保證 2 KiB 對(duì)齊每個(gè)條目通過.balign 0x80保持 128 字節(jié)32 條指令間隔save_volatile_to_stack宏用stp指令將 x0–x18、x29、x30 壓棧并用mrs x0, elr_el1/mrs x1, spsr_el1保存返回地址與程序狀態(tài)以便支持嵌套異常時(shí)的?;厮輈urrent_exception宏將「保存易失寄存器 → 跳轉(zhuǎn) Rust 處理函數(shù) → 恢復(fù)寄存器 →eret」串成一條流水線整套保存與恢復(fù)各占 14 條指令加上跳轉(zhuǎn)/返回共 30 條指令恰好控制在 32 條指令上限之內(nèi)。對(duì)應(yīng)的 Rust 端src/bare-metal/aps/examples/src/exceptions.rs則用#[unsafe(no_mangle)] extern C導(dǎo)出 8 個(gè)全局處理函數(shù)use log::error; use smccc::Hvc; use smccc::psci::system_off; // SAFETY: There is no other global function of this name. #[unsafe(no_mangle)] extern C fn sync_current(_elr: u64, _spsr: u64) { error!(sync_current); system_off::Hvc().unwrap(); } // irq_current / fiq_current / serror_current / sync_lower / irq_lower // fiq_lower / serror_lower 結(jié)構(gòu)相同僅日志文本不同每個(gè)函數(shù)接收elr_el1與spsr_el1作為前兩個(gè)參數(shù)由匯編中的mrs指令準(zhǔn)備好示例中只是記錄日志并調(diào)用 PSCIsystem_off關(guān)機(jī)——因?yàn)檎n程預(yù)期這些異常在實(shí)際運(yùn)行中不會(huì)發(fā)生。aarch64-rt抽象trait 宏手工方案可行但繁瑣每增加一個(gè)處理器都要維護(hù)兩處代碼匯編表 Rust 函數(shù)。aarch64-rt將這個(gè)模式抽象為兩個(gè)構(gòu)件正如核心文檔 src/bare-metal/aps/aarch64-rt/exceptions.md 所述aarch64-rtprovides a trait to define exception handlers, and a macro to generate the assembly code for the exception vector to call them.ExceptionHandlerstrait用于定義異常處理器exception_handlers!宏生成調(diào)用這些處理器的異常向量匯編代碼。trait 的每個(gè)方法都有默認(rèn)實(shí)現(xiàn)——默認(rèn)行為是直接panic因此對(duì)于預(yù)期不會(huì)發(fā)生的異常可以放心省略對(duì)應(yīng)的方法代碼量大幅精簡。完整代碼示例核心文檔內(nèi)嵌的示例來自 src/bare-metal/aps/examples/src/exceptions_rt.rs這里給出完整版本use aarch64_rt::{ExceptionHandlers, RegisterStateRef, exception_handlers}; use log::error; use smccc::Hvc; use smccc::psci::system_off; struct Handlers; impl ExceptionHandlers for Handlers { extern C fn sync_current(_state: RegisterStateRef) { error!(sync_current); system_off::Hvc().unwrap(); } extern C fn irq_current(_state: RegisterStateRef) { error!(irq_current); system_off::Hvc().unwrap(); } extern C fn fiq_current(_state: RegisterStateRef) { error!(fiq_current); system_off::Hvc().unwrap(); } extern C fn serror_current(_state: RegisterStateRef) { error!(serror_current); system_off::Hvc().unwrap(); } } exception_handlers!(Handlers);使用要點(diǎn)定義一個(gè)空結(jié)構(gòu)體如Handlers為它實(shí)現(xiàn)ExceptionHandlers每個(gè)方法必須是extern C調(diào)用約定參數(shù)為RegisterStateRef只覆蓋你關(guān)心的異常類型其余繼承默認(rèn)的 panic 行為最后調(diào)用exception_handlers!(Handlers)生成向量表。相比手工版本這里不再需要#[unsafe(no_mangle)]也不用手動(dòng)約定elr/spsr參數(shù)位置宏會(huì)負(fù)責(zé)把匯編層的寄存器狀態(tài)包裝好后交給 Rust 方法。底層原理宏如何生成異常向量exception_handlers!宏并非魔法它在背后做了和手工exceptions.S相同的工作。文檔明確指出宏會(huì)生成一個(gè)global_asm!塊內(nèi)含完整的異常向量用于跳轉(zhuǎn)到 Rust 代碼——這與之前手寫的exceptions.S是同一套思路RegisterStateRef是對(duì)棧幀的引用封裝——匯編代碼在異常發(fā)生時(shí)把寄存器值保存到棧上RegisterStateRef就指向這塊保存區(qū)。RegisterStateRef的兩種典型用途提取參數(shù)當(dāng)異常來自低 ELlower EL時(shí)例如收到一個(gè) SMC安全監(jiān)控調(diào)用或 HVC虛擬機(jī)管理調(diào)用可以從保存的寄存器棧幀中讀出調(diào)用方傳入的參數(shù)改寫恢復(fù)值在處理函數(shù)返回、寄存器從棧上恢復(fù)之前修改棧幀中保存的寄存器值從而改變eret返回后 CPU 的狀態(tài)——這是實(shí)現(xiàn)系統(tǒng)調(diào)用參數(shù)回寫、返回值注入等功能的基石。也就是說aarch64-rt把「匯編負(fù)責(zé)保存/恢復(fù)、Rust 負(fù)責(zé)策略」的邊界固定下來RegisterStateRef就是跨越這條邊界的窗口。兩種實(shí)現(xiàn)方式對(duì)比維度手工實(shí)現(xiàn)aarch64-rt實(shí)現(xiàn)向量表手寫exceptions.S匯編exception_handlers!宏生成global_asm!Rust 端#[unsafe(no_mangle)] extern C函數(shù)impl ExceptionHandlerstrait 方法參數(shù)傳遞顯式_elr: u64, _spsr: u64RegisterStateRef寄存器棧幀引用未處理異常必須為全部 16 項(xiàng)提供函數(shù)trait 默認(rèn)實(shí)現(xiàn)直接 panic可省略代碼量匯編 8 個(gè)函數(shù)1 個(gè) impl 塊 1 個(gè)宏調(diào)用在完整示例中集成entry!與initial_pagetable!異常處理器不是孤立存在的課程示例 src/bare-metal/aps/examples/src/main_rt.rs 展示了它與aarch64-rt其他能力的組合使用。該文件頂部通過mod exceptions_rt;引入上文實(shí)現(xiàn)的異常模塊然后用initial_pagetable!宏在 Rust 中定義初始靜態(tài)頁表取代此前手工idmap.S的做法把 1 GiB 設(shè)備內(nèi)存、1 GiB 普通內(nèi)存以及 256 GiB 處的另一塊設(shè)備內(nèi)存放入恒等映射用entry!(main)宏標(biāo)記入口函數(shù)main(x0, x1, x2, x3)接收啟動(dòng)時(shí)由匯編傳入的 4 個(gè)參數(shù)直接使用arm-pl011-uartcrate 的Uart驅(qū)動(dòng)輸出日志而不是自己實(shí)現(xiàn) PL011panic_handler同樣調(diào)用system_off::Hvc()關(guān)機(jī)。異常處理器、頁表、入口點(diǎn)與 UART 驅(qū)動(dòng)全部由aarch64-rt生態(tài)接管這正是該 crate 的設(shè)計(jì)目標(biāo)——如 src/bare-metal/aps/aarch64-rt.md 所說它提供了此前手工實(shí)現(xiàn)的匯編入口點(diǎn)和異常向量。依賴版本可在 src/bare-metal/aps/examples/Cargo.toml 中確認(rèn)aarch64-rt 0.4.3、aarch64-paging 0.12.1、arm-pl011-uart 0.5.0、smccc 0.2.3、log 0.4.30。在 QEMU 中運(yùn)行驗(yàn)證在src/bare-metal/aps/examples目錄下Makefilesrc/bare-metal/aps/examples/Makefile提供了現(xiàn)成的運(yùn)行目標(biāo)make qemu_rt該目標(biāo)依次執(zhí)行cargo build→cargo objcopy --bin rt -- -O binary rt.bin→ 啟動(dòng) QEMUqemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel rt.bin -s模擬器使用virt機(jī)器與maxCPU通過串口輸出日志。在正常運(yùn)行路徑下程序打印main(...)參數(shù)后調(diào)用 PSCI 關(guān)機(jī)若異常被觸發(fā)例如訪問非法地址sync_current等處理器會(huì)輸出sync_current之類的錯(cuò)誤日志后再關(guān)機(jī)——這為驗(yàn)證異常處理邏輯提供了直接的觀測手段。異常處理器與并發(fā)模型Send / Sync 視角文檔還給出了一個(gè)重要的思維模型異常處理器與主執(zhí)行上下文之間的關(guān)系可以類比為不同的線程。與線程間通信一樣兩者之間能共享什么數(shù)據(jù)由Send與Sync決定。舉例來說如果某個(gè)值要在異常處理器與程序其他部分之間共享且它是Send但不是Sync例如RefCell這類內(nèi)部可變但非線程安全的類型就需要把它包裝進(jìn)Mutex之類的同步原語并放入static中如果異常處理器與主流程并發(fā)訪問共享狀態(tài)必須保證訪問的原子性否則可能產(chǎn)生數(shù)據(jù)競爭——這在裸機(jī)環(huán)境同樣成立因?yàn)闆]有操作系統(tǒng)替你兜底。記住這一點(diǎn)可以避免寫出在異常上下文中觸發(fā)未定義行為的共享邏輯。實(shí)踐要點(diǎn)小結(jié)只實(shí)現(xiàn)需要的異常ExceptionHandlers的默認(rèn)方法會(huì) panic未預(yù)期異??梢哉w省略保持extern C約定trait 方法必須標(biāo)注extern C參數(shù)類型為RegisterStateRef用好RegisterStateRef它是讀寫寄存器棧幀的入口是實(shí)現(xiàn) SMC/HVC 參數(shù)提取與返回值注入的關(guān)鍵向量表對(duì)齊與指令預(yù)算若你仍需要手寫匯編注意 2 KiB 表對(duì)齊、128 字節(jié)條目間隔以及 32 條指令上限詳見 src/bare-metal/aps/examples/src/exceptions.S 中的注釋共享狀態(tài)考慮 Send/Sync異常上下文近似線程共享數(shù)據(jù)需要合適的同步原語。aarch64-rt將課程中手工實(shí)現(xiàn)的匯編異常向量、入口點(diǎn)、初始頁表一并收編為安全、可組合的 Rust 抽象讓開發(fā)者把精力聚焦在異常處理策略本身——這也是在 bare-metal 場景下用 Rust 寫出可維護(hù)系統(tǒng)軟件的一條清晰路徑。進(jìn)一步可閱讀 src/bare-metal/aps/exceptions.md 了解手工方案的完整對(duì)比以及 src/bare-metal/aps/aarch64-rt.md 了解該 crate 的入口點(diǎn)與頁表能力?!久赓M(fèi)下載鏈接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考