[rCore学习笔记 07]移除标准库依赖

改造Rust hello world

移除println!

rustc添加对裸机的支持

rustup target add riscv64gc-unknown-none-elf

detail

  1. rustup: 是Rust语言的工具链管理器,允许你安装和管理多个Rust版本以及相关工具。它还使切换编译目标变得简单,这对于跨平台开发特别有用。
  2. target add: 这是rustup的一个子命令,用于向当前的Rust工具链环境中添加额外的编译目标。这意味着之后你可以为这个目标平台编译Rust代码,即使你的开发机器不是运行在这个目标平台上的硬件。
  3. riscv64gc-unknown-none-elf: 这是一个具体的编译目标三元组,含义如下:
    • riscv64gc: 指定目标架构为64位的RISC-V,其中gc表示通用(general-purpose)并包含压缩指令集(compressed instruction set)。
    • unknown: 表示没有特定的供应商或操作系统变体,通常用于bare-metal(无操作系统)或自定义内核的开发。
    • none: 指出目标没有标准库环境,意味着编译出的程序不能依赖于像C标准库这样的系统库。
    • elf: 表示输出的可执行文件或对象文件格式为ELF(Executable and Linkable Format),这是Linux和许多类UNIX系统常用的一种文件格式。

为cargo添加配置文件

mkdir .cargo
cd .cargo
touch config

config文件内容

# os/.cargo/config
[build]
target = "riscv64gc-unknown-none-elf"

这会对于 Cargo 工具在 os 目录下的行为进行调整:现在默认会使用 riscv64gc 作为目标平台而不是原先的默认 x86_64-unknown-linux-gnu。事实上,这是一种编译器运行的开发平台(x86_64)与可执行文件运行的目标平台(riscv-64)不同的情况。我们把这种情况称为 交叉编译 (Cross Compile)。

在rs文件前添加注解

main.rs的第一行添加:

#![no_std]

这样告诉编译器不使用std库,并且转用core库.

build

cargo build
log

这说明std库支持的println!core中不支持.

warning: `/home/winddevil/workspace/os/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
   Compiling os v0.1.0 (/home/winddevil/workspace/os)
error: cannot find macro `println` in this scope
 --> src/main.rs:3:5
  |
3 |     println!("Hello, world!");
  |     ^^^^^^^

error: `#[panic_handler]` function required, but not found

error: could not compile `os` (bin "os") due to 2 previous errors

注释掉println!

main.rs的内容改成如下所示:

#![no_std]
fn main() {

    // println!("Hello, world!");

}

提供panic_handler功能对应致命错误

重新编译

cargo build

log

这里需要关注的关键点在于,对于panic_handler的需求,但是没有实现.

warning: `/home/winddevil/workspace/os/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
   Compiling os v0.1.0 (/home/winddevil/workspace/os)
error: `#[panic_handler]` function required, but not found

error: could not compile `os` (bin "os") due to 1 previous error

原因

根据官方文档:

在使用 Rust 编写应用程序的时候,我们常常在遇到了一些无法恢复的致命错误(panic),导致程序无法继续向下运行。这时手动或自动调用 `panic!` 宏来打印出错的位置,让软件能够意识到它的存在,并进行一些后续处理。 `panic!` 宏最典型的应用场景包括断言宏 `assert!` 失败或者对 `Option::None/Result::Err` 进行 `unwrap` 操作。所以Rust编译器在编译程序时,从安全性考虑,需要有 `panic!` 宏的具体实现。
在标准库 std 中提供了关于 `panic!` 宏的具体实现,其大致功能是打印出错位置和原因并杀死当前应用。但本章要实现的操作系统不能使用还需依赖操作系统的标准库std,而更底层的核心库 core 中只有一个 `panic!` 宏的空壳,并没有提供 `panic!` 宏的精简实现。因此我们需要自己先实现一个简陋的 panic 处理函数,这样才能让“三叶虫”操作系统 – LibOS的编译通过。

实现panic!

创建文件

cd os/src
touch lang_items.rs

编写接口

// os/src/lang_items.rs

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}
detail
  1. 文件作用域声明: 代码位于一个特定于操作系统的源文件(os/src/lang_items.rs)中,旨在定义语言项(lang items),这些是Rust编译器识别并需要的特殊函数实现。
  2. 引入 PanicInfo:
    • 使用 use 关键字从 core::panic 模块中导入 PanicInfo 结构体。PanicInfo 包含当Rust程序发生恐慌时的相关信息,如恐慌发生的地点、消息等。
  3. 定义恐慌处理函数:
    • #[panic_handler] 是一个属性宏,它标记了接下来定义的函数 panic 作为整个程序的恐慌处理入口点。
    • fn panic(_info: &PanicInfo) 定义了恐慌处理函数,它接收一个指向 PanicInfo 实例的引用作为参数,但在这个例子中并未使用(使用 _info 表示忽略)。
  4. 函数体内容:
    • 函数体内包含一个无限循环 loop {},这意味着当恐慌发生并调用此函数时,程序将陷入这个循环中,不再执行其他任何操作。
  5. 返回类型 -> !:
    • 函数的返回类型标注为 !,这是Rust的"never"类型,表示这个函数永远不会正常返回。这对于恐慌处理函数十分恰当,因为它表明一旦执行到这里,预期的程序流程已经中断,不会继续正常执行下去。

