A Minimal Rust Kernel

发布时间 2023-09-28 17:26:05作者: ImreW
Feb 10, 2018

In this post, we create a minimal 64-bit Rust kernel for the x86 architecture. We build upon the freestanding Rust binary from the previous post to create a bootable disk image that prints something to the screen.

在这篇文章中,我们为 x86 架构创建了一个最小的 64 位 Rust 内核。 我们基于上一篇文章中的独立 Rust 二进制文件来创建一个可启动磁盘映像,该映像可以在屏幕上打印一些内容。

This blog is openly developed on GitHub. If you have any problems or questions, please open an issue there. You can also leave comments at the bottom. The complete source code for this post can be found in the post-02 branch.

该博客是在 GitHub 上公开开发的。 如果您有任何问题或疑问,请在那里提出问题。 也可以在底部留言评论。 这篇文章的完整源代码可以在 post-02 分支中找到。

The Boot Process

When you turn on a computer, it begins executing firmware code that is stored in motherboard ROM. This code performs a power-on self-test, detects available RAM, and pre-initializes the CPU and hardware. Afterwards, it looks for a bootable disk and starts booting the operating system kernel.

当您打开计算机时,它开始执行存储在主板 ROM 中的固件代码。 此代码执行加电自检、检测可用 RAM 并预初始化 CPU 和硬件。 然后,它会查找可启动磁盘并开始启动操作系统内核。

On x86, there are two firmware standards: the “Basic Input/Output System“ (BIOS) and the newer “Unified Extensible Firmware Interface” (UEFI). The BIOS standard is old and outdated, but simple and well-supported on any x86 machine since the 1980s. UEFI, in contrast, is more modern and has much more features, but is more complex to set up (at least in my opinion).

在 x86 上,有两种固件标准:“基本输入/输出系统”(BIOS) 和较新的“统一可扩展固件接口”(UEFI)。 BIOS 标准陈旧且过时,但简单且自 20 世纪 80 年代以来的任何 x86 计算机都得到良好支持。 相比之下,UEFI 更现代,具有更多功能,但设置更复杂(至少在我看来)。

Currently, we only provide BIOS support, but support for UEFI is planned, too. If you’d like to help us with this, check out the Github issue.

目前,我们仅提供 BIOS 支持,但也计划支持 UEFI。 如果您想帮助我们解决此问题,请查看 Github 问题。

BIOS Boot

Almost all x86 systems have support for BIOS booting, including newer UEFI-based machines that use an emulated BIOS. This is great, because you can use the same boot logic across all machines from the last century. But this wide compatibility is at the same time the biggest disadvantage of BIOS booting, because it means that the CPU is put into a 16-bit compatibility mode called real mode before booting so that archaic bootloaders from the 1980s would still work.

几乎所有 x86 系统都支持 BIOS 启动,包括使用模拟 BIOS 的较新的基于 UEFI 的计算机。 这很棒,因为您可以在上个世纪的所有机器上使用相同的引导逻辑。 但这种广泛的兼容性同时也是 BIOS 启动的最大缺点,因为这意味着 CPU 在启动之前被置于称为实模式的 16 位兼容模式,这样 20 世纪 80 年代的老式引导加载程序仍然可以工作。

But let’s start from the beginning:

但让我们从头开始:

When you turn on a computer, it loads the BIOS from some special flash memory located on the motherboard. The BIOS runs self-test and initialization routines of the hardware, then it looks for bootable disks. If it finds one, control is transferred to its bootloader, which is a 512-byte portion of executable code stored at the disk’s beginning. Most bootloaders are larger than 512 bytes, so bootloaders are commonly split into a small first stage, which fits into 512 bytes, and a second stage, which is subsequently loaded by the first stage.

当您打开计算机时,它会从主板上的某些特殊闪存加载 BIOS。 BIOS 运行硬件的自检和初始化例程,然后查找可启动磁盘。 如果找到,控制权就会转移到其引导加载程序,这是存储在磁盘开头的 512 字节可执行代码部分。 大多数引导加载程序都大于 512 字节,因此引导加载程序通常分为较小的第一阶段(适合 512 字节)和第二阶段(随后由第一阶段加载)。

The bootloader has to determine the location of the kernel image on the disk and load it into memory. It also needs to switch the CPU from the 16-bit real mode first to the 32-bit protected mode, and then to the 64-bit long mode, where 64-bit registers and the complete main memory are available. Its third job is to query certain information (such as a memory map) from the BIOS and pass it to the OS kernel.

