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

相关文章

每天认识几个maven依赖(ActiveMQ+activemq-jaxb+activesoap+activespace+adarwin)

八、ActiveMQ 1、是什么? ActiveMQ 是一个开源的消息中间件(Message Broker),由 Apache 软件基金会开发和维护。它实现了 Java 消息服务(Java Message Service, JMS)规范,并支持多种消息传递协议,包括 AMQP、MQTT 和 OpenWire 等。 2、有什么用? 可靠性:ActiveMQ 提供了消息持久性和事务支持,确保消

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

C 标准库 - `<float.h>`

C 标准库 - <float.h> 概述 <float.h> 是 C 标准库中的一个头文件,它定义了与浮点数类型相关的宏。这些宏提供了关于浮点数的属性信息,如精度、最小和最大值、以及舍入误差等。这个头文件对于需要精确控制浮点数行为的程序非常有用,尤其是在数值计算和科学计算领域。 主要宏 <float.h> 中定义了许多宏,下面列举了一些主要的宏: FLT_RADIX:定义了浮点数的基数。

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

【Rust练习】12.枚举

练习题来自:https://practice-zh.course.rs/compound-types/enum.html 1 // 修复错误enum Number {Zero,One,Two,}enum Number1 {Zero = 0,One,Two,}// C语言风格的枚举定义enum Number2 {Zero = 0.0,One = 1.0,Two = 2.0,}fn m

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(

PHP7扩展开发之依赖其他扩展

前言 有的时候,我们的扩展要依赖其他扩展。比如,我们PHP的mysqli扩展就依赖mysqlnd扩展。这中情况下,我们怎么使用其他扩展呢?这个就是本文讲述的内容。 我们新建立一个扩展,名字叫 demo_dep , 依赖之前的say扩展。 在demo_dep扩展中,我们实现demo_say方法。这个方法调用say扩展的say方法。 代码 基础代码 确保say扩展的头文件正确安装到了php

第二十四章 rust中的运算符重载

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust中的运算符重载 目录 注意一、前言二、基本使用三、常用运算符四、通用约束 一、前言 C/C++中有运算符重载这一概念,它的目的是让即使含不相干的内容也能通过我们自定义的方法进行运算符操作运算。 比如字符串本身是不能相加的,但由于C++中的String重载了运算符+,所以我们就可以将两个字符串进行相加、但实际

Go 依赖注入库dig

简介 今天我们来介绍 Go 语言的一个依赖注入(DI)库——dig。dig 是 uber 开源的库。Java 依赖注入的库有很多,相信即使不是做 Java 开发的童鞋也听过大名鼎鼎的 Spring。相比庞大的 Spring,dig 很小巧,实现和使用都比较简洁。 快速使用 第三方库需要先安装,由于我们的示例中使用了前面介绍的go-ini和go-flags,这两个库也需要安装: $ go g