SVSM: Secure VM Service Module,即安全虚拟机服务模块。
SVSM 的白皮书:https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/58019.pdf
COCONUT-SVSM 项目地址:GitHub – coconut-svsm/svsm: COCONUT-SVSM
git clone https://github.com/coconut-svsm/svsm cd svsm git submodule update --init cargo install bindgen-cli
这个项目是使用 Rust 语言编写的,前期需要安装 Rust 相关工具。
阅读大型项目还是非常推荐安装语言服务器的,这样可以概览一个代码文件实现的函数情况,从而有针对性的阅读。
项目概览
项目根目录下就放了很多目录,大部分是项目所需的库和工具。需要关注的几个代码文件夹是:
-
cpuarch:有 ARM 架构相关的内容,其中包含 VMSA (VM Shared Area)。
-
kernel:项目核心代码文件夹了。
-
stage1:第一启动阶段。
-
syscall:系统调用相关的代码。
其他文件夹就按需阅读。
项目主体
项目的主题代码都在 kernel 目录下。打开文件夹后,可以看看 kernel 的目录结构:
-
各个功能模块,包括:
-
acpi:中断控制相关。
-
cpu:抽象虚拟 cpu 模块。
-
fs:ram_fs 文件系统。
-
locking:锁功能模块。
-
mm:内存管理模块。
-
platform:具体的 Guest CPU 的实现。
-
protocols:按照 SVSM 白皮书实现的相关接口功能。
-
sev:即 SEV 的相关功能。
-
syscall:系统调用。
-
task:任务管理模块。
-
vtpm:TPM 的相关实现。
-
一些其他功能模块。
-
-
单独的代码文件。
这么一看,SVSM 堪称一个完成的操作系统。在单独的代码文件中,可以看到 svsm.rs
代码文件。不妨先从这个代码文件入手。
通过浏览这个代码实现的函数,就发现两个可能比较重要的函数。一个 svsm_start
,一个 svsm_main
。先来看看后者。
svsm_main
完成了 SVSM 客户机的初步配置。包括 SVSM 平台的环境初始化,SVSM 参数配置,相关内存空间的初始化和拷贝。通过 config.load_cpu_info
函数获取 CPU 相关信息和中断相关信息。然后,调用 start_kernel_task
来启动一个 kernel 任务。
再看看 svsm_start
,似乎感觉这个代码才是 SVSM 的主函数。不仅完成了整个 SVSM 的GDT 和 IDT 的初始化,还完成每个 BSP_CPU 的初始化(此处的 BSP 指的是 Bootstrap Processor),包括页表,架构,以及初始任务。这个初始任务,就是 svsm_main
。最后,通过调度的方式,进入到 svsm_main
中执行。
svsm_start
有两个函数参数,应该还有其他函数来调用该函数。直接看代码,不太能直接找到这个函数。但是再看看文件目录,找到了 svsm.lds
链接文件。链接文件的内容如下:
OUTPUT_ARCH(i386:x86-64)
SECTIONS
{
. = 0xffffff8000000000;
.text : {
*(.startup.*)
*(.text)
*(.text.*)
. = ALIGN(16);
entry_code_start = .;
*(.entry.text)
entry_code_end = .;
. = ALIGN(16);
exception_table_start = .;
KEEP(*(__exception_table))
exception_table_end = .;
}
. = ALIGN(4096);
.rodata : { *(.rodata) *(.rodata.*) }
. = ALIGN(4096);
.data : { *(.data) *(.data.*) }
. = ALIGN(4096);
.bss : {
*(.bss) *(.bss.*)
. = ALIGN(4096);
}
. = ALIGN(4096);
}
ENTRY(startup_64)
大概率,整个 SVSM 的启动代码是用汇编写的。用 grep
命令搜索一下 startup
,结果发现,startup
的汇编代码是镶嵌在 Rust 代码中的,也就在svsm.rs
文件中。startup
的汇编代码如下:
.text
.section ".startup.text","ax"
.code64
.globl startup_64
startup_64:
/* Setup stack */
leaq bsp_stack_end(%rip), %rsp
/* Jump to rust code */
movq %r8, %rdi
movq %r9, %rsi
jmp svsm_start
.bss
.align {PAGE_SIZE}
bsp_stack:
.fill 8*{PAGE_SIZE}, 1, 0
bsp_stack_end:
啊startup_64
也仅仅初始化了栈的大小以及将 r8
和 r9
值拷贝到传递参数的两个寄存器中,并调用 svsm_start
。这样看来,系统的根也不在这里。再读读 startup_64
的注释,得到了是 stage2 加载器设置的该入口。
第一阶段启动过程
第一阶段启动过程 (stage1) 的代码在 SVSM 项目根目录下 stage1
文件夹中。主要是 reset
和 stage1
两个汇编代码。后者完成了激活缓存和准备 stage2 启动的工作。
stage2 的准备工作
第二阶段的启动准备工作包括以下内容:
-
初始化栈指针、第二阶段启动二进制文件相关信息,起始和终止地址、内核二进制文件的相关信息,起始和终止地址。
-
将 BSP 一些元数据压入栈。
-
跳转第二阶段启动地址,进入二阶段启动。
第二阶段启动过程
第二阶段启动过程的代码在 kernel/stage2.rs
文件中。从 stage2.lds
链接文件中可以看出,其入口函数仍然为 startup
函数。startup
函数在 boot_stage2.rs
文件中实现。该函数完成一些页面初始化后,调用了 stage2_main
函数。stage2_main
函数在 kernel/stage2.rs
中实现。
stage2_main
函数完成了以下内容:
-
初始化平台的类型。
-
获取 SVSM 的配置信息。
-
分配内核的内存空间。
-
加载第一块内核的ELF和IGVM,得到 kernel_entry 的地址。
-
设置内核的启动选项。
-
设置内核启动选项参数,跳转到 kernel_entry,实际上就是
startup_64
。