引导加载程序必须确定内核映像在磁盘上的位置并将其加载到内存中。 它还需要先将CPU从16位实模式切换到32位保护模式,然后再切换到64位长模式,其中64位寄存器和完整的主存储器都可用。 它的第三项工作是从 BIOS 查询某些信息(例如内存映射)并将其传递给操作系统内核。

Writing a bootloader is a bit cumbersome as it requires assembly language and a lot of non insightful steps like “write this magic value to this processor register”. Therefore, we don’t cover bootloader creation in this post and instead provide a tool named bootimage that automatically prepends a bootloader to your kernel.

编写引导加载程序有点麻烦,因为它需要汇编语言和许多非深入的步骤,例如“将这个神奇的值写入该处理器寄存器”。 因此,我们不会在本文中介绍引导加载程序的创建,而是提供一个名为 bootimage 的工具,该工具会自动将引导加载程序添加到内核中。

If you are interested in building your own bootloader: Stay tuned, a set of posts on this topic is already planned!

如果您有兴趣构建自己的引导加载程序:请继续关注,我们已经计划了一系列关于此主题的帖子!

The Multiboot Standard

To avoid that every operating system implements its own bootloader, which is only compatible with a single OS, the Free Software Foundation created an open bootloader standard called Multiboot in 1995. The standard defines an interface between the bootloader and the operating system, so that any Multiboot-compliant bootloader can load any Multiboot-compliant operating system. The reference implementation is GNU GRUB, which is the most popular bootloader for Linux systems.

为了避免每个操作系统都实现自己的引导加载程序,而该引导加载程序仅与单个操作系统兼容,自由软件基金会于 1995 年创建了一个名为 Multiboot 的开放引导加载程序标准。该标准定义了引导加载程序和操作系统之间的接口,以便任何 符合多重引导的引导加载程序可以加载任何符合多重引导的操作系统。 参考实现是 GNU GRUB,它是 Linux 系统最流行的引导加载程序。

To make a kernel Multiboot compliant, one just needs to insert a so-called Multiboot header at the beginning of the kernel file. This makes it very easy to boot an OS from GRUB. However, GRUB and the Multiboot standard have some problems too:

要使内核兼容多重引导,只需在内核文件的开头插入所谓的多重引导标头即可。 这使得从 GRUB 引导操作系统变得非常容易。 然而,GRUB 和 Multiboot 标准也存在一些问题:

  • They support only the 32-bit protected mode. This means that you still have to do the CPU configuration to switch to the 64-bit long mode.
  • 它们仅支持 32 位保护模式。 这意味着您仍然需要进行 CPU 配置才能切换到 64 位长模式。
  • They are designed to make the bootloader simple instead of the kernel. For example, the kernel needs to be linked with an adjusted default page size, because GRUB can’t find the Multiboot header otherwise. Another example is that the boot information, which is passed to the kernel, contains lots of architecture-dependent structures instead of providing clean abstractions.
  • 它们旨在使引导加载程序而不是内核变得简单。 例如,内核需要与调整后的默认页面大小链接,否则 GRUB 无法找到 Multiboot 标头。 另一个例子是,传递给内核的引导信息包含大量依赖于体系结构的结构,而不是提供干净的抽象。
  • Both GRUB and the Multiboot standard are only sparsely documented.
  • GRUB 和 Multiboot 标准的文档很少。
  • GRUB needs to be installed on the host system to create a bootable disk image from the kernel file. This makes development on Windows or Mac more difficult.
  • 需要在主机系统上安装 GRUB 才能从内核文件创建可引导磁盘映像。 这使得在 Windows 或 Mac 上的开发变得更加困难。

Because of these drawbacks, we decided to not use GRUB or the Multiboot standard. However, we plan to add Multiboot support to our bootimage tool, so that it’s possible to load your kernel on a GRUB system too. If you’re interested in writing a Multiboot compliant kernel, check out the first edition of this blog series.

由于这些缺点,我们决定不使用 GRUB 或多重引导标准。 但是,我们计划在 bootimage 工具中添加多重引导支持,以便也可以在 GRUB 系统上加载内核。 如果您有兴趣编写符合多重引导的内核,请查看本博客系列的第一版。

UEFI

