实验一 环境配置
安装工具链
注意
Linux请使用ubuntu 18以上的版本开展本实验。
安装rust
如果你已经安装了一个版本的Rust,需补充安装相关工具:
$ cargo install cargo-binutils rustfilt
如果你想要全新安装:
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ source $HOME/.cargo/env
$ cargo install cargo-binutils rustfilt
如果你想安装指定的版本,如nightly-2021-11-20:
$ rustup install nightly-2021-11-20
查看当前项目使用的rust版本
$ rustc -V
$ rustup show
注意
本系列实验需要nightly版本,可以将rust默认设置为stable或nightly版本。由于rust特性一直在发展,最好选择与nightly-2021-11-20比较接近的版本,而不是最新版和稳定版。
$ rustup default stable-xxx版本
$ rustup default nightly-xxx版本
或者仅将当前项目设为nightly
$ rustup override set nightly-xxx版本
提示
如果你使用 Visual Studio Code,强烈推荐你安装 Rust Analyzer 扩展
为rust增加armv8支持
列出 rust支持的目标三元组(CPU架构、平台供应者、操作系统和应用程序二进制接口ABI)
$ rustup target list
增加 armv8支持
$ rustup target add aarch64-unknown-none-softfloat
安装QEMU模拟器
请参考官网 https://wiki.qemu.org/Hosts/Linux 或者 https://wiki.qemu.org/Hosts/Mac 等进行安装。
安装交叉编译工具链 (aarch64)
Linux
$ wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.2-2020.11/binrel/gcc-arm-10.2-2020.11-x86_64-aarch64-none-elf.tar.xz
$ tar -xf gcc-arm-10*
$ cp gcc-arm-10*/bin/* /usr/local/bin/
$ rm -rf gcc-arm-10*
Mac
$ brew tap SergioBenitez/osxct
$ brew install aarch64-none-elf
创建裸机(Bare Metal)程序
由于我们的目标是编写一个操作系统,所以我们需要创建一个独立于操作系统的可执行程序,又称 独立式可执行程序(freestanding executable) 或 裸机程序(bare-metal executable) 。这意味着所有依赖于操作系统的库我们都不能使用。比如 std 中的大部分内容(io, thread, file system, etc.)都需要操作系统的支持,所以这部分内容我们不能使用。
但是,不依赖与操作系统的 rust 的语言特性 我们还是可以继续使用的,比如:迭代器、模式匹配、字符串格式化、所有权系统等。这使得 rust 依旧可以作为一个功能强大的高级语言,帮助我们编写操作系统。
用cargo创建项目
创建新项目:
$ cargo new rui_armv8_os --bin --edition 2021
小技巧
rui_armv8_os为项目名,可自行修改。
在src/下创建main.rs, panic.rs, start.s三个文件
main.rs源码
1 // 不使用标准库
2 #![no_std]
3 // 不使用预定义入口点
4 #![no_main]
5 #![feature(global_asm)]
6
7 use core::ptr;
8
9 mod panic;
10
11 global_asm!(include_str!("start.s"));
12
13 #[no_mangle] // 不修改函数名
14 pub extern "C" fn not_main() {
15 const UART0: *mut u8 = 0x0900_0000 as *mut u8;
16 let out_str = b"AArch64 Bare Metal";
17 for byte in out_str {
18 unsafe {
19 ptr::write_volatile(UART0, *byte);
20 }
21 }
22 }
备注
#![no_std]表示不使用标准库,因为标准库需要系统支持,而我们需要构建操作系统,所以构建裸金属(Bare Metal)程序。
#[no_mangle]指示编译器不修改函数名not_main,因为默认情况下编译器会修改函数名,而在start.s中_start中会通过bl not_main进行调用。
not_main函数通过ptr::write_volatile向串口输出字符,其原理将在 实验二 Hello World 进行介绍。
panic.rs源码
1 use core::panic::PanicInfo;
2
3 #[panic_handler]
4 fn on_panic(_info: &PanicInfo) -> ! {
5 loop {}
6 }
start.s源码
1 .globl _start
2 .extern LD_STACK_PTR
3 .section ".text.boot"
4
5 _start:
6 ldr x30, =LD_STACK_PTR
7 mov sp, x30
8 bl not_main
9
10 .equ PSCI_SYSTEM_OFF, 0x84000002
11 .globl system_off
12 system_off:
13 ldr x0, =PSCI_SYSTEM_OFF
14 hvc #0
备注
_start标号开始设置好栈指针后,通过bl not_main跳转到main.rs中对应函数。
LD_STACK_PTR是全局符号,在下面的aarch64-qemu.ld中定义。
关于PSCI_SYSTEM_OFF参见 [psci] 。
在项目目录下创建链接文件aarch64-qemu.ld
ENTRY(_start)
SECTIONS
{
. = 0x40080000;
.text.boot : { *(.text.boot) }
.text : { *(.text) }
.data : { *(.data) }
.rodata : { *(.rodata) }
.bss : { *(.bss) }
. = ALIGN(8);
. = . + 0x4000;
LD_STACK_PTR = .;
}
备注
ENTRY(_start)中指明入口函数为_start函数,该函数在start.s中。
通过 . = 0x40080000; 将程序安排在内存位置0x40080000开始的地方。
链接脚本中的符号LD_STACK_PTR是全局符号,可以在程序中使用(如start.s中),这里定义的是栈底的位置。
备注
链接脚本中除了组织各个段之外,还可以定义符号,链接脚本中定义的符号被添加到全局符号中
symbol = expression ; symbol += expression ;第一个表达式表示定义一个符号,第二个表达式对符号值进行操作,中间的空格是必须的
当程序和链接脚本中同时定义了变量符号时,链接脚本中的符号会覆盖掉程序中定义的符号
定义内存区域后,一个段没有显示地指定将要添加到哪个区域,将会对段的属性和区域的属性进行匹配
详情可参考 The GNU linker。此外,这里还有一个简单的 链接脚本基本介绍 可参考。
重要
链接脚本对理解操作系统的实现非常重要,所以应及早熟悉。
配置Cargo.toml
[package]
name = "rui_armv8_os"
version = "0.1.0"
edition = "2021"
authors = ["Rui Li <rui@hnu.edu.cn>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
# [build]
# 设定编译目标,cargo build --target aarch64-unknown-none-softfloat
# target = "aarch64-unknown-none-softfloat"
[dependencies]
# eh_personality语言项标记的函数,将被用于实现栈展开(stack unwinding)。
# 在使用标准库的情况下,当panic发生时,Rust将使用栈展开,来运行在栈上活跃的
# 所有变量的析构函数(destructor)——这确保了所有使用的内存都被释放。
# 如果不禁用会出现错误:language item required, but not found: `eh_personality`
# 通过下面的配置禁用栈展开
# dev时禁用panic时栈展开
[profile.dev]
panic = "abort"
# release时禁用panic时栈展开
[profile.release]
panic = "abort"
在项目目录下创建aarch64-unknown-none-softfloat.json,配置目标平台相关参数
{
"abi-blacklist": [
"stdcall",
"fastcall",
"vectorcall",
"thiscall",
"win64",
"sysv64"
],
"arch": "aarch64",
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
"disable-redzone": true,
"env": "",
"executables": true,
"features": "+strict-align,+neon,+fp-armv8",
"is-builtin": false,
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"linker-is-gnu": true,
"pre-link-args": {
"ld.lld": ["-Taarch64-qemu.ld"]
},
"llvm-target": "aarch64-unknown-none",
"max-atomic-width": 128,
"os": "none",
"panic-strategy": "abort",
"relocation-model": "static",
"target-c-int-width": "32",
"target-endian": "little",
"target-pointer-width": "64",
"vendor": ""
}
备注
pre-link-args参数指定了链接时使用我们先前创建的链接脚本。
linker参数指定了所采用的的链接器。
执行tree -L 2可以看到你的项目目录结构。最终,你的项目目录看起来应该类似下图。
编译运行
编译
$ cargo build --target aarch64-unknown-none-softfloat
或者在项目目录下新建 .cargo/config.toml,设定编译目标和参数如下
[build]
target = "aarch64-unknown-none-softfloat"
rustflags = ["-C","link-arg=-Taarch64-qemu.ld", "-C", "target-cpu=cortex-a53", "-D", "warnings"]
然后直接执行
$ cargo build
运行
$ qemu-system-aarch64 -machine virt -m 1024M -cpu cortex-a53 -nographic -kernel target/aarch64-unknown-none-softfloat/debug/rui_armv8_os
当然,你也可以使用 cargo run 来运行,但同样需要首先在 .cargo/config.toml 中进行配置,请自行查找资料。
调试支持
GDB简单调试方法
编译成功后就可以运行,这需要使用前面安装的QEMU模拟器。此外,为了查找并修正bug,我们需要使用调试工具。
QEMU进入调试,启动调试服务器,默认端口1234
$ qemu-system-aarch64 -machine virt -m 1024M -cpu cortex-a53 -nographic -kernel target/aarch64-unknown-none-softfloat/debug/rui_armv8_os -S -s
备注
qemu的参数说明:
-S freeze CPU at startup (use ‘c’ to start execution)
-s shorthand for -gdb tcp::1234
查看相关参数的作用可在命令行执行: qemu-system-aarch64 --help
,
启动调试客户端
$ aarch64-none-elf-gdb target/aarch64-unknown-none-softfloat/debug/rui_armv8_os
设置调试参数,开始调试
(gdb) target remote localhost:1234
(gdb) disassemble
(gdb) n
提示
可以安装使用 GDB dashboard 进入可视化调试界面
将调试集成到vscode
打开一个.rs文件,点击 vscode左侧的运行和调试按钮,弹出对话框选择创建 launch.json文件,增加如下配置:
{
"name": "aarch64-gdb",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/target/aarch64-unknown-none-softfloat/debug/rui_armv8_os",
"stopAtEntry": true,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"launchCompleteCommand": "exec-run",
"MIMode": "gdb",
"miDebuggerPath": "/usr/local/bin/aarch64-none-elf-gdb",
"miDebuggerServerAddress": "localhost:1234",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
在左边面板顶部选择刚添加的 aarch64-gdb 选项,点击旁边的绿色 开始调试(F5) 按钮开始调试。
在调试时,可以在调试控制台执行gdb命令,如:
查看指定地址的内存内容。在调试控制台执行 -exec x/20xw 0x40000000 即可,其中 x表示查看命令,20表示查看数量,x表示格式,可选格式包括 Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),t(binary), f(float), a(address), i(instruction), c(char) and s(string).Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).,最后的 w表示字宽,b表示单字节,h表示双字节,w表示四字节,g表示八字节。还可以是指令:-exec x/20i 0x40000000; 字符串:-exec x/20s 0x40000000
显示所有寄存器。-exec info all-registers
查看寄存器内容。-exec p/x $pc
修改寄存器内容。-exec set $x24 = 0x5
修改指定MMIO 寄存器的内容。 -exec call core::ptr::write_volatile(0x08010004 as *const u32, 0x1)
总之,可以通过 -exec这种方式可以执行所有的 gdb调试指令。
提示
集成到vscode的调试方法不支持调试类似start.s的汇编代码,如需要调试.s文件,需采用最开始的 GDB简单调试方法。
qemu执行结果