comprehensive-rust 裸机课程:在 no_std AArch64 程序中初始化并使用基于 `log` crate 的 UART Logger 📅 发布时间:2026/9/10 18:15:49 👁 浏览次数: comprehensive-rust 裸机课程在 no_std AArch64 程序中初始化并使用基于logcrate 的 UART Logger【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust导读本指南聚焦于 comprehensive-rust 课程中Android Platform ServicesAPS裸机章节的日志系统落地环节在无操作系统、无标准库no_std、no_main的 AArch64 裸机程序里如何把 Rust 生态标准的log日志宏info!、error!等与自定义的 PL011 UART 驱动桥接起来。读完本文你将掌握Logtrait 的实现思路、logger::init的初始化时机与返回值处理并能在 QEMU 中通过make qemu_logger直接运行验证看到日志输出与 panic 详情上报。背景为什么裸机程序需要日志框架在src/bare-metal/aps这一课程章节中程序运行在 AArch64 虚拟机的裸机环境中没有内核、没有文件系统、没有标准输出。此前课程通过 UART 驱动 直接向串口写字符串例如 main_improved.rs 中writeln!(uart, ...)的用法但这种方式存在明显不足每个模块都要持有Uart实例并手动拼装writeln!没有日志级别trace / debug / info / warn / error的概念无法按严重程度过滤输出panic 时无法统一地把现场信息输出到串口。课程给出的解决方案见 logging.md是实现 Rust 生态通用的logcrate 中的Logtrait把日志记录重定向到 UART 串口。这样代码中就可以直接使用熟悉的info!、error!等宏行为与其他 Rust 项目保持一致。核心实现实现Logtrait 并桥接 UART日志框架的核心代码位于 logger.rs课程文档 logging.md 将其完整列出。整体思路是定义一个全局的Logger结构体内部持有一个受自旋锁保护的、可选的 UART 实例use crate::pl011::Uart; use core::fmt::Write; use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; use spin::mutex::SpinMutex; static LOGGER: Logger Logger { uart: SpinMutex::new(None) }; struct Logger { uart: SpinMutexOptionUartstatic, }关键设计点全局静态单例LOGGER是一个static通过spin::mutex::SpinMutex自旋锁保护内部状态满足Log::log需要以self调用的约束同时保证多核/中断上下文下的互斥访问。spin 0.12.0在 examples/Cargo.toml 中被声明为依赖。OptionUartstaticUART 实例并非在编译期固定而是延迟到init时注入Option让尚未初始化成为显式状态。PL011 UARTUart来自同目录的 pl011.rs封装了基于safe_mmio的UniqueMmioPointer寄存器访问并实现了core::fmt::Writetrait因此可以用writeln!输出格式化文本。Logtrait 的三个方法impl Log for Logger { fn enabled(self, _metadata: Metadata) - bool { true } fn log(self, record: Record) { writeln!( self.uart.lock().as_mut().unwrap(), [{}] {}, record.level(), record.args() ) .unwrap(); } fn flush(self) {} }enabled返回true表示所有日志级别均被接受不做额外过滤过滤工作交给set_max_level完成。log加锁拿到Uart的可变引用后以[级别] 内容的格式通过writeln!写入串口。record.level()输出如INFO、ERROR的级别名record.args()输出格式化后的消息正文。flushUART 是同步写出的无需缓冲刷新因此为空实现。init把 UART 注入并注册为全局 logger/// Initialises UART logger. pub fn init( uart: Uartstatic, max_level: LevelFilter, ) - Result(), SetLoggerError { LOGGER.uart.lock().replace(uart); log::set_logger(LOGGER)?; log::set_max_level(max_level); Ok(()) }init做了三件事LOGGER.uart.lock().replace(uart)把传入的 UART 实例放入全局状态replace返回旧的Option此处无需关心log::set_logger(LOGGER)?把LOGGER注册为进程全局日志器失败返回SetLoggerError例如重复初始化时log::set_max_level(max_level)设定全局最大日志级别低于该级别的宏调用将被logcrate 快速跳过。初始化后再使用main 中的完整流程关联文档 using.md 明确指出使用日志宏之前必须先初始化 logger。完整示例见 main_logger.rs#![no_main] #![no_std] mod asm; mod exceptions; mod logger; mod pl011; use crate::pl011::Uart; use core::panic::PanicInfo; use core::ptr::NonNull; use log::{LevelFilter, error, info}; use safe_mmio::UniqueMmioPointer; use smccc::Hvc; use smccc::psci::system_off; /// Base address of the primary PL011 UART. const PL011_BASE_ADDRESS: NonNullpl011::Registers NonNull::new(0x900_0000 as _).unwrap(); // SAFETY: There is no other global function of this name. #[unsafe(no_mangle)] extern C fn main(x0: u64, x1: u64, x2: u64, x3: u64) { // SAFETY: PL011_BASE_ADDRESS is the base address of a PL011 device, and // nothing else accesses that address range. let uart unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) }; logger::init(uart, LevelFilter::Trace).unwrap(); info!(main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})); assert_eq!(x1, 42); system_off::Hvc().unwrap(); } #[panic_handler] fn panic(info: PanicInfo) - ! { error!({info}); system_off::Hvc().unwrap(); loop {} }使用步骤拆解构造 UARTPL011 的基地址为0x900_0000QEMUvirt平台的串口地址通过NonNull::new(...).unwrap()包装为非空指针再交给Uart::new与safe_mmio的UniqueMmioPointer建立寄存器视图。这个 unsafe 操作的 Safety 注释是该地址确实是一个 PL011 设备且没有其他代码访问这段地址空间。初始化 loggerlogger::init(uart, LevelFilter::Trace).unwrap()。注意LevelFilter::Trace是最高过滤级别不丢弃任何日志init返回Result(), SetLoggerError此处直接unwrap。使用日志宏info!(main({x0:#x}, ...))把入口参数以十六进制格式记录到串口。业务逻辑assert_eq!(x1, 42)模拟一个可能失败的检查——它由 QEMU 端以参数x1 42启动见下文的 Makefile 与汇编入口约定。关机通过smccccrate 的 PSCIsystem_off经 HVC 调用优雅关闭虚拟机。panic handler让崩溃也能被记录这是本示例最有价值的改进之一#[panic_handler]不再是无输出的死循环而是先调用error!({info})把 panic 详情消息与位置写入 UART再执行system_off关机。关联文档 using.md 特别强调our panic handler can now log details of panics。由于error!宏在no_std下依然可用logcrate 是纯库不依赖标准库而PanicInfo实现了Display所以error!({info})即可输出完整信息。这为后续调试裸机程序提供了崩在何处、为何而崩的第一手串口证据。对比此前 main_improved.rs 中直接writeln!(uart, ...)的写法接入 logger 后 panic handler 无需持有Uart实例即可通过全局LOGGER间接访问串口这正是Logtrait 抽象的价值所在。两个 unwrap 为什么必然成功在logger.rs中log方法里有两次unwrapself.uart.lock().as_mut().unwrap(), // 第一次 ... .wrap(); // 第二次课程文档 logging.md 的补充说明给出理由第一次 unwraplog::set_logger被调用之前LOGGER.uart就已经被init里的replace(uart)填充为Some也就是说先注入 UART再注册 logger因此log被调用时as_mut()必然得到Some。第二次 unwrapUart::write_str的实现见 pl011.rs逐个字节写入并固定返回Ok(())——它不会返回fmt::Error因此writeln!的Result也必然为Ok。这两点保证了在初始化正确的前提下日志路径不会意外 panic。在 QEMU 中运行验证关联文档 using.md 给出了运行方式在src/bare-metal/aps/examples目录下执行make qemu_logger。对应 Makefile 中的目标见 examples/Makefileqemu_logger: logger.bin qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $ -s执行链路如下logger.bin依赖buildcargo build随后cargo objcopy --bin logger -- -O binary logger.bin把 ELF 转成裸二进制镜像Makefileqemu-system-aarch64以virt机器模型、maxCPU 启动串口映射到宿主终端-serial mon:stdio并挂载调试端口-s等价于-gdb tcp::1234便于 GDB 接入程序从汇编入口 entry.S 启动经由运行库初始化后调用main随后依次输出INFO [main(...)]日志、执行断言最后 PSCI 关机。预期输出运行后宿主终端应看到形如[INFO] main(0x0, 0x2a, 0x0, 0x0)其中0x2a正是十进制 42说明assert_eq!(x1, 42)通过。若将断言故意改错则会看到[ERROR] panicked at ...输出后再关机——这就是 panic 日志能力的直接体现。依赖与构建配置要点该示例是独立 workspaceexamples/Cargo.tomllogger可执行文件对应src/main_logger.rs。与日志相关的依赖依赖版本作用log0.4.30提供Logtrait、LevelFilter、info!/error!等宏spin0.12.0提供SpinMutex保护全局LOGGER状态safe-mmio0.3.0提供UniqueMmioPointer与寄存器字段访问PL011 驱动基础arm-pl011-uart0.5.0课程文档中提到的 PL011 参考实现smccc0.2.3提供Hvc与psci::system_off用于关闭虚拟机edition 2024整个 workspacepublish false仅供课程本地构建。需要注意这是裸机程序main_logger.rs声明了#![no_main]与#![no_std]main是一个#[unsafe(no_mangle)]的 C ABI 函数由汇编入口以 x0–x3 四个参数调用与 inline-assembly.md 中的约定一致。小结通过Logtrait 实现 logger::init初始化 panic handler 记录课程 APS 日志章节 与 使用指南 共同展示了一条清晰的 no_std 日志落地路径用SpinMutexOptionUart构造全局单例 logger实现Logtrait 的三个方法在入口处先logger::init(uart, LevelFilter::Trace)注入 UART 并注册再使用任何日志宏panic handler 中通过error!({info})把崩溃信息输出到串口在src/bare-metal/aps/examples下执行make qemu_logger一键编译并运行验证。这套模式把 Rust 标准日志生态无缝带入了无操作系统环境是后续编写更复杂裸机组件如内存管理、驱动初始化时可复用的基础设施。【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考