pkg/build: pave way for multi-OS support

Unify kernel and image build, that distinction is really uninteresting.
Define interface that each OS needs to implement.
Add gvisor stub.
This commit is contained in:
Dmitry Vyukov 2018-06-21 17:45:53 +02:00
parent 8c9738f9c7
commit ea804a7120
7 changed files with 134 additions and 73 deletions

View File

@ -101,7 +101,8 @@ func (env *env) bisect() (*git.Commit, error) {
if env.head, err = git.Poll(cfg.Manager.KernelSrc, cfg.Kernel.Repo, cfg.Kernel.Branch); err != nil {
return nil, err
}
if err := build.Clean(cfg.Manager.KernelSrc); err != nil {
if err := build.Clean(cfg.Manager.TargetOS, cfg.Manager.TargetArch,
cfg.Manager.Type, cfg.Manager.KernelSrc); err != nil {
return nil, fmt.Errorf("kernel clean failed: %v", err)
}
env.log("building syzkaller on %v", cfg.Syzkaller.Commit)
@ -220,7 +221,8 @@ func (env *env) test() (git.BisectResult, error) {
}
env.log("testing commit %v with %v", current.Hash, compilerID)
buildStart := time.Now()
if err := build.Clean(cfg.Manager.KernelSrc); err != nil {
if err := build.Clean(cfg.Manager.TargetOS, cfg.Manager.TargetArch,
cfg.Manager.Type, cfg.Manager.KernelSrc); err != nil {
return 0, fmt.Errorf("kernel clean failed: %v", err)
}
err = env.inst.BuildKernel(be.compiler, cfg.Kernel.Userspace,

View File

@ -12,6 +12,53 @@ import (
"github.com/google/syzkaller/pkg/osutil"
)
// Image creates a disk image for the specified OS/ARCH/VM.
// Kernel is taken from kernelDir, userspace system is taken from userspaceDir.
// If cmdlineFile is not empty, contents of the file are appended to the kernel command line.
// If sysctlFile is not empty, contents of the file are appended to the image /etc/sysctl.conf.
// Output is stored in outputDir and includes:
// - image: the image
// - key: ssh key for the image if applicable
// - kernel.config: actual kernel config used during build
// - obj/: directory with kernel object files (e.g. vmlinux for linux)
func Image(targetOS, targetArch, vmType, kernelDir, outputDir, compiler, userspaceDir,
cmdlineFile, sysctlFile string, config []byte) error {
builder, err := getBuilder(targetOS, targetArch, vmType)
if err != nil {
return err
}
return builder.build(targetArch, vmType, kernelDir, outputDir, compiler, userspaceDir, cmdlineFile, sysctlFile, config)
}
func Clean(targetOS, targetArch, vmType, kernelDir string) error {
builder, err := getBuilder(targetOS, targetArch, vmType)
if err != nil {
return err
}
return builder.clean(kernelDir)
}
type KernelBuildError struct {
*osutil.VerboseError
}
type builder interface {
build(targetArch, vmType, kernelDir, outputDir, compiler, userspaceDir,
cmdlineFile, sysctlFile string, config []byte) error
clean(kernelDir string) error
}
func getBuilder(targetOS, targetArch, vmType string) (builder, error) {
switch {
case targetOS == "linux" && targetArch == "amd64" && vmType == "gvisor":
return gvisor{}, nil
case targetOS == "linux" && targetArch == "amd64" && (vmType == "qemu" || vmType == "gce"):
return linux{}, nil
default:
return nil, fmt.Errorf("unsupported image type %v/%v/%v", targetOS, targetArch, vmType)
}
}
func CompilerIdentity(compiler string) (string, error) {
arg := "--version"
if strings.HasSuffix(compiler, "bazel") {

15
pkg/build/gvisor.go Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2018 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package build
type gvisor struct{}
func (gvisor) build(targetArch, vmType, kernelDir, outputDir, compiler, userspaceDir,
cmdlineFile, sysctlFile string, config []byte) error {
return nil
}
func (gvisor) clean(kernelDir string) error {
return nil
}

View File

@ -22,8 +22,24 @@ import (
"github.com/google/syzkaller/pkg/osutil"
)
func Build(dir, compiler string, config []byte) error {
configFile := filepath.Join(dir, ".config")
type linux struct{}
func (linux linux) build(targetArch, vmType, kernelDir, outputDir, compiler, userspaceDir,
cmdlineFile, sysctlFile string, config []byte) error {
if err := osutil.MkdirAll(filepath.Join(outputDir, "obj")); err != nil {
return err
}
if err := linux.buildKernel(kernelDir, outputDir, compiler, config); err != nil {
return err
}
if err := linux.createImage(vmType, kernelDir, outputDir, userspaceDir, cmdlineFile, sysctlFile); err != nil {
return err
}
return nil
}
func (linux) buildKernel(kernelDir, outputDir, compiler string, config []byte) error {
configFile := filepath.Join(kernelDir, ".config")
if err := osutil.WriteFile(configFile, config); err != nil {
return fmt.Errorf("failed to write config file: %v", err)
}
@ -37,7 +53,7 @@ func Build(dir, compiler string, config []byte) error {
if err := osutil.Sandbox(cmd, true, true); err != nil {
return err
}
cmd.Dir = dir
cmd.Dir = kernelDir
if _, err := osutil.Run(10*time.Minute, cmd); err != nil {
return err
}
@ -47,38 +63,23 @@ func Build(dir, compiler string, config []byte) error {
if err := osutil.Sandbox(cmd, true, true); err != nil {
return err
}
cmd.Dir = dir
// Build of a large kernel can take a while on a 1 CPU VM.
if _, err := osutil.Run(3*time.Hour, cmd); err != nil {
cmd.Dir = kernelDir
if _, err := osutil.Run(time.Hour, cmd); err != nil {
return extractRootCause(err)
}
outputConfig := filepath.Join(outputDir, "kernel.config")
if err := osutil.CopyFile(configFile, outputConfig); err != nil {
return err
}
vmlinux := filepath.Join(kernelDir, "vmlinux")
outputVmlinux := filepath.Join(outputDir, "obj", "vmlinux")
if err := os.Rename(vmlinux, outputVmlinux); err != nil {
return fmt.Errorf("failed to rename vmlinux: %v", err)
}
return nil
}
func Clean(dir string) error {
cpu := strconv.Itoa(runtime.NumCPU())
cmd := osutil.Command("make", "distclean", "-j", cpu)
if err := osutil.Sandbox(cmd, true, true); err != nil {
return err
}
cmd.Dir = dir
_, err := osutil.Run(10*time.Minute, cmd)
return err
}
// CreateImage creates a disk image that is suitable for syzkaller.
// Kernel is taken from kernelDir, userspace system is taken from userspaceDir.
// If cmdlineFile is not empty, contents of the file are appended to the kernel command line.
// If sysctlFile is not empty, contents of the file are appended to the image /etc/sysctl.conf.
// Produces image and root ssh key in the specified files.
func CreateImage(targetOS, targetArch, vmType, kernelDir, userspaceDir, cmdlineFile, sysctlFile,
image, sshkey string) error {
if targetOS != "linux" || targetArch != "amd64" {
return fmt.Errorf("only linux/amd64 is supported")
}
if vmType != "qemu" && vmType != "gce" {
return fmt.Errorf("images can be built only for qemu/gce machines")
}
func (linux) createImage(vmType, kernelDir, outputDir, userspaceDir, cmdlineFile, sysctlFile string) error {
tempDir, err := ioutil.TempDir("", "syz-build")
if err != nil {
return err
@ -101,18 +102,31 @@ func CreateImage(targetOS, targetArch, vmType, kernelDir, userspaceDir, cmdlineF
return fmt.Errorf("image build failed: %v", err)
}
// Note: we use CopyFile instead of Rename because src and dst can be on different filesystems.
if err := osutil.CopyFile(filepath.Join(tempDir, "disk.raw"), image); err != nil {
imageFile := filepath.Join(outputDir, "image")
if err := osutil.CopyFile(filepath.Join(tempDir, "disk.raw"), imageFile); err != nil {
return err
}
if err := osutil.CopyFile(filepath.Join(tempDir, "key"), sshkey); err != nil {
keyFile := filepath.Join(outputDir, "key")
if err := osutil.CopyFile(filepath.Join(tempDir, "key"), keyFile); err != nil {
return err
}
if err := os.Chmod(sshkey, 0600); err != nil {
if err := os.Chmod(keyFile, 0600); err != nil {
return err
}
return nil
}
func (linux) clean(kernelDir string) error {
cpu := strconv.Itoa(runtime.NumCPU())
cmd := osutil.Command("make", "distclean", "-j", cpu)
if err := osutil.Sandbox(cmd, true, true); err != nil {
return err
}
cmd.Dir = kernelDir
_, err := osutil.Run(10*time.Minute, cmd)
return err
}
func extractRootCause(err error) error {
verr, ok := err.(*osutil.VerboseError)
if !ok {
@ -133,7 +147,7 @@ func extractRootCause(err error) error {
if cause != nil {
verr.Title = string(cause)
}
return verr
return KernelBuildError{verr}
}
type buildFailureCause struct {

View File

@ -79,16 +79,15 @@ func (env *Env) BuildSyzkaller(repo, commit string) error {
func (env *Env) BuildKernel(compilerBin, userspaceDir, cmdlineFile, sysctlFile string, kernelConfig []byte) error {
cfg := env.cfg
if err := build.Build(cfg.KernelSrc, compilerBin, kernelConfig); err != nil {
return osutil.PrependContext("kernel build failed", err)
}
cfg.KernelObj = cfg.KernelSrc
cfg.Image = filepath.Join(cfg.Workdir, "syz-image")
cfg.SSHKey = filepath.Join(cfg.Workdir, "syz-key")
if err := build.CreateImage(cfg.TargetOS, cfg.TargetVMArch, cfg.Type,
cfg.KernelSrc, userspaceDir, cmdlineFile, sysctlFile, cfg.Image, cfg.SSHKey); err != nil {
return osutil.PrependContext("image build failed", err)
imageDir := filepath.Join(cfg.Workdir, "image")
if err := build.Image(cfg.TargetOS, cfg.TargetVMArch, cfg.Type,
cfg.KernelSrc, imageDir, compilerBin, userspaceDir,
cmdlineFile, sysctlFile, kernelConfig); err != nil {
return err
}
cfg.KernelObj = filepath.Join(imageDir, "obj")
cfg.Image = filepath.Join(imageDir, "image")
cfg.SSHKey = filepath.Join(imageDir, "key")
return nil
}

View File

@ -204,7 +204,7 @@ func (jp *JobProcessor) test(job *Job) error {
resp.Build.KernelCommitTitle = kernelCommit.Title
resp.Build.KernelCommitDate = kernelCommit.Date
if err := build.Clean(kernelDir); err != nil {
if err := build.Clean(mgrcfg.TargetOS, mgrcfg.TargetArch, mgrcfg.Type, kernelDir); err != nil {
return fmt.Errorf("kernel clean failed: %v", err)
}
if len(req.Patch) != 0 {

View File

@ -270,36 +270,20 @@ func (mgr *Manager) build() error {
if err := config.SaveFile(filepath.Join(tmpDir, "tag"), info); err != nil {
return fmt.Errorf("failed to write tag file: %v", err)
}
if err := build.Build(mgr.kernelDir, mgr.mgrcfg.Compiler, kernelConfigData); err != nil {
rep := &report.Report{
Title: fmt.Sprintf("%v build error", mgr.mgrcfg.RepoAlias),
Output: []byte(err.Error()),
}
if err := mgr.reportBuildError(rep, info, tmpDir); err != nil {
mgr.Errorf("failed to report image error: %v", err)
if err := build.Image(mgr.managercfg.TargetOS, mgr.managercfg.TargetVMArch, mgr.managercfg.Type,
mgr.kernelDir, tmpDir, mgr.mgrcfg.Compiler, mgr.mgrcfg.Userspace,
mgr.mgrcfg.KernelCmdline, mgr.mgrcfg.KernelSysctl, kernelConfigData); err != nil {
if _, ok := err.(build.KernelBuildError); ok {
rep := &report.Report{
Title: fmt.Sprintf("%v build error", mgr.mgrcfg.RepoAlias),
Output: []byte(err.Error()),
}
if err := mgr.reportBuildError(rep, info, tmpDir); err != nil {
mgr.Errorf("failed to report image error: %v", err)
}
}
return fmt.Errorf("kernel build failed: %v", err)
}
kernelConfig := filepath.Join(tmpDir, "kernel.config")
if err := osutil.CopyFile(filepath.Join(mgr.kernelDir, ".config"), kernelConfig); err != nil {
return err
}
image := filepath.Join(tmpDir, "image")
key := filepath.Join(tmpDir, "key")
err = build.CreateImage(mgr.managercfg.TargetOS, mgr.managercfg.TargetVMArch, mgr.managercfg.Type,
mgr.kernelDir, mgr.mgrcfg.Userspace, mgr.mgrcfg.KernelCmdline, mgr.mgrcfg.KernelSysctl, image, key)
if err != nil {
return fmt.Errorf("image build failed: %v", err)
}
vmlinux := filepath.Join(mgr.kernelDir, "vmlinux")
objDir := filepath.Join(tmpDir, "obj")
osutil.MkdirAll(objDir)
if err := os.Rename(vmlinux, filepath.Join(objDir, "vmlinux")); err != nil {
return fmt.Errorf("failed to rename vmlinux file: %v", err)
}
if err := mgr.testImage(tmpDir, info); err != nil {
return err