rCore-Tutorial-Book第二课(移除Rust std标准库依赖)

本文详细介绍了如何在Rust中移除对标准库的依赖,编译到RV64GC裸机平台,并处理println!宏、main函数和panic_handler的迁移。通过实例展示了如何禁用标准库、提供自定义panic处理以及进行本地和交叉编译的过程。
摘要由CSDN通过智能技术生成

本节任务: 移除掉代码对 Rust std标准库的依赖,并将自己的程序改造成为能被编译到 RV64GC 裸机平台

1. 移除 println!

1.1 rust代码编译到指定目标平台

指令格式:rustup target add <target-spec>

$ rustup target add riscv64gc-unknown-none-elf

补充理解:

  • Linux 系统上,默认编译的目标平台是 x86_64-unknown-linux-gnu
  • riscv64gc-unknown-none-elf 是特定目标三元组,指定了编译器应该生成的目标平台和运行环境
  • riscv64gc 表示 RISC-V 64 位指令集架构,unknown 表示目标操作系统为未知,none 表示不使用标准库,elf 表示生成的目标文件格式为 ELF
  • 编译器运行的开发平台(x86_64)可执行文件运行的目标平台(riscv-64)不同的情况。我们把这种情况称为 交叉编译 (Cross Compile)
  • 偷懒技巧,不想每次 cargo build 加上指令 --target 参数,可以在根目录下.cargo 创建 config.toml,并输入以下内容
[build]
target = "riscv64gc-unknown-none-elf"

1.2 禁用 rust-std 标准库

代码:#![no_std]

放置位置:main.rs 文件开头

#![no_std]
fn main() {
    println!("Hello,world!");
}

补充理解:

  • 执行 cargo build 后编译器会报错,因为禁用了 标准库后,println! 宏没有被实现,标准库实现了宏,并使用了名为write的系统调用
$ 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!("Hello,world!") 语句后再次执行 cargo build,会引发没有实现 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

1.3 提供panic_handler 功能

代码:#[panic_handler]

需要实现:fn(&PanicInfo) -> ! 函数签名

use core::panic::PanicInfo;

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

补充理解:

  • 自己实现对致命错误的处理方法
  • 通过 #[panic_handler] 属性通知编译器用panic函数来对接 panic!
  • 将该子模块添加到项目中,我们还需要在 main.rs#![no_std] 的下方加上 mod 模块名;
#![no_std]
mod lang_items;
fn main() {
  println!("Hello,world!");
}
  • 目前 panic 函数没有实现任何功能,后序需要解析 PanicInfo 打印出错位置 + 杀死应用程序。

2. 移除main 函数

代码:#[no_main]

放置位置:main.rs 文件开头

#[no_main]
#![no_std]
mod lang_items;
fn main() {
    //println!("Hello,world!");
}

补充理解:

  • 没有 #[no_main] 运行 cargo build 会有编译错误,错误提示告诉我们,fn main 需要标准库支持
root@ww:/OSHomework/rustsrc/os/src# cargo build
   Compiling os v0.1.0 (/OSHomework/rustsrc/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 函数
  • 因为我们禁用了标准库,所以编译器找不到 fn main
  • 解决方案是在 main.rs 的开头加入设置 #![no_main] 告诉编译器我们没有一般意义上的 main 函数,并将原来的 main 函数删除。在失去了 main 函数的情况下,编译器就不需要完成初始化工作。
  • 移除后,我们做到了第一步!通过编译器检查并生成执行码。
root@ww:/OSHomework/rustsrc/os/src# cargo build
   Compiling os v0.1.0 (/OSHomework/rustsrc/os)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s

3. 分析被移除标准库的程序

上述操作已经通过了 rust 编译器的检查和编译,形成了二进制代码,如何去查看这个二进制代码呢?

为了分析二进制可执行程序,我们需要安装 cargo-binutils 工具集

3.1 安装cargo-binutils 工具集

指令:cargo install cargo-binutils \

rustup component add llvm-tools-preview

目的:安装 cargo-binutilsllvm-tools-preview 工具,用于后续分析二进制文件

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

3.2 分析二进制文件信息

3.2.1 分析文件格式

指令:file target/riscv64gc-unknown-none-elf/debug/os

目的:查看编译后在 target/riscv64gc-unknown-none-elf/debug/os 这个可执行文件的文件类型信息

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

补充理解:

  • ELF 64-bit LSB executable: 这表示该文件是一个 ELF 格式的可执行文件,采用的是 64 位格式,并且是小端序(Little-Endian)字节顺序。
  • UCB RISC-V, version 1 (SYSV): 这说明该可执行文件是为 RISC-V 架构生成的,采用的是UC Berkeley的指令集版本,并且符合 System V ABI 规范。
  • statically linked: 这表示该可执行文件是静态链接的,意味着它包含了所有需要的库文件和依赖,而不依赖于外部共享库。
  • with debug_info: 这表示该可执行文件包含调试信息,可以用于调试程序。
  • not stripped: 这表示该可执行文件未被剥离(stripped),即保留了符号表和其他调试信息。
3.2.2 分析文件头信息

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

目的:查看目标文件的头部信息,包括文件类型、架构、入口地址等

$ 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
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: 0x1908
  Flags [ (0x5)
    EF_RISCV_FLOAT_ABI_DOUBLE (0x4)
    EF_RISCV_RVC (0x1)
  ]
  HeaderSize: 64
  ProgramHeaderEntrySize: 56
  ProgramHeaderCount: 4
  SectionHeaderEntrySize: 64
  SectionHeaderCount: 12
  StringTableSectionIndex: 10
}