(We don’t provide UEFI support at the moment, but we would love to! If you’d like to help, please tell us in the Github issue.)

(我们目前不提供 UEFI 支持,但我们很乐意提供!如果您想提供帮助,请在 Github 问题中告诉我们。)

A Minimal Kernel

Now that we roughly know how a computer boots, it’s time to create our own minimal kernel. Our goal is to create a disk image that prints a “Hello World!” to the screen when booted. We do this by extending the previous post’s freestanding Rust binary.

现在我们大致了解了计算机如何启动,是时候创建我们自己的最小内核了。 我们的目标是创建一个打印“Hello World!”的磁盘映像。 启动时显示到屏幕。 我们通过扩展上一篇文章的独立 Rust 二进制文件来实现这一点。

As you may remember, we built the freestanding binary through cargo, but depending on the operating system, we needed different entry point names and compile flags. That’s because cargo builds for the host system by default, i.e., the system you’re running on. This isn’t something we want for our kernel, because a kernel that runs on top of, e.g., Windows, does not make much sense. Instead, we want to compile for a clearly defined target system.

您可能还记得,我们通过 Cargo 构建了独立的二进制文件,但根据操作系统,我们需要不同的入口点名称和编译标志。 这是因为默认情况下,cargo 是为主机系统(即您正在运行的系统)构建的。 这不是我们想要的内核,因为运行在 Windows 之上的内核没有多大意义。 相反,我们希望针对明确定义的目标系统进行编译。

Installing Rust Nightly

Rust has three release channels: stablebeta, and nightly. The Rust Book explains the difference between these channels really well, so take a minute and check it out. For building an operating system, we will need some experimental features that are only available on the nightly channel, so we need to install a nightly version of Rust.

Rust 有 3 个发布渠道:stable、beta 和 nightly。 Rust Book 很好地解释了这些通道之间的区别,所以花点时间检查一下。 为了构建操作系统,我们需要一些仅在 nightly 频道上可用的实验性功能,因此我们需要安装 Rust 的 nightly 版本。

To manage Rust installations, I highly recommend rustup. It allows you to install nightly, beta, and stable compilers side-by-side and makes it easy to update them. With rustup, you can use a nightly compiler for the current directory by running rustup override set nightly. Alternatively, you can add a file called rust-toolchain with the content nightly to the project’s root directory. You can check that you have a nightly version installed by running rustc --version: The version number should contain -nightly at the end.

要管理 Rust 安装,我强烈推荐 rustup。 它允许您并行安装 nightly、beta 和 stable 编译器,并且可以轻松更新它们。 使用 rustup,您可以通过运行 rustup override set nightly 对当前目录使用 nightly 编译器。 或者,您可以将一个名为 rust-toolchain 的文件(其中包含 nightly 的内容)添加到项目的根目录中。 您可以通过运行 rustc --version 检查是否安装了 nightly 版本:版本号应在末尾包含 -nightly。

The nightly compiler allows us to opt-in to various experimental features by using so-called feature flags at the top of our file. For example, we could enable the experimental asm! macro for inline assembly by adding #![feature(asm)] to the top of our main.rs. Note that such experimental features are completely unstable, which means that future Rust versions might change or remove them without prior warning. For this reason, we will only use them if absolutely necessary.

nightly 编译器允许我们通过使用文件顶部所谓的功能标志来选择各种实验功能。 例如,我们可以启用实验性的 asm! 通过将 #![feature(asm)] 添加到 main.rs 的顶部来实现内联汇编宏。 请注意,此类实验性功能完全不稳定,这意味着未来的 Rust 版本可能会在没有事先警告的情况下更改或删除它们。 因此,我们只会在绝对必要的情况下使用它们。

Target Specification

Cargo supports different target systems through the --target parameter. The target is described by a so-called target triple, which describes the CPU architecture, the vendor, the operating system, and the ABI. For example, the x86_64-unknown-linux-gnu target triple describes a system with an x86_64 CPU, no clear vendor, and a Linux operating system with the GNU ABI. Rust supports many different target triples, including arm-linux-androideabi for Android or wasm32-unknown-unknown for WebAssembly.

Cargo 通过 --target 参数支持不同的目标系统。 目标由所谓的目标三元组来描述,它描述了CPU架构、供应商、操作系统和ABI。 例如,x86_64-unknown-linux-gnu 目标三元组描述了具有 x86_64 CPU、没有明确供应商的系统以及具有 GNU ABI 的 Linux 操作系统。 Rust 支持许多不同的目标三元组,包括适用于 Android 的 arm-linux-androideabi 或适用于 WebAssembly 的 wasm32-unknown-unknown。

