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

2024-04-22 00:52

本文主要是介绍rCore-Tutorial-Book第二课(移除Rust std标准库依赖),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

文章目录

    • 1. 移除 `println!` 宏
      • 1.1 `rust`代码编译到指定目标平台
      • 1.2 禁用 `rust-std` 标准库
      • 1.3 提供`panic_handler` 功能
    • 2. 移除`main` 函数
    • 3. 分析被移除标准库的程序
      • 3.1 安装`cargo-binutils` 工具集
      • 3.2 分析二进制文件信息
        • 3.2.1 分析文件格式
        • 3.2.2 分析文件头信息
        • 3.2.3 分析反汇编导出汇编程序
    • 4. 额外知识点补充
      • 4. 1本地编译与交叉编译
      • 4. 2`#[panic_handler]`
      • 4.3 `Rust`模块化编程
    • 5. 参考文章

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 buildCompiling 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 buildCompiling 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 buildCompiling 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/osFile: 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: 1OS/ABI: SystemV (0x0)ABIVersion: 0Unused: (00 00 00 00 00 00 00)}Type: Executable (0x2)Machine: EM_RISCV (0xF3)Version: 1Entry: 0x0ProgramHeaderOffset: 0x40SectionHeaderOffset: 0x1908Flags [ (0x5)EF_RISCV_FLOAT_ABI_DOUBLE (0x4)EF_RISCV_RVC (0x1)]HeaderSize: 64ProgramHeaderEntrySize: 56ProgramHeaderCount: 4SectionHeaderEntrySize: 64SectionHeaderCount: 12StringTableSectionIndex: 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/ostarget/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)

这篇关于rCore-Tutorial-Book第二课(移除Rust std标准库依赖)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/924457

相关文章

2390.从字符串中移除星号

给你一个包含若干星号 * 的字符串 s 。 在一步操作中,你可以: 选中 s 中的一个星号。 移除星号左侧最近的那个非星号字符,并移除该星号自身。 返回移除 所有 星号之后的字符串。 注意: 生成的输入保证总是可以执行题面中描述的操作。 可以证明结果字符串是唯一的。 示例 1: 输入:s = “leet**cod*e” 输出:“lecoe” 解释:从左到右执行移除操作: 距离第 1 个

C++面试八股文:std::deque用过吗?

100编程书屋_孔夫子旧书网 某日二师兄参加XXX科技公司的C++工程师开发岗位第26面: 面试官:deque用过吗? 二师兄:说实话,很少用,基本没用过。 面试官:为什么? 二师兄:因为使用它的场景很少,大部分需要性能、且需要自动扩容的时候使用vector,需要随机插入和删除的时候可以使用list。 面试官:那你知道STL中的stack是如何实现的吗? 二师兄:默认情况下,stack使

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述。以下是从不同角度对气象站的种类和应用范围的介绍: 一、气象站的种类 根据用途和安装环境分类: 农业气象站:专为农业生产服务,监测土壤温度、湿度等参数,为农业生产提供科学依据。交通气象站:用于公路、铁路、机场等交通场所的气象监测,提供实时气象数据以支持交通运营和调度。林业气象站:监测林区风速、湿度、温度等气象要素,为林区保护和

C++标准模板库STL介绍

STL的六大组成部分 STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,提供了丰富的通用数据结构和算法,使得 C++ 编程变得更加高效和方便。STL 包括了 6 大类组件,分别是算法(Algorithm)、容器(Container)、空间分配器(Allocator)、迭代器(Iterator)、函数对象(Functor)、适配器(Adapter)

Rust:Future、async 异步代码机制示例与分析

0. 异步、并发、并行、进程、协程概念梳理 Rust 的异步机制不是多线程或多进程,而是基于协程(或称为轻量级线程、微线程)的模型,这些协程可以在单个线程内并发执行。这种模型允许在单个线程中通过非阻塞的方式处理多个任务,从而实现高效的并发。 关于“并发”和“并行”的区别,这是两个经常被提及但含义不同的概念: 并发(Concurrency):指的是同时处理多个任务的能力,这些任务可能在同一时

秋招突击——6/22——复习{区间DP——加分二叉树,背包问题——买书}——新作{移除元素、实现strStr()}

文章目录 引言复习区间DP——加分二叉树个人实现 背包问题——买书个人实现参考实现 新作移除元素个人实现参考思路 找出字符串中第一个匹配项的下标个人实现参考实现 总结 引言 今天做了一个噩梦,然后流了一身汗,然后没起来,九点多才起床背书。十点钟才开始把昨天那道题题目过一遍,然后十一点才开始复习题目,为了不耽误下午的时间,所以这里的就单纯做已经做过的题目,主打一个有量,不在学

Android gradle打印依赖的各种姿势

查看Android Gradle 依赖树 1.查看单独模块的依赖 命令行 ./gradlew :模块名:dependencies 例子: ./gradlew :app:dependencies 这个命令会将 gradle 执行的各个步骤全打印出来,包括引用的库,和库中引用的库文件 ./gradlew :app:dependencies --configuration im

Android Studio 下项目的依赖配置

Android Studio 下项目的依赖配置 背景 项目需要用到一个github上的开源库swipelistview,原来在eclipse环境下配置过相关的依赖(导入jar包或者是lib依赖),但是在Android Studio下还是没有操作过。上网查了一下相关的资料,在stackoverflow上找到了答案。根据上面的介绍,结合实际情况,完成了依赖配置。 stackoverflow答案

Creating custom and compound Views in Android - Tutorial(翻译)

Creating custom and compound Views in Android - Tutorial(翻译) 译前的: 之前做了三篇学习笔记,从知乎上面看到了这篇英文的推荐,总的来说可以是一篇导读,没有相关的学习,看这篇,可以作为一个学习脉络导向;有相关的学习底子,可以作为一个基础夯实、思维理清。没想到一翻译就是四个多小时…英语渣,很多词句都不太准确,幸好有之前的学习基础打底…

Maven的依赖传递、依赖管理、依赖作用域

在Maven项目中通常会引入大量依赖,但依赖管理不当,会造成版本混乱冲突或者目标包臃肿。因此,我们以SpringBoot为例,从三方面探索依赖的使用规则。 1、 依赖传递 依赖是会传递的,依赖的依赖也会连带引入。例如在项目中引入了spring-boot-starter-web依赖: <dependency><groupId>org.springframework.boot</groupId>