声明接口

mian.rs的第二行添加:

mod lang_items;

移除main函数

重新编译

cd ./os
cargo build

log

这里出现了新的报错,提示说fn main也是需要标准库的支持的.

warning: `/home/winddevil/workspace/os/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
   Compiling os v0.1.0 (/home/winddevil/workspace/os)
error: using `fn main` requires the standard library
  |
  = help: use `#![no_main]` to bypass the Rust generated entrypoint and declare a platform specific entrypoint yourself, usually with `#[no_mangle]`

error: could not compile `os` (bin "os") due to 1 previous error

干脆删掉main函数

直接编辑删除main函数

再次重新编译

cd ./os
cargo build

log

可以看到程序又需要这样一个入口.

warning: `/home/winddevil/workspace/os/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
   Compiling os v0.1.0 (/home/winddevil/workspace/os)
error[E0601]: `main` function not found in crate `os`
 --> src/main.rs:2:16
  |
2 | mod lang_items;
  |                ^ consider adding a `main` function to `src/main.rs`

For more information about this error, try `rustc --explain E0601`.
error: could not compile `os` (bin "os") due to 1 previous error

注意,这里遇到一个问题,无法触发文档里提到的:

   error: requires `start` lang_item

加入注释忽略main函数

再在文件的第一行中加入:

#![no_main]

第三次重新编译

cd ./os
cargo build

log

可以看到终于是编译成功了

warning: `/home/winddevil/workspace/os/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
   Compiling os v0.1.0 (/home/winddevil/workspace/os)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.67s

总结和反思"改造"过程

  1. 似乎过度重视于和std做切割,以图使得编译器高兴.
  2. 把原本希望能够显示一串字符串的任务忘在一边了.

分析被移除标准库的程序

分析编译出来的二进制可执行程序

安装分析工具

cargo install cargo-binutils
rustup component add llvm-tools-preview

detail

  1. cargo install cargo-binutils:
    • 这条命令是通过Cargo(Rust的包管理器)来安装cargo-binutils这个工具集。cargo-binutils提供了一系列二进制实用工具的Cargo包裹,这些工具对于操作Rust生成的二进制文件非常有用,比如objdumpnmsizestrip等。这些工具可以帮助开发者进行诸如查看二进制文件的符号信息、大小、重定位信息等操作,对于深入理解编译产物、性能分析或是调试底层问题非常有帮助。
  2. rustup component add llvm-tools-preview:
    • rustup 是Rust的工具链管理器,用于安装和管理不同的Rust版本以及相关的组件。这条命令是通过rustup添加llvm-tools-preview组件。LLVM是一个开源的编译器基础架构项目,Rust编译器(rustc)正是基于LLVM来实现代码优化和生成目标代码的。llvm-tools-preview组件包含了LLVM相关的工具集合,比如LLVM的汇编器、反汇编器、链接器等,这些工具对于进行底层优化、代码分析或是与LLVM交互的开发工作非常重要。加入这个组件后,你可以使用如llvm-objdumpllvm-dwarfdump等工具,这些工具在某些场景下可能比cargo-binutils提供的功能更为强大或具体。

分析文件格式

file target/riscv64gc-unknown-none-elf/debug/os

log

./target/riscv64gc-unknown-none-elf/debug/os: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped

detail

  • file命令: 是一个Unix/Linux shell命令,用来确定文件类型。当你对一个文件执行file命令时,它会告诉你该文件是一个普通文件、目录、符号链接,还是可执行文件等,并且如果可能的话,还会告诉你可执行文件的目标架构或解释器路径。
  • ELF 64-bit LSB executable: 表明这是一个64位的Executable and Linkable Format (ELF) 文件,用于Linux和类UNIX系统上的可执行程序、对象代码、共享库等。LSB指的是Little-Endian System V,即小端字节序的System V ABI规范。
  • UCB RISC-V: 这里提到的"UCB"可能是笔误或者特定工具链的标识,通常应解读为RISC-V架构。RISC-V是一种开放源代码的指令集架构,设计用于现代计算设备。
  • RVC: 表明支持RISC-V的压缩指令集(RVC, RISC-V Compressed Instruction Set),这是一套可选的指令集,用于减少代码大小。
  • double-float ABI: 指的是浮点数ABI(Application Binary Interface)为双精度(64位)浮点数,影响了浮点数的传递和返回规则。
  • version 1 (SYSV): 指的是System V接口的版本,这里是第1版,涉及到ABI兼容性。
  • statically linked: 表明该可执行文件是静态链接的,所有的依赖库都已经包含在内,因此在没有额外动态库的情况下也能运行,适合于嵌入式系统或无操作系统环境。
  • with debug_info: 表明编译时包含了调试信息,这对于使用调试器(如GDB)进行代码调试非常有用,因为这些信息包含了源代码级别的细节,如变量名、行号等。
  • not stripped: 表示该可执行文件没有经过strip处理。Stripping是一个过程,用于移除可执行文件中的符号表、调试信息等,以减小文件大小。由于未被剥离,这个文件会比较大,但保留了便于调试的所有信息。