For our target system, however, we require some special configuration parameters (e.g. no underlying OS), so none of the existing target triples fits. Fortunately, Rust allows us to define our own target through a JSON file. For example, a JSON file that describes the x86_64-unknown-linux-gnu target looks like this:

然而,对于我们的目标系统,我们需要一些特殊的配置参数(例如,没有底层操作系统),因此现有的目标三元组都不适合。 幸运的是,Rust 允许我们通过 JSON 文件定义自己的目标。 例如,描述 x86_64-unknown-linux-gnu 目标的 JSON 文件如下所示:

{
    "llvm-target": "x86_64-unknown-linux-gnu",
    "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
    "arch": "x86_64",
    "target-endian": "little",
    "target-pointer-width": "64",
    "target-c-int-width": "32",
    "os": "linux",
    "executables": true,
    "linker-flavor": "gcc",
    "pre-link-args": ["-m64"],
    "morestack": false
}

Most fields are required by LLVM to generate code for that platform. For example, the data-layout field defines the size of various integer, floating point, and pointer types. Then there are fields that Rust uses for conditional compilation, such as target-pointer-width. The third kind of field defines how the crate should be built. For example, the pre-link-args field specifies arguments passed to the linker.

LLVM 需要大多数字段来为该平台生成代码。 例如,data-layout  字段定义各种整数、浮点和指针类型的大小。 然后是 Rust 用于条件编译的字段,例如 target-pointer-width。 第三种字段定义了如何构建 crate。 例如,pre-link-args 字段指定传递给链接器的参数。

We also target x86_64 systems with our kernel, so our target specification will look very similar to the one above. Let’s start by creating an x86_64-blog_os.json file (choose any name you like) with the common content:

我们的内核还针对 x86_64 系统,因此我们的目标规范看起来与上面的非常相似。 让我们首先创建一个包含常见内容的 x86_64-blog_os.json 文件(选择您喜欢的任何名称):

{
    "llvm-target": "x86_64-unknown-none",
    "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
    "arch": "x86_64",
    "target-endian": "little",
    "target-pointer-width": "64",
    "target-c-int-width": "32",
    "os": "none",
    "executables": true
}

Note that we changed the OS in the llvm-target and the os field to none, because we will run on bare metal.

请注意,我们将 llvm-target 中的操作系统和 os 字段更改为 none,因为我们将在裸机上运行。

We add the following build-related entries:

我们添加以下与构建相关的条目:

"linker-flavor": "ld.lld",
"linker": "rust-lld",

Instead of using the platform’s default linker (which might not support Linux targets), we use the cross-platform LLD linker that is shipped with Rust for linking our kernel.

我们不使用平台的默认链接器(可能不支持 Linux 目标),而是使用 Rust 附带的跨平台 LLD 链接器来链接我们的内核。

"panic-strategy": "abort",

This setting specifies that the target doesn’t support stack unwinding on panic, so instead the program should abort directly. This has the same effect as the panic = "abort" option in our Cargo.toml, so we can remove it from there. (Note that, in contrast to the Cargo.toml option, this target option also applies when we recompile the core library later in this post. So, even if you prefer to keep the Cargo.toml option, make sure to include this option.)

此设置指定目标不支持 panic 情况下的堆栈展开,因此程序应直接中止。 这与 Cargo.toml 中的panic =“abort”选项具有相同的效果,因此我们可以从那里删除它。 (请注意,与 Cargo.toml 选项相反,当我们在本文后面重新编译核心库时,此目标选项也适用。因此,即使您希望保留 Cargo.toml 选项,也请确保包含此选项。 )

"disable-redzone": true,

We’re writing a kernel, so we’ll need to handle interrupts at some point. To do that safely, we have to disable a certain stack pointer optimization called the “red zone”, because it would cause stack corruption otherwise. For more information, see our separate post about disabling the red zone.

我们正在编写一个内核,因此我们需要在某些时候处理中断。 为了安全地做到这一点,我们必须禁用称为“红色区域”的某种堆栈指针优化,因为否则会导致堆栈损坏。 有关更多信息,请参阅我们关于禁用红色区域的单独帖子。