补充理解:

  • 入口地址Entry: 0x0 ,从 C/C++ 等语言中得来的经验告诉我们, 0 一般表示 NULL 或空指针,因此等于 0 的入口地址看上去无法对应到任何指令。
  • File: target/riscv64gc-unknown-none-elf/debug/os: 指定的目标文件路径。
  • Format: elf64-littleriscv: 文件格式为 ELF 64 位小端 RISC-V 格式,表示这是一个针对 RISC-V 架构的 64 位 ELF 格式文件。
  • Arch: riscv64: 架构为 RISC-V 64 位,表示这个文件是为 RISC-V 64 位架构生成的。
  • AddressSize: 64bit: 地址大小为 64 位。
  • LoadName: <Not found>: 未找到加载名称。
  • Ident: ELF 头部标识信息,包括文件魔数、类别、数据编码、操作系统/ABI 等信息。
  • Type: Executable: 文件类型为可执行文件。
  • Machine: EM_RISCV: 机器码表示为 EM_RISCV,即 RISC-V 架构。
  • Entry: 0x0: 入口地址为 0x0,表示程序的执行从地址 0x0 开始。
  • ProgramHeaderOffset: 0x40: 程序头偏移地址为 0x40
  • SectionHeaderOffset: 0x1908: 节头偏移地址为 0x1908
  • Flags: 标志字段,包括 RISC-V 相关的标志信息。
  • HeaderSize: 64: 头部大小为 64 字节。
  • ProgramHeaderEntrySize: 56: 程序头条目大小为 56 字节。
  • ProgramHeaderCount: 4: 程序头数量为 4 个。
  • SectionHeaderEntrySize: 64: 节头条目大小为 64 字节。
  • SectionHeaderCount: 12: 节头数量为 12 个。
  • StringTableSectionIndex: 10: 字符串表节索引为 10。
3.2.3 分析反汇编导出汇编程序

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

目的:对指定的目标文件进行反汇编,并且输出反汇编的结果以及源代码的对应部分,-S 选项指示 rust-objdump 在显示反汇编代码时同时显示源代码的对应部分.

$ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os
	target/riscv64gc-unknown-none-elf/debug/os:       file format elf64-littleriscv

补充理解:

  • 可以看到没有生成汇编代码,所以,我们可以断定,这个二进制程序虽然合法,但它是一个空程序。
  • 产生该现象的原因是:目前我们的程序(参考上面的源代码)没有进行任何有意义的工作,由于我们移除了 main 函数并将项目设置为 #![no_main] ,它甚至没有一个传统意义上的入口点(即程序首条被执行的指令所在的位置),因此 Rust 编译器会生成一个空程序。

4. 额外知识点补充

4. 1本地编译与交叉编译

本地编译与交叉编译:

下面指的 平台 主要由CPU硬件和操作系统这两个要素组成。

  • 本地编译,即在当前开发平台下编译出来的程序,也只是放到这个平台下运行。如在 Linux x86-64 平台上编写代码并编译成可在 Linux x86-64 同样平台上执行的程序。

  • 交叉编译,是一个与本地编译相对应的概念,即在一种平台上编译出在另一种平台上运行的程序。程序编译的环境与程序运行的环境不一样。如我们后续会讲到,在Linux x86-64 开发平台上,编写代码并编译成可在 rCore Tutorial(这是我们要编写的操作系统内核)和 riscv64gc(这是CPU硬件)构成的目标平台上执行的程序。

