Skip to content

fix zcore bug

Yu Chen edited this page Aug 10, 2021 · 4 revisions

一次发现并修补(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支持测试单个测例

基本思路

于是我们开始进行对zCore的CI/CD进行扩展。实现内核态zCore的测试,需要有一个模拟物理机的虚拟机软件(我们采用的QEMU)。参考zCore现有Makefile中的一些运行脚本,我们首先需要让zCore通过某种方式很方便地支持启动不同的应用程序。这其实在zCore中已经有所考虑了。以x86-64的硬件环境为例,zCore支持基于UEFIbootloaderrboot的启动,而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的CI/CD脚本

在已有.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中文件系统对文件的读写准备的,目前其实没有用到。这里面的大致执行流程的含义写在了usesname中。具体的步骤是:

  1. runs-on: ubuntu-20.04:在ubuntu-20.04 x86-64上运行测试
  2. uses: actions/checkout@v2:获取zCore repo
  3. uses: actions-rs/toolchain@v1:安装rustc-nightly-2021-07-27工具链,包含rust-src component
  4. name: Pull prebuilt images:下载预编译的fs镜像,和用于LibOS模式的定制libc-libos.so (这里的测试其实用不上)
  5. name: Install musl toolchain qemu-system-x86:安装编译musl libc-tests测试用例的工具和QEMU工具
  6. name: Prepare rootfs and libc-test:构建rootfs,编译libc-tests测试用例,并放到rootfs中
  7. name: Build kernel:编译zCore kernel
  8. name: create qemu disk:建立一个虚拟盘,用于文件相关的测试(目前的测试没有用)
  9. name: Run baremetal-libc-test:执行测试脚本,测试libc-test测试用例

这样,就建立好了基于GITHUB的CI/CD自动测试。

Clone this wiki locally