# 移除标准库依赖 - Open-Source-OS-Training-Camp-2022 文档

Hide navigation sidebar
Hide table of contents sidebar
Toggle table of contents sidebar

# 移除标准库依赖

由于后续实验需要 `rustc` 编译器缺省生成RISC-V 64的目标代码，所以我们首先要给 `rustc` 添加一个target : `riscv64gc-unknown-none-elf` 。这可通过如下命令来完成：

    $ rustup target add riscv64gc-unknown-none-elf

然后在 `os` 目录下新建 `.cargo` 目录，并在这个目录下创建 `config` 文件，输入如下内容：

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

这将使 cargo 工具在 os 目录下默认会使用 riscv64gc-unknown-none-elf 作为目标平台。 这种编译器运行的平台（x86_64）与可执行文件运行的目标平台不同的情况，称为 交叉编译 (Cross Compile)。

## 移除 println! 宏

我们在 `main.rs` 的开头加上一行 `#![no_std]`， 告诉 Rust 编译器不使用 Rust 标准库 std 转而使用核心库 core。重新编译，报错如下：

错误

    $ cargo build
       Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
    error: cannot find macro `println` in this scope
    --> src/main.rs:4:5
      |
    4 |     println!("Hello, world!");
      |     ^^^^^^^

println! 宏是由标准库 std 提供的，且会使用到一个名为 write 的系统调用。 无论如何，我们先将这行代码注释掉。

## 提供语义项 panic_handler

错误

    $ cargo build
       Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
    error: `#[panic_handler]` function required, but not found

标准库 std 提供了 Rust 错误处理函数 `#[panic_handler]`，其大致功能是打印出错位置和原因并杀死当前应用。 但核心库 core 并没有提供这项功能，得靠我们自己实现。

新建一个子模块 `lang_items.rs`，在里面编写 panic 处理函数，通过标记 `#[panic_handler]` 告知编译器采用我们的实现：

    // os/src/lang_items.rs
    use core::panic::PanicInfo;

    #[panic_handler]
    fn panic(_info: &PanicInfo) -> ! {
        loop {}
    }

在把 `panic_handler` 配置在单独的文件 `os/src/lang_items.rs` 后，需要在os/src/main.rs文件中添加以下内容才能正常编译整个软件：

    // os/src/main.rs
    #![no_std]
    mod lang_items;
    // ... other code

注意，panic 处理函数的函数签名需要一个 `PanicInfo` 的不可变借用作为输入参数，它在核心库中得以保留，这也是我们第一次与核心库打交道。之后我们会从 `PanicInfo` 解析出错位置并打印出来，然后杀死应用程序。但目前编译出的简单OS在运行时如果遇到错误什么都不做，只在原地 `loop` 。

## 移除 main 函数

重新编译，又有了新错误：

错误

    $ cargo build
       Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
    error: requires `start` lang_item

编译器提醒我们缺少一个名为 `start` 的语义项。 `start` 语义项代表了标准库 std 在执行应用程序之前需要进行的一些初始化工作。由于我们禁用了标准库，编译器也就找不到这项功能的实现了。

在 `main.rs` 的开头加入设置 `#![no_main]` 告诉编译器我们没有一般意义上的 `main` 函数， 并将原来的 `main` 函数删除。这样编译器也就不需要考虑初始化工作了。

    $ cargo build
       Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
        Finished dev [unoptimized + debuginfo] target(s) in 0.06s

至此，我们终于移除了所有标准库依赖。目前的主要代码包括 `main.rs` 和 `lang_items.rs` ，大致内容如下：

    // os/src/main.rs
    #![no_main]
    #![no_std]
    mod lang_items;
    // ... other code

    // os/src/lang_items.rs
    use core::panic::PanicInfo;

    #[panic_handler]
    fn panic(_info: &PanicInfo) -> ! {
        loop {}
    }

## 分析被移除标准库的程序

首先安装 cargo-binutils 工具集：

我们可以通过各种工具来分析目前的程序：

    [文件格式]
    $ file target/riscv64gc-unknown-none-elf/debug/os
    target/riscv64gc-unknown-none-elf/debug/os: ELF 64-bit LSB executable, UCB RISC-V, ......

    [文件头信息]
    $ rust-readobj -h target/riscv64gc-unknown-none-elf/debug/os
       File: target/riscv64gc-unknown-none-elf/debug/os
       Format: elf64-littleriscv
       Arch: riscv64
       AddressSize: 64bit
       ......
       Type: Executable (0x2)
       Machine: EM_RISCV (0xF3)
       Version: 1
       Entry: 0x0
       ......
       }

    [反汇编导出汇编程序]
    $ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os
       target/riscv64gc-unknown-none-elf/debug/os:       file format elf64-littleriscv

通过 `file` 工具对二进制程序 `os` 的分析可以看到，它好像是一个合法的 RV64 执行程序， 但 `rust-readobj` 工具告诉我们它的入口地址 Entry 是 `0`。 再通过 `rust-objdump` 工具把它反汇编，没有生成任何汇编代码。 可见，这个二进制程序虽然合法，但它是一个空程序，原因是缺少了编译器规定的入口函数 `_start` 。

从下一节开始，我们将着手实现本节移除的、由用户态执行环境提供的功能。

注解

本节内容部分参考自 BlogOS 的相关章节 。