"features": "-mmx,-sse,+soft-float",

The features field enables/disables target features. We disable the mmx and sse features by prefixing them with a minus and enable the soft-float feature by prefixing it with a plus. Note that there must be no spaces between different flags, otherwise LLVM fails to interpret the features string.

features 字段启用/禁用目标功能。 我们通过在 mmx 和 sse 功能前添加减号来禁用它们,并通过在其前添加加号来启用软浮动功能。 请注意,不同标志之间不能有空格,否则 LLVM 无法解释特征字符串。

The mmx and sse features determine support for Single Instruction Multiple Data (SIMD) instructions, which can often speed up programs significantly. However, using the large SIMD registers in OS kernels leads to performance problems. The reason is that the kernel needs to restore all registers to their original state before continuing an interrupted program. This means that the kernel has to save the complete SIMD state to main memory on each system call or hardware interrupt. Since the SIMD state is very large (512–1600 bytes) and interrupts can occur very often, these additional save/restore operations considerably harm performance. To avoid this, we disable SIMD for our kernel (not for applications running on top!).

mmx 和 sse 功能决定了对单指令多数据 (SIMD) 指令的支持,这通常可以显着加快程序速度。 然而,在操作系统内核中使用大型 SIMD 寄存器会导致性能问题。 原因是内核需要在继续中断的程序之前将所有寄存器恢复到原始状态。 这意味着内核必须在每次系统调用或硬件中断时将完整的 SIMD 状态保存到主内存。 由于 SIMD 状态非常大(512-1600 字节)并且中断可能经常发生,因此这些额外的保存/恢复操作会极大地损害性能。 为了避免这种情况,我们为内核禁用 SIMD(不适用于在顶部运行的应用程序!)。

A problem with disabling SIMD is that floating point operations on x86_64 require SIMD registers by default. To solve this problem, we add the soft-float feature, which emulates all floating point operations through software functions based on normal integers.

禁用 SIMD 的一个问题是 x86_64 上的浮点运算默认需要 SIMD 寄存器。 为了解决这个问题,我们添加了软浮点功能,它通过基于普通整数的软件函数来模拟所有浮点运算。

For more information, see our post on disabling SIMD.

有关更多信息,请参阅我们关于禁用 SIMD 的文章。

Putting it Together

Our target specification file now looks like this:

我们的目标规范文件现在如下所示:

{
    "llvm-target": "x86_64-unknown-none",
    "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
    "arch": "x86_64",
    "target-endian": "little",
    "target-pointer-width": "64",
    "target-c-int-width": "32",
    "os": "none",
    "executables": true,
    "linker-flavor": "ld.lld",
    "linker": "rust-lld",
    "panic-strategy": "abort",
    "disable-redzone": true,
    "features": "-mmx,-sse,+soft-float"
}

Building our Kernel

Compiling for our new target will use Linux conventions (I’m not quite sure why; I assume it’s just LLVM’s default). This means that we need an entry point named _start as described in the previous post:

为我们的新目标进行编译将使用 Linux 约定(我不太清楚为什么;我认为这只是 LLVM 的默认设置)。 这意味着我们需要一个名为 _start 的入口点,如上一篇文章中所述:

// src/main.rs

#![no_std] // don't link the Rust standard library
#![no_main] // disable all Rust-level entry points

use core::panic::PanicInfo;

/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

#[no_mangle] // don't mangle the name of this function
pub extern "C" fn _start() -> ! {
    // this function is the entry point, since the linker looks for a function
    // named `_start` by default
    loop {}
}

Note that the entry point needs to be called _start regardless of your host OS.

请注意,无论您的主机操作系统如何,入口点都需要调用 _start。

We can now build the kernel for our new target by passing the name of the JSON file as --target:

现在,我们可以通过将 JSON 文件的名称传递为 --target 来为新目标构建内核:

> cargo build --target x86_64-blog_os.json

error[E0463]: can't find crate for `core`

It fails! The error tells us that the Rust compiler no longer finds the core library. This library contains basic Rust types such as ResultOption, and iterators, and is implicitly linked to all no_std crates.

它失败了! 该错误告诉我们 Rust 编译器不再找到核心库。 该库包含基本的 Rust 类型,例如 Result、Option 和迭代器,并隐式链接到所有 no_std crate。