分析文件头信息

rust-readobj -h target/riscv64gc-unknown-none-elf/debug/os

log

File: target/riscv64gc-unknown-none-elf/debug/os
Format: elf64-littleriscv
Arch: riscv64
AddressSize: 64bit
LoadName: <Not found>
ElfHeader {
  Ident {
    Magic: (7F 45 4C 46)
    Class: 64-bit (0x2)
    DataEncoding: LittleEndian (0x1)
    FileVersion: 1
    OS/ABI: SystemV (0x0)
    ABIVersion: 0
    Unused: (00 00 00 00 00 00 00)
  }
  Type: Executable (0x2)
  Machine: EM_RISCV (0xF3)
  Version: 1
  Entry: 0x0
  ProgramHeaderOffset: 0x40
  SectionHeaderOffset: 0x18F8
  Flags [ (0x5)
    EF_RISCV_FLOAT_ABI_DOUBLE (0x4)
    EF_RISCV_RVC (0x1)
  ]
  HeaderSize: 64
  ProgramHeaderEntrySize: 56
  ProgramHeaderCount: 4
  SectionHeaderEntrySize: 64
  SectionHeaderCount: 12
  StringTableSectionIndex: 10
}

detail

  • File: 指定分析的文件路径。
  • Format: 显示文件的格式,这里是elf64-littleriscv,即64位RISC-V架构的小端(little-endian)ELF格式。
  • Arch: 目标架构为riscv64,即64位的RISC-V处理器。
  • AddressSize: 指出地址宽度为64位。
  • ElfHeader: 描述了ELF文件头的关键信息,包括:
    • Ident:
      • Magic: 文件魔数(7F 45 4C 46),标识这是一个ELF文件。
      • Class: 表示64位体系结构。
      • DataEncoding: 小端字节序。
      • FileVersion: ELF文件版本为1。
      • OS/ABI: 使用System V ABI。
      • ABIVersion: ABI版本为0。
      • Unused: 未使用的预留字段。
    • Type: 文件类型为Executable (0x2),表明这是一个可执行文件。
    • Machine: 目标机器为EM_RISCV (0xF3),即RISC-V架构。
    • Version: ELF文件版本号为1。
    • Entry: 入口点地址为0x0,这可能是因为该信息未被正确解析或该程序的入口点需根据加载地址动态确定。
    • ProgramHeaderOffset/SectionHeaderOffset: 分别指示程序头和节头表在文件中的偏移量。
    • Flags: 标志位包含:
      • EF_RISCV_FLOAT_ABI_DOUBLE: 表明使用双精度浮点数ABI。
      • EF_RISCV_RVC: 表明支持RISC-V的压缩指令集。
    • HeaderSize: 文件头的大小为64字节。
    • ProgramHeaderEntrySize/ProgramHeaderCount: 程序头表项的大小和数量,指示了程序加载、动态链接等信息的分布。
    • SectionHeaderEntrySize/SectionHeaderCount: 节头表项的大小和数量,节头包含了代码、数据、符号表等各个部分的信息。
    • StringTableSectionIndex: 指向节头表中字符串表的索引,用于解析节名称等文本信息。

反汇编导出汇编程序

rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os

log

target/riscv64gc-unknown-none-elf/debug/os:     file format elf64-littleriscv

detail

  1. rust-objdump: 这是Rust工具链中的一部分,特别用于反汇编Rust编译器(rustc)生成的二进制文件或库文件。它类似于GNU的objdump工具,但更加针对Rust语言特性进行了优化,能够提供关于 Rust 代码在底层的表示方式的更多信息。
  2. -S: 这是一个选项,指示rust-objdump在反汇编时同时显示对应的源代码行。这意味着输出中不仅会包含汇编代码,还会插入相关的源代码行,使得开发者可以直观地看到高级Rust代码与最终生成的机器码之间的对应关系。这对于理解代码的编译效果、进行性能调优或者底层调试非常有帮助。
  3. target/riscv64gc-unknown-none-elf/debug/os: 这是待分析的目标文件路径。从路径中我们可以提取出以下信息:
    • target/riscv64gc-unknown-none-elf/: 表示这是一个为RISC-V 64位GC(General Customer,一般客户机,指支持标准用户级ISA的处理器)架构编译的目标文件,目标平台是未知的(unknown),没有操作系统(none),使用ELF(Executable and Linkable Format,可执行和可链接格式)作为二进制格式。这是典型的嵌入式系统或裸机编程的配置。
    • debug: 指示这是以Debug模式构建的。在Debug模式下,编译出的代码通常包含更多的调试信息,如符号表等,便于开发者调试,但可能牺牲了执行效率和代码大小。
    • os: 这是目标文件的名字,暗示这可能是某个操作系统内核或与操作系统相关的组件。在Rust项目中,os可能指的是项目的主要二进制输出,尤其是在开发操作系统或嵌入式系统相关软件时。
posted @ 2024-07-09 19:52  winddevil  阅读(273)  评论(0编辑  收藏  举报