-
Notifications
You must be signed in to change notification settings - Fork 213
fix zcore bug
zCore 操作系统目前的核心代码已经接近3万行代码,如何发现并修补zCore的漏洞越来越成为一个值得重视的事情。本文将描述近期在修补zCore操作系统的过程,包括建立CI/CD, 选择测试用例,分析测试用例,分析出错情况,修补内核等。希望能帮助zCore的开发者改进和修补zCore操作系统。
zCore操作系统是一种用 Rust 语言重新实现的 C++ based Zircon 微内核,并扩展了LibOS模式和Linux模式。zCore操作系统可以在用户态和内核态运行Linux应用和Fuchsia应用。随着zCore的功能发展,代码量越来越大,如何发现、测试和修复zCore中的bug也越来越有挑战。目前zCore驻留在GITHUB上,而GITHUB有自动的CI/CD支持。为此,我们想到充分利用GITHUB的CI/CD来帮助我们自动测试zCore的功能是否正常。为此从2020年6月开始,王润基逐步开展了基于zCore的用户态LibOS模式来自动测试zCore操作系统。这部分的测试代码主要在:
# github CI/CD自动测试的总控脚本(会调用下面的测试脚本)
/.github/workflows/main.yml b/.github/workflows/main.yml
# 测试 zircon syscall的测试脚本(基于zircon的core-tests测试集)
/scripts/core-tests.py
# 通过的core-tests测试点列表(基于zircon的core-tests测试集)
/scripts/zircon/test-check-passed.txt
# core-tests测试集列表(基于zircon的core-tests测试集)
/scripts/zircon/testcased.txt
# 测试 linux syscall的测试脚本(基于musl libc的libc-tests测试集)
/scripts/libc-tests.py
# 通过的libc-tests测试集列表 (在zcore LibOS模式下通过的libc-tests测试集)
/scripts/linux/test-result.txt
# 没通过的libc-tests测试集列表 (在zcore LibOS模式下没通过的libc-tests测试集)
/scripts/linux/test-allow-failed.txt
开发者在对zCore仓库的每次提交时,上述这部分测试功能通过GITHUB的CI/CD的管理被自动触发。并且只有在通过了测试(没有引入新bug)的情况下,才能被merge到zCore仓库的master分支。这样在一定程度上有效地杜绝了潜在bug的任意引入。但上述测试功能的一个不足是没有加入对基于内核态的zCore的功能测试,而zCore已经加入了对RISC-V 64 CPU的支持,这导致测试的范围受限,这需要改进上述测试脚本。
于是我们开始进行对zCore的CI/CD进行扩展。实现内核态zCore的测试,需要有一个模拟物理机的虚拟机软件(我们采用的QEMU)。参考zCore现有Makefile中的一些运行脚本,我们首先需要让zCore通过某种方式很方便地支持启动不同的应用程序。这其实在zCore中已经有所考虑了。以x86-64的硬件环境为例,zCore支持基于UEFIbootloader即rboot的启动,而rboot会读取rboot.conf文件中的内容,并把信息传递给zCore kernel。zCore kernel启动后,就可以读到rboot.conf中的内容。所以我们每次把要测试的用例路径名放到这个文件(位于/zCore/target/x86_64/release/esp/EFI/Boot/rboot.conf)中,重启QEMU,就可以在不修改zCore 内核和文件系统的情况下,快速测试不同的测试用例了。
rboot.conf文件格式
...
# LOG=debug/info/error/warn/trace
# add ROOTPROC info ? split CMD and ARG : ROOTPROC=/libc-test/src/functional/argv.exe? OR ROOTPROC=/bin/busybox?sh
cmdline=LOG=error:TERM=xterm-256color:console.shell=true:virtcon.disable=true:ROOTPROC=/libc-test/src/math/fmin.exe?
包含cmdline这一行的内容是rboot要传递给zCore的命令行信息。“:”是分割不同参数的分隔符。最后一部分ROOTPROC=/libc-test/src/math/fmin.exe? 表示的是要zCore执行的应用程序路径和它的参数。其中的ROOTPROC是一个key,/libc-test/src/math/fmin.exe?是用?分隔的两个value。前面的value表示应用程序路径,后面的value表示应用程序的参数。 \zCore\main.rs中的get_rootproc函数完成对应程序路径和参数的解析。如果cmdline这一行没有ROOTPROC,那么zCore将缺省执行/bin/busybox sh命令。
有了rboot.conf文件和zCore的识别后,就算完成了让zCore支持执行不同应用的关键部分。接下来要做的就是写测试脚本了。参考已有的\scripts\libc-test.py的大致思路,我们可以写出\scripts\baremetal\libc-test.py代码:
......
TIMEOUT = 10 # seconds
ZCORE_PATH = '../zCore'
BASE = 'linux/'
CHECK_FILE = BASE + 'baremetal-test-allow.txt'
FAIL_FILE = BASE + 'baremetal-test-fail.txt'
RBOOT_FILE = 'rboot.conf'
RESULT_FILE ='../stdout-zcore'
# 用rboot字符串表示的rboot.conf文件的内容
rboot= r'''
# The config file for rboot.
# Place me at \EFI\Boot\rboot.conf
......
# LOG=debug/info/error/warn/trace
# add ROOTPROC info ? split CMD and ARG : ROOTPROC=/libc-test/src/functional/argv.exe? OR ROOTPROC=/bin/busybox?sh
cmdline=LOG=error:TERM=xterm-256color:console.shell=true:virtcon.disable=true:ROOTPROC='''
......
# 判断zCore或应用执行失败的字符串
FAILED = [
"failed", # 应用产生的
"ERROR", # 内核产生的
]
# 获得要测试的文件列表
with open(CHECK_FILE, 'r') as f:
allow_files = set([case.strip() for case in f.readlines()])
# 获得还没测试通过的文件列表。在下面的循环测试中,避免测试该列表中的文件
with open(FAIL_FILE,'r') as f:
failed_files = set([case.strip() for case in f.readlines()])
# 基于QEMU模拟器,循环更新rboot.conf,并测试zCore,把输出结果放到stdout-zcore文件中,扫描该是否有执行失败的字符串
for file in allow_files:
if not (file in failed_files):
rboot_file=rboot+file+'?'
with open(RBOOT_FILE,'w') as f:
print(rboot_file, file=f)
try:
subprocess.run(r'cp rboot.conf ../zCore && cd ../ && make baremetal-test | tee stdout-zcore '
r'&& '
r'sed -i '
r'"/BdsDxe/d" stdout-zcore',
shell=True, timeout=TIMEOUT, check=True)
with open(RESULT_FILE, 'r') as f:
output=f.read()
break_out_flag = False
for pattern in FAILED:
if re.search(pattern, output):
failed.add(file)
break_out_flag = True
break
if not break_out_flag:
passed.add(file)
except subprocess.CalledProcessError:
failed.add(file)
except subprocess.TimeoutExpired:
timeout.add(file)
# 统计最后的测试结果
......
print("Total tested num: ", len(allow_files)-len(failed_files))
......
# 如果有3个以上的测例没过,该次测试返回-1,表示测试失败
if len(failed) > 3 :
sys.exit(-1)
else:
sys.exit(0)在已有.github\workflow\rustc20210727.yml中,添加如下内容,就可以实现在QEMU上执行 zCore测试的自动化CI/CD。增加的部分如下:
baremetal-libc-test:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2021-07-27
components: rust-src
- name: Pull prebuilt images
run: git lfs pull -I prebuilt/linux/libc-libos.so
- name: Install musl toolchain qemu-system-x86
run: sudo apt-get install musl-tools musl-dev qemu-system-x86 -y
- name: Prepare rootfs and libc-test
run: make baremetal-test-img
- name: Build kernel
run: cd zCore && make build mode=release linux=1 arch=x86_64
- name: create qemu disk
run: cd zCore && make baremetal-qemu-disk mode=release linux=1 arch=x86_64
- name: Run baremetal-libc-test
run: |
cd scripts
python3 ./baremetal-libc-test.py这里面的20~21行是为了以后测试zCore中文件系统对文件的读写准备的,目前其实没有用到。这里面的大致执行流程的含义写在了uses和name中。具体的步骤是:
-
runs-on: ubuntu-20.04:在ubuntu-20.04 x86-64上运行测试 -
uses: actions/checkout@v2:获取zCore repo -
uses: actions-rs/toolchain@v1:安装rustc-nightly-2021-07-27工具链,包含rust-srccomponent -
name: Pull prebuilt images:下载预编译的fs镜像,和用于LibOS模式的定制libc-libos.so (这里的测试其实用不上) -
name: Install musl toolchain qemu-system-x86:安装编译musl libc-tests测试用例的工具和QEMU工具 -
name: Prepare rootfs and libc-test:构建rootfs,编译libc-tests测试用例,并放到rootfs中 -
name: Build kernel:编译zCore kernel -
name: create qemu disk:建立一个虚拟盘,用于文件相关的测试(目前的测试没有用) -
name: Run baremetal-libc-test:执行测试脚本,测试libc-test测试用例
这样,就建立好了基于GITHUB的CI/CD自动测试。