RISC-V裸机程序(bare-metal-app)

前言

正常用户程序是运行在OS上的,而我们的OS是运行在SBI上,所以不能使用std库,而是使用core库并且环境也需要我们自己去进行配置

|------|-----------------------------------------|-------------------------------------|
| 环节 | 普通用户程序 | 裸机程序 |
| 标准库 | 依赖 std 库,std 库依赖 OS 的系统调用 | 禁用 std 库,只用不依赖 OS 的 core 库 |
| 运行时 | 有 std 提供的运行时,负责初始化栈、调用 main 函数、处理 panic | 无任何运行时,必须自己实现入口、栈初始化、panic 处理 |
| 目标平台 | 编译为宿主平台(x86_64/arm64)的可执行文件 | 交叉编译为 riscv64 平台的裸机 ELF 文件,不依赖任何 OS |
| 链接环节 | 用系统默认的链接脚本,适配 OS 的程序加载规则 | 必须自己写链接脚本,指定内存布局、入口地址、段的位置 |

构建kernel可执行机器码步骤:

entry.asm(kernel基本布局) -> entry.o(目标文件) -> os(ELF文件) -> os.bin(二进制文件)

开发环境搭建

这里系统我自己有两台电脑,暂时用Ubuntu,另一个是Arch的。

1.配置rustup

安装RISCV交叉编译所需的Rust组件:

bash 复制代码
# 使用nighty发行版
rustup update nightly

# 添加riscv64目标平台支持
rustup target add riscv64gc-unknown-none-elf
# 安装编译所需的工具组件
rustup component add rust-src llvm-tools-preview
cargo install cargo-binutils

补充每个组件的作用:

  • riscv64gc-unknown-none-elf:RISC-V 64 位目标平台的编译后端,让 rustc 能生成 RISC-V 架构的指令;
  • rust-src:提供 Rust 核心库的源码,用于no_std环境下编译 core 库;
  • llvm-tools-preview:提供rust-objcopy、rust-objdump等工具,用于 ELF 转 BIN、反汇编;
  • cargo-binutils:让 cargo 可以直接调用 llvm 工具链,简化交叉编译操作。

2. 安装QEMU

这里我选择手动编译QEMU,觉得麻烦的可以直接使仓库一体式安装:

bash 复制代码
sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential git qemu-system-misc gcc-riscv64-linux-gnu gdb-multiarch

安装qeMu编译需要的依赖和工具:

bash 复制代码
sudo apt-get update
sudo apt-get install -y curl git python3 wget
sudo apt-get install -y autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat1-dev git ninja-build pkg-config libglib2.0-dev libpixman-1-dev libsdl2-dev

安装完后下载qeMu然后解压和编译然后安装:

bash 复制代码
# 这里QEMU_VERSION填写你自己想下载的版本
sudo wget https://download.qemu.org/qemu-${QEMU_VERSION}.tar.xz

# 我下载的是7.0.0
sudo tar xvJf qemu-7.0.0.tar.xz 

cd qemu-7.0.0/
# 设置configura,我们只需要riscv的
./configure --target-list=riscv64-softmmu,riscv64-linux-user

# 开始编译
sudo make -j$(nproc) && make install


# 检验是否安装成功
qemu-system-risv64 --version
qemu-riscv64 --version
 

编译工具链

在前面已经设置了rustc的目标平台,这样就会将代码交叉编译到riscv平台,接下来就是要配置编译工具链。

交叉编译与目标三元组

目标三元组是riscv64gc-unknown-none-elf,每一段的含义直接决定了编译行为:

  • riscv64gc:CPU 架构,riscv64 位,g代表通用扩展(IMAFD),c代表压缩指令扩展,是 RISC-V 64 位的标准配置;
  • unknown(目标厂商):无特定厂商
  • none(目标操作系统):无操作系统,也就是裸机环境
  • elf(可执行文件格式):ELF 格式

这就是为什么我们能编译出不依赖 Linux、不依赖任何 OS 的 RISC-V 程序。

ELF文件格式

ELF(可执行与可链接格式)是编译后生成的文件格式,核心分为 4 部分,每一部分都和程序生死相关:

  • ELF头:记录文件的目标架构(riscv64)、入口地址、程序头表 / 节头表的位置
  • 程序头表:告诉加载器(QEMU 的 loader),哪些段需要加载到内存里、加载到什么地址,裸机程序的内存布局,核心靠程序头表决定;
  • 节(Section):程序的核心内容,裸机里最关键的 4 个节
  • .text:代码段,存我们写的汇编、Rust 编译出的 CPU 指令
  • .rodata:只读数据段,存字符串常量(比如 "hello world")
  • .data:数据段,存已初始化的全局变量、静态变量
  • .bss:未初始化数据段,存未初始化的全局 / 静态变量,必须在启动时手动清零,否则会出现未定义行为(PPT 完全没提这个新手必踩的坑)
  • 节头表:记录每个节的位置、大小、属性。

LLVM编译器

Rust目前使用用LLVM架构,核心是「前端 - IR - 后端」的解耦,这也是 Rust 做交叉编译这么简单的核心原因:

  1. 前端:把 Rust 源代码解析、生成 LLVM IR(中间表示),和目标平台无关;
  2. 优化器:对 LLVM IR 做平台无关的优化;
  3. 后端:把优化后的 IR,翻译成目标平台(riscv64)的汇编指令,最终生成机器码。

这就是为什么我们只需要加一个 target,就能从 x86 主机编译出 RISC-V 的程序,不需要自己改编译器。

QEMU+SBI启动流程

在写代码之前,必须先明白我们的程序是怎么被执行的:

  1. 执行 QEMU 启动命令,QEMU 会先加载我们指定的 BIOS(RustSBI/OpenSBI 固件)
  2. SBI 固件会初始化 RISC-V 硬件,CPU 进入 M 模式(机器模式,最高特权级)执行SBI代码
  3. SBI 初始化完成后,会把 CPU 切换到 S 模式(监管者模式),然后跳转到指定的内核加载地址(0x80200000)

程序入口(_start),必须放在 0x80200000 这个地址,CPU从这里开始执行内核代码。

编写汇编入口代码

Rust 代码的执行,依赖合法的栈空间(函数调用会压栈、用栈存局部变量),而 CPU 刚跳转到我们的程序时,sp(栈指针寄存器)的值是随机的,没法执行Rust代码

必须先用汇编做2件最基础的事:

  1. 设置合法的栈空间,给sp寄存器赋一个正确的地址
  2. 跳转到 Rust 代码(rust_main),让Rust代码能正常执行

在src目录下创建entry.asm文件

TypeScript 复制代码
    # 定义一个代码段,名为.text.entry,专门放入口代码
    .section .text.entry

    # 把_start(程序入口)符号导出为全局符号,让链接器能看到
    .globl _start
_start:
    la sp, boot_stack_top
    call rust_main

    # 定义bss段,专门放栈空间(bss段是未初始化的内存,不会占用ELF文件体积)
    .section .bss.stack

    # 导出栈的下界符号
    .globl boot_stack_lower_bound
boot_stack_lower_bound:
    .space 4096 * 16
    
    # 导出栈的上界符号
    .globl boot_stack_top
boot_stack_top:
  1. .section .text.entry:把这段代码放到.text.entry段里,后续我们会在链接脚本里,把这个段放在最开头的 0x80200000 地址,确保 CPU 跳过来第一行就执行_start
  2. .globl _start:_start是链接器默认的程序入口符号,必须导出为全局,否则链接器找不到入口地址
  3. la sp, boot_stack_top:RISC-V 的栈是向下增长的(从高地址向低地址压栈),所以栈指针必须初始化为栈空间的最高地址(boot_stack_top),而不是最低地址。如果设反了,栈一压栈就会访问非法内存,直接崩溃
  4. .space 4096 * 16:预留 64KB 的连续内存作为栈空间,.space指令会在bss段里预留指定大小的内存,初始值为 0
  5. 为什么栈放在.bss段?因为 bss 段的内容不会被存到 ELF 文件里,只会记录大小,编译出来的文件体积更小,而且启动时会统一清零 bss 段

当你执行cargo build时,Rust 的构建系统会调用汇编器(as) 处理entry.asm:

  1. 汇编器逐行解析entry.asm里的汇编指令(比如la、call、.section);
  2. 把每一条汇编指令翻译成对应的 RISC-V 机器码(比如la sp, boot_stack_top会被翻译成0x00000013这类二进制数);
  3. 生成目标文件(entry.o) ------ 包含机器码、符号表(比如_start、boot_stack_top这些符号对应的地址)、段信息(比如.text.entry、.bss.stack)。
关键补充:
  • 你不用手动执行汇编命令,Cargo 会通过build.rs(构建脚本)或rustflags自动处理.asm文件;
  • 如果想手动验证,可以执行:
bash 复制代码
# 把entry.asm汇编成riscv64的目标文件
riscv64-linux-gnu-as entry.asm -o entry.o

# 查看目标文件里的符号(能看到_start、boot_stack_top的地址)
riscv64-linux-gnu-nm entry.o

编写链接脚本

编译器默认的链接脚本,是给运行在 OS 上的用户程序用的,默认的内存地址、入口布局完全不符合 RISC-V 裸机的要求,会导致:

  1. 程序入口不在 0x80200000 地址,CPU 跳过来执行不到我们的代码
  2. 代码段、数据段、bss 段的布局混乱,内存访问出错
  3. 找不到我们定义的_start入口、栈符号

在项目根目录下创建linker.ld文件,写入以下完整的链接脚本,逐行带注释:

TypeScript 复制代码
/* 指定目标CPU架构 */
OUTPUT_ARCH(riscv)

/* 指定程序的入口符号,就是我们汇编里写的_start */
ENTRY(_start)

/* 定义目标架构的内存地址:QEMU virt平台的物理内存从0x80000000开始 */
/* 我们的内核加载地址是0x8020000,前面的0x80000000-0x80200000留给SBI固件用 */
BASE_ADDRESS = 0x80200000;

/* 核心:段的布局定义 */
SECTIONS
{
    
    /* 把所有段的起始地址,设置为BASE_ADDRESS=0x80200000 */
    . = BASE_ADDRESS;
    skernel = .;
    stext = .;

    /* 代码段:放所有的CPU指令 */
    .text : {

        /* 先放我们的入口代码.text.entry,确保入口在0x80200000的起始位置 */
        *(.text.entry)

        /* 再放其他所有的代码段 */
        *(.text .text.*)
    }


    /* 4K对齐,和内存页大小一致 */
    . = ALIGN(4K);
    etext = .;
    srodata = .;

    /* 只读数据段:放字符串常量、只读的全局变量 */
    .rodata : {
        *(.rodata .rodata.*)
        *(.srodata .srodata.*)
    }

    . = ALIGN(4K);
    erodata = .;
    sdata = .;
    
    /* 数据段:放已初始化的全局/静态变量 */
    .data : {
        *(.data .data.*)
        *(.sdata .sdata.*)
    }

    . = ALIGN(4K);
    edata = .;

    /* BSS段:放未初始化的全局/静态变量,包括我们的栈空间 */
    .bss : {
        *(.bss.stack)

        /* 先记录bss段的起始地址,后续清零用 */
        sbss = .;
        *(.bss .bss.*)
        *(.sbss .sbss.*) /* 栈空间就在这里 */
    }

    . = ALIGN(4K);

    /* 记录bss段的结束地址 */
    ebss = .;

    /* 记录内核的结束地址,后续内存管理会用到 */
    ekernel = .;

     /* 把不需要的段都丢弃,比如用户程序用的.comment、note段等 */
    /DISCARD/ : {
        *(.eh_frame)
    }
}

先放.text.entry:确保入口汇编代码,就在 0x80200000 这个起始地址,CPU 跳过来第一行就执行_start。

ALIGN(4K): 让每个段都按 4KB 对齐,和 RISC-V 的页大小一致,后续做内存管理、页表映射时不会出问题

sbss和ebss: 记录了 bss 段的起始和结束地址,可以在 Rust 代码里,用这两个符号把整个 bss段清零,解决未初始化变量的未定义行为

链接器(ld)会读取你写的linker.ld链接脚本,做两件核心事:

