Skip to content

Utils

内存布局

下面图“数据存储器”其实是 Data Memory. image.png|500

Qemu 启动流程

  • Step 1:
    • Qemu CPU pc set to 0x1000,执行神秘指令,使得待会儿能跳转到 0x80000000:
0x0000000000001000 in ?? ()
(gdb) x/10i $pc
=> 0x1000:      auipc   t0,0x0
   0x1004:      addi    a2,t0,40
   0x1008:      csrr    a0,mhartid
   0x100c:      ld      a1,32(t0)
   0x1010:      ld      t0,24(t0)
   0x1014:      jr      t0 # 这里 t0 值已经变成 0x80000000
   0x1018:      unimp
   0x101a:      0x8000
   0x101c:      unimp
   0x101e:      unimp
  • Step 2: 0x80000000
    • 这里是 RUSTSBI,会执行很多指令,所以实操时在 0x80200000 打了个断点快速过去:
      • SBI 做的事情有: 对部分硬件例如串口等进行初始化.
      • 通过 mret 跳转到 payload 也就是 kernel 所在的起始地址。
      • kernel 进行一系列的初始化后(内存管理,虚存管理,线程(进程)初始化等),通过 sret 跳转到应用程序的第一条指令开始执行。
0x0000000080000000 in ?? ()
(gdb) x/10i $pc
=> 0x80000000:  auipc   ra,0x2
   0x80000004:  jalr    834(ra)
   0x80000008:  auipc   ra,0x0
   0x8000000c:  jalr    116(ra)
   0x80000010:  j       0x80001690
   0x80000014:  unimp
   0x80000016:  addi    sp,sp,-80
   0x80000018:  sd      ra,72(sp)
   0x8000001a:  ld      a1,40(a0)
   0x8000001c:  ld      a2,32(a0)
  • Step 3: 0x80200000 自己的 Rust 程序:
(gdb) x/5i $pc
=> 0x80200000:  li      ra,100
   0x80200004:  unimp
   0x80200006:  unimp
   0x80200008:  unimp
   0x8020000a:  unimp

What is RustSBI?

[from comment] SBI 是 RISC-V Supervisor Binary Interface 规范的缩写,OpenSBI 是RISC-V官方用C语言开发的SBI参考实现;RustSBI 是用Rust语言实现的SBI。

BIOS 是 Basic Input/Output System,作用是引导计算机系统的启动以及硬件测试,并向OS提供硬件抽象层。

机器上电之后,会从ROM中读取引导代码,引导整个计算机软硬件系统的启动。而整个启动过程是分为多个阶段的,现行通用的多阶段引导模型为:

ROM -> LOADER -> RUNTIME -> BOOTLOADER -> OS

Loader 要干的事情,就是内存初始化,以及加载 Runtime 和 BootLoader 程序。而Loader自己也是一段程序,常见的Loader就包括 BIOS 和 UEFI,后者是前者的继任者。

Runtime 固件程序是为了提供运行时服务(runtime services),它是对硬件最基础的抽象,对OS提供服务,当我们要在同一套硬件系统中运行不同的操作系统,或者做硬件级别的虚拟化时,就离不开Runtime服务的支持。SBI就是RISC-V架构的Runtime规范。

BootLoader 要干的事情包括文件系统引导、网卡引导、操作系统启动配置项设置、操作系统加载等等。常见的 BootLoader 包括GRUB,U-Boot,LinuxBoot等。

而 BIOS/UEFI 的大多数实现,都是 Loader、Runtime、BootLoader 三合一的,所以不能粗暴的认为 SBI 跟 BIOS/UEFI 有直接的可比性。

如果把BIOS当做一个泛化的术语使用,而不是指某个具体实现的话,那么可以认为 SBI 是 BIOS 的组成部分之一。

练习

  • 应用程序在执行过程中,会占用哪些计算机资源? > 占用 CPU 计算资源(CPU 流水线,缓存等),内存(内存不够还会占用外存)等
  • 请用相关工具软件分析并给出应用程序A的代码段/数据段/堆/栈的地址空间范围。
readelf -all ch1-6_1 | nvim

# 输出
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         00000000000002e0  000002e0
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             00000000000002fc  000002fc
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.bu[...] NOTE             000000000000031c  0000031c
       0000000000000024  0000000000000000   A       0     0     4
...

# 解释

  • 列解释:
  • [Nr]: 节的编号。
  • Name: 节的名称。
  • Type: 节的类型。
  • Address: 节在内存中的地址。
  • Offset: 节在文件中的偏移量。
  • Size: 节的大小。
  • EntSize: 节中每个条目的大小(如果适用)。
  • Flags: 节的标志,描述了节的属性。
  • Link: 与该节相关的另一个节的编号。
  • Info: 依赖于节类型的额外信息。
  • Align: 节的对齐方式。
  • 行解释:
  • [ 0]: 这是 NULL 节,每个 ELF 文件都有一个 NULL 节,它不占用空间,主要用于对齐和占位。
  • Type: NULL
  • Address: 0x0000000000000000
  • Offset: 0x00000000
  • Size: 0x0000000000000000
  • Flags: 0 (没有标志)
  • Link: 0
  • Info: 0
  • Align: 0
  • [ 1] .interp: 这个节包含程序解释器的路径名,通常是动态链接器。
  • Type: PROGBITS
  • Address: 0x00000000000002e0
  • Offset: 0x000002e0
  • Size: 0x000000000000001c (28字节)
  • Flags: A (可分配的)
  • Link: 0
  • Info: 0
  • Align: 1

还可以:cat /proc/[pid]/maps,因为 应用程序的堆栈是由内核为其动态分配的,需要在运行时查看。

  1. ** 为何应用程序员编写应用时不需要建立栈空间和指定地址空间?

    应用程度对内存的访问需要通过 MMU 的地址翻译完成,应用程序运行时看到的地址和实际位于内存中的地址是不同的,栈空间和地址空间需要内核进行管理和分配。应用程序的栈指针在 trap return 过程中初始化。此外,应用程序可能需要动态加载某些库的内容,也需要内核完成映射。