4. 2#[panic_handler]

#[panic_handler]

#[panic_handler]是一种编译指导属性,用于标记核心库core中的 panic!要对接的函数

  • 函数需实现对致命错误的具体处理
  • 函数需有 fn(&PanicInfo) -> ! 函数签名
  • 函数可通过 PanicInfo 数据结构获取致命错误的相关信息

4.3 Rust模块化编程

Rust模块化编程

  • 将一个软件工程项目划分为多个子模块分别进行实现是一种被广泛应用的编程技巧,它有助于促进复用代码,并显著提升代码的可读性和可维护性。因此,众多编程语言均对模块化编程提供了支持,Rust 语言也不例外。
  • 每个通过 Cargo 工具创建的 Rust 项目均是一个模块,取决于 Rust 项目类型的不同,模块的根所在的位置也不同。当使用 --bin 创建一个可执行的 Rust 项目时,模块的根是 src/main.rs 文件;而当使用 --lib 创建一个 Rust 库项目时,模块的根是 src/lib.rs 文件。在模块的根文件中,我们需要声明所有可能会用到的子模块。如果不声明的话,即使子模块对应的文件存在,Rust 编译器也不会用到它们。如上面的代码片段中,我们就在根文件 src/main.rs 中通过 mod lang_items; 声明了子模块 lang_items ,该子模块实现在文件 src/lang_item.rs 中,我们将项目中所有的语义项放在该模块中。
  • 创建的指令如下
$ cargo new fileName --bin
$ cargo new fileName --lib
  • 当一个子模块比较复杂的时候,它往往不会被放在一个独立的文件中,而是放在一个 src 目录下与子模块同名的子目录之下,在后面的章节中我们常会用到这种方法。例如第二章代码(参见代码仓库的 ch2 分支)中的 syscall 子模块就放在 src/syscall 目录下。对于这样的子模块,其所在目录下的 mod.rs 为该模块的根,其中可以进而声明它的子模块。同样,这些子模块既可以放在一个文件中,也可以放在一个目录下。
  • 我们可以使用绝对路径或相对路径来引用其他模块或当前模块的内容。参考上面的 use core::panic::PanicInfo; ,类似 C++ ,我们将模块的名字按照层级由浅到深排列,并在相邻层级之间使用分隔符 :: 进行分隔。路径的最后一级(如 PanicInfo)则表示我们具体要引用或访问的内容,可能是变量、类型或者方法名。当通过绝对路径进行引用时,路径最开头可能是项目依赖的一个外部库的名字,或者是 crate 表示项目自身的根模块。

5. 参考文章

移除标准库依赖 - rCore-Tutorial-Book-v3 3.6.0-alpha.1 文档 (rcore-os.cn)

  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ug871-vivado-high-level-synthesis-tutorial.pdf是有关Vivado高级综合教程的文档。该文档提供了使用Vivado高级综合工具的指南和教程,以帮助开发人员更高效地进行数字设计。 Vivado是赛灵思公司开发的综合工具套件,用于设计和实现数字电路。高级综合是一种将高级语言(如C或C++)转换为硬件描述语言(如VHDL或Verilog)的技术。它使开发人员能够使用更高级的语言进行设计,并将其转换为硬件电路,从而加快设计过程的速度。 在ug871-vivado-high-level-synthesis-tutorial.pdf中,开发人员将学习如何使用Vivado高级综合工具来创建和转换高级语言设计。文档以简单易懂的方式介绍了Vivado高级综合工具的基本概念和操作步骤。 该教程包含以下主要内容: 1. 介绍了高级综合的基本原理和优势,以及该技术可以加快设计速度的原因。 2. 解释了Vivado高级综合工具的功能和特点,以及如何进行安装和配置。 3. 提供了使用Vivado高级综合工具进行设计的具体步骤和操作指南。其中包括创建高级语言设计文件、设定综合目标和选项、运行综合和优化过程等。 4. 展示了如何生成和验证转换后的硬件电路,并进行仿真和测试。 5. 提供了一些示例案例,帮助开发人员更好地理解和应用Vivado高级综合工具。 通过学习和应用ug871-vivado-high-level-synthesis-tutorial.pdf中的内容,开发人员可以更有效地利用Vivado高级综合工具进行数字设计。这将使他们在开发过程中节省时间和精力,并且能够更快地实现设计目标。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值