1.合并所有目标文件:把entry.o(汇编机器码)和Rust 编译出的main.o(Rust 转的机器码)合并

2.按链接脚本分配地址:

  • 把.text.entry段(_start的机器码)放在BASE_ADDRESS=0x80200000;
  • 把.bss.stack段(栈空间)分配到后续的内存地址;
  • 把_start标记为程序入口;
  • 生成最终的ELF 可执行文件(target/riscv64gc-unknown-none-elf/debug/os)------ 包含所有机器码、内存地址映射、符号表。

**关键:**这一步后,_start对应的机器码就被固定在0x80200000地址上,boot_stack_top也有了具体的内存地址(比如0x80204000)

cargo配置

创建.cargo目录,在里面创建config.toml文件,写入以下配置:

rust 复制代码
[build]

# 指定默认的编译目标,不用每次都加--target
target = "riscv64gc-unknown-none-elf"

[target.riscv64gc-unknown-none-elf]

# 指定链接器参数,用我们写的linker.ld作为链接脚本
rustflags = [
    "-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes"
]

函数部分

这里函数部分就是内核源代码了,内核通过调用SBI来进行执行硬件操作。

SBI调用规则

RISC-V 的 SBI 调用,有严格的寄存器约定

触发指令:ecall指令,会触发环境调用异常,CPU 从 S 模式陷入 M 模式,SBI 固件处理这个异常

参数传递:用 RISC-V 的通用寄存器a0-a7(对应 x10-x17)传递参数:

  • a7寄存器:传递EID(扩展 ID,代表要调用哪一类 SBI 服务)
  • a6寄存器:传递FID(功能 ID,代表该类服务里的具体功能)
  • a0-a5寄存器:传递调用的具体参数

返回值:SBI 调用完成后,返回值存在两个寄存器里:

  • a0:错误码,0 代表成功,非 0 代表错误类型
  • a1:返回的数值

实现基础的SBI调用函数,创建一个sbi.rs:

rust 复制代码
use core::arch::asm;

const SBI_CONSOLE_PUTCHAR: usize = 1;

/// 通用SBI调用函数
#[inline(always)]
fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
    let mut ret;
    unsafe {
        asm!(
            "li x16, 0",
            "ecall",
            inlateout("x10") arg0 => ret,
            in("x11") arg1,
            in("x12") arg2,
            in("x17") which,
        );
    }
    ret
}

/// sbi打印指令
/// EID = 1,c(a0)为需要打印的参数
pub fn console_putchar(c: usize) {
    sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0);
}

use crate::board::QEMUExit;
/// use sbi call to shutdown the kernel
pub fn shutdown() -> ! {
    crate::board::QEMU_EXIT_HANDLE.exit_failure();
}

实现 print!和 println!宏

有了console_putchar,就可以实现 Rust 里的格式化输出宏,和 std 里的print!()用法一致

创建console.rs

rust 复制代码
//! SBI console driver, for text output
use crate::sbi::console_putchar;
use core::fmt::{self, Write};

struct Stdout;

/// 实现Write trait,才能支持格式化输出
impl Write for Stdout {
    
    /// 字符输出,调用SBI putchar输出格式化后的字符
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.chars() {
            console_putchar(c as usize);
        }
        Ok(())
    }
}

/// 格式化输出函数
pub fn print_fmt(args: fmt::Arguments) {
    Stdout.write_fmt(args).unwrap();
}

/// 编写宏定义
#[macro_export]
macro_rules! print {
    ($fmt: literal $(, $($arg: tt)+)?) => {
        $crate::console::print_fmt(format_args!($fmt $(, $($arg)+)?))
    }
}

/// 实现println宏,自动换行
#[macro_export]
macro_rules! println {
    ($fmt: literal $(, $($arg: tt)+)?) => {
        $crate::console::print_fmt(format_args!(concat!($fmt, "\n") $(, $($arg)+)?))
    }
}

在main.rs中添加bss 段清零、rust_main 函数:

rust 复制代码
#![deny(missing_docs)]
#![deny(warnings)]
#![no_std]
#![no_main]
#![feature(panic_info_message)]

use core::arch::global_asm;
use log::*;

#[macro_use]
mod console;
mod lang_items;
mod logging;
mod sbi;

#[path = "boards/qemu.rs"]
mod board;

global_asm!(include_str!("entry.asm"));

/// 清空BSS段
pub fn clear_bss() {
    // 导入连接脚本中的函数
    extern "C" {
        fn sbss();
        fn ebss();
    }

    // 清空地址 遍历每一个字节置为0
    (sbss as usize..ebss as usize).for_each(|a| unsafe { (a as *mut u8).write_volatile(0) });
}

/// the rust entry-point of os
#[no_mangle]
pub fn rust_main() -> ! {

    // 导入段
    extern "C" {
        fn stext(); // begin addr of text segment
        fn etext(); // end addr of text segment
        fn srodata(); // start addr of Read-Only data segment
        fn erodata(); // end addr of Read-Only data ssegment
        fn sdata(); // start addr of data segment
        fn edata(); // end addr of data segment
        fn sbss(); // start addr of BSS segment
        fn ebss(); // end addr of BSS segment
        fn boot_stack_lower_bound(); // stack lower bound
        fn boot_stack_top(); // stack top
    }
    clear_bss();
    
    // 使用自己实现的宏定义
    println!("[kernel] Hello, world!");
    use crate::board::QEMUExit;
    crate::board::QEMU_EXIT_HANDLE.exit_success(); // CI autotest success
                                                   //crate::board::QEMU_EXIT_HANDLE.exit_failure(); // CI autoest failed
}

QEMU运行与调试

现在程序已经写好了,接下来要编译成 QEMU 能加载的 BIN 文件,然后用 QEMU 运行

  • ELF 文件:带元信息的可执行文件,包含 ELF 头、程序头、节头、代码和数据。它需要一个能解析 ELF 格式的加载器,才能把代码和数据加载到内存的正确位置。
  • BIN 文件 :纯二进制镜像,只包含代码、数据的原始字节,和内存里应该存放的内容完全一致。QEMU 的-device loader参数,会直接把 BIN 文件的内容,原封不动地拷贝到我们指定的物理地址(0x80200000),不需要解析任何格式,简单高效,适合裸机场景。

手动转换与运行

ELF转BIN

bash 复制代码
rust-objcopy -O binary <输入ELF文件> <输出BIN文件>
rust-objcopy --binary-architecture=riscv64 --strip-all -O binary /target/riscv64gc-unknown-none-elf/debug/os os.bin
  • --strip-all:去掉所有符号信息,减小文件体积;
  • -O binary:指定输出格式为纯二进制 BIN 文件。

准备 SBI 固件

需要 RustSBI 的固件,作为 QEMU 的 BIOS,先下载预编译的固件:

bash 复制代码
# 在项目根目录下,创建bootloader目录
mkdir ../bootloader
cd ../bootloader

# 下载RustSBI的virt平台预编译固件,版本自己选择
wget https://github.com/rustsbi/rustsbi-qemu/releases/download/rustsbi-qemu-0.2.0/rustsbi-qemu-release.bin

mv rustsbi-qemu-release.bin rustsbi-qemu.bin
cd ../os

QEMU运行程序

bash 复制代码
qemu-system-riscv64 \
  -machine virt \
  -nographic \
  -bios ../bootloader/rustsbi-qmenu.bin \
  -device loader,file=os.bin,addr=0x80200000

运行结果:

bash 复制代码
tibbers@HOME:~/rCore-Tutorial-Code-2025S/os$ qemu-system-riscv64   -machine virt   -nographic   -bios ~/rCore-Tutorial-Code-2025S/bootloader/rustsbi-qemu.bin   -device loader,file=os.bin,addr=0x80200000
[rustsbi] RustSBI version 0.3.0-alpha.4, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000ef2
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!

MakeFile进行自动构建

Lua 复制代码
# 目标平台
TARGET := riscv64gc-unknown-none-elf

# 编译模式:debug/release
MODE := release

# 内核ELF文件路径
KERNEL_ELF := target/$(TARGET)/$(MODE)/os

# 内核BIN文件路径
KERNEL_BIN := $(KERNEL_ELF).bin

# asm
DISASM_TMP := target/$(TARGET)/$(MODE)/asm

# Building mode argument
ifeq ($(MODE), release)
	MODE_ARG := --release
endif


# BOARD
BOARD := qemu

# SBI架构
SBI ?= rustsbi

# SBI路径
BOOTLOADER := ../bootloader/$(SBI)-$(BOARD).bin

# 内核entry地址
KERNEL_ENTRY_PA := 0x80200000

# 工具链
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64

# Disassembly
DISASM ?= -x

build: env $(KERNEL_BIN)

env:
	(rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add $(TARGET)
	cargo install cargo-binutils
	rustup component add rust-src
	rustup component add llvm-tools-preview

$(KERNEL_BIN): kernel
	@$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $@

kernel:
	@echo Platform: $(BOARD)
	@cargo build $(MODE_ARG)

clean:
	@cargo clean

disasm: kernel
	@$(OBJDUMP) $(DISASM) $(KERNEL_ELF) | less

disasm-vim: kernel
	@$(OBJDUMP) $(DISASM) $(KERNEL_ELF) > $(DISASM_TMP)
	@vim $(DISASM_TMP)
	@rm $(DISASM_TMP)

run: run-inner

run-inner: build
	@qemu-system-riscv64 \
		-machine virt \
		-nographic \
		-bios $(BOOTLOADER) \
		-device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA)

# 调试模式:启动QEMU,开启GDB调试,暂停执行
debug: build
	@tmux new-session -d \
		"qemu-system-riscv64 -machine virt -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S" && \
		tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \
		tmux -2 attach-session -d

gdbserver: build
	@qemu-system-riscv64 -machine virt -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S

gdbclient:
	@riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'

.PHONY: build env kernel clean disasm disasm-vim run-inner gdbserver gdbclient

通过make run直接构建和启动。

GDB 调试裸机程序

1.启动GDB,连接QEMU

bash 复制代码
gdb-multiarch target/riscv64gc-unknown-none-elf/debug/os

进入 GDB 的交互界面后,执行以下命令:

bash 复制代码
# 连接QEMU的调试端口
target remote :1234
# 在rust_main函数打个断点
break rust_main
# 继续执行,会停在rust_main的入口
continue
# 接下来就可以用调试命令了:
# ni:单步执行一条汇编指令
# n:单步执行一行Rust代码
# s:步入函数
# backtrace:查看函数调用栈
# info registers:查看所有寄存器的值
# x/10i $pc:查看当前指令指针后面的10条汇编指令

这样就可以调试kernel了。

相关推荐
国科安芯1 天前
星载电源遥测模块抗辐照RISC-V MCU的性能适配与应用
单片机·嵌入式硬件·无人机·cocos2d·risc-v
国科安芯6 天前
抗辐照MCU在高空长航时无人机热管理系统中的可靠性研究
单片机·嵌入式硬件·架构·无人机·cocos2d·risc-v
开开心心就好22 天前
内存清理软件灵活设置,自动阈值快捷键清
运维·服务器·windows·pdf·harmonyos·risc-v·1024程序员节
国科安芯24 天前
基于RISC-V架构的抗辐照MCU在空间EDFA控制单元中的可靠性分析
单片机·嵌入式硬件·性能优化·架构·risc-v·安全性测试
国科安芯25 天前
空间站机械臂中MCU与CANFD抗辐照芯片的集成研究
单片机·嵌入式硬件·fpga开发·架构·risc-v
信创天地1 个月前
国产化分布式服务框架双雄:Dubbo与Spring Cloud Alibaba 服务调用解决方案全解析
人工智能·系统架构·开源·dubbo·运维开发·risc-v
weixin_553132071 个月前
探索Vortex开源GPGPU:RISC-V SIMT架构(4-2),TCU 矩阵计算(1)
矩阵·架构·github·risc-v·wmma·simt·tcu
OpenAnolis小助手1 个月前
RISC-V 基金会 Data Center SIG 第六次会议圆满结束,推动数据中心缺口改进及引入
ai·risc-v
码云数智-园园1 个月前
“架构之争,生态之合”:.NET 生态系统对 LoongArch 与 RISC-V 的支持深度解析
架构·.net·risc-v