The problem is that the core library is distributed together with the Rust compiler as a precompiled library. So it is only valid for supported host triples (e.g., x86_64-unknown-linux-gnu) but not for our custom target. If we want to compile code for other targets, we need to recompile core for these targets first.

问题在于核心库是作为预编译库与 Rust 编译器一起分发的。 因此它仅对受支持的主机三元组(例如 x86_64-unknown-linux-gnu)有效,但对我们的自定义目标无效。 如果我们想为其他目标编译代码,我们需要首先为这些目标重新编译核心。

The build-std Option

That’s where the build-std feature of cargo comes in. It allows to recompile core and other standard library crates on demand, instead of using the precompiled versions shipped with the Rust installation. This feature is very new and still not finished, so it is marked as “unstable” and only available on nightly Rust compilers.

这就是 Cargo 的 build-std 功能的用武之地。它允许根据需要重新编译核心和其他标准库包,而不是使用 Rust 安装附带的预编译版本。 这个功能非常新,还没有完成,因此它被标记为“不稳定”,并且仅在 nightly Rust 编译器上可用。

To use the feature, we need to create a local cargo configuration file at .cargo/config.toml (the .cargo folder should be next to your src folder) with the following content:

要使用该功能,我们需要在 .cargo/config.toml (.cargo 文件夹应该位于 src 文件夹旁边)创建一个本地 cargo 配置文件,其中包含以下内容:

# in .cargo/config.toml

[unstable]
build-std = ["core", "compiler_builtins"]

This tells cargo that it should recompile the core and compiler_builtins libraries. The latter is required because it is a dependency of core. In order to recompile these libraries, cargo needs access to the rust source code, which we can install with rustup component add rust-src.

这告诉Cargo它应该重新编译核心和compiler_builtins库。 后者是必需的,因为它是核心的依赖项。 为了重新编译这些库,cargo 需要访问 rust 源代码,我们可以使用 rustup 组件 add rust-src 来安装它。

Note: The unstable.build-std configuration key requires at least the Rust nightly from 2020-07-15.

注意:unstable.build-std 配置密钥至少需要 2020 年 7 月 15 日起的 Rust nightly。

After setting the unstable.build-std configuration key and installing the rust-src component, we can rerun our build command:

设置 stable.build-std 配置密钥并安装 rust-src 组件后,我们可以重新运行构建命令:

> cargo build --target x86_64-blog_os.json
   Compiling core v0.0.0 (/…/rust/src/libcore)
   Compiling rustc-std-workspace-core v1.99.0 (/…/rust/src/tools/rustc-std-workspace-core)
   Compiling compiler_builtins v0.1.32
   Compiling blog_os v0.1.0 (/…/blog_os)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29 secs

We see that cargo build now recompiles the corerustc-std-workspace-core (a dependency of compiler_builtins), and compiler_builtins libraries for our custom target.

我们看到,cargo build 现在为我们的自定义目标重新编译了核心、rustc-std-workspace-core(compiler_builtins 的依赖项)和compiler_builtins 库。

The Rust compiler assumes that a certain set of built-in functions is available for all systems. Most of these functions are provided by the compiler_builtins crate that we just recompiled. However, there are some memory-related functions in that crate that are not enabled by default because they are normally provided by the C library on the system. These functions include memset, which sets all bytes in a memory block to a given value, memcpy, which copies one memory block to another, and memcmp, which compares two memory blocks. While we didn’t need any of these functions to compile our kernel right now, they will be required as soon as we add some more code to it (e.g. when copying structs around).

Since we can’t link to the C library of the operating system, we need an alternative way to provide these functions to the compiler. One possible approach for this could be to implement our own memset etc. functions and apply the #[no_mangle] attribute to them (to avoid the automatic renaming during compilation). However, this is dangerous since the slightest mistake in the implementation of these functions could lead to undefined behavior. For example, implementing memcpy with a for loop may result in an infinite recursion because for loops implicitly call the IntoIterator::into_iter trait method, which may call memcpy again. So it’s a good idea to reuse existing, well-tested implementations instead.

Fortunately, the compiler_builtins crate already contains implementations for all the needed functions, they are just disabled by default to not collide with the implementations from the C library. We can enable them by setting cargo’s build-std-features flag to ["compiler-builtins-mem"]. Like the build-std flag, this flag can be either passed on the command line as a -Z flag or configured in the unstable table in the .cargo/config.toml file. Since we always want to build with this flag, the config file option makes more sense for us: