github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/instance/instance.go (about)

     1  // Copyright 2018 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  // Package instance provides helper functions for creation of temporal instances
     5  // used for testing of images, patches and bisection.
     6  package instance
     7  
     8  import (
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"net"
    13  	"os"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/google/syzkaller/pkg/build"
    20  	"github.com/google/syzkaller/pkg/csource"
    21  	"github.com/google/syzkaller/pkg/log"
    22  	"github.com/google/syzkaller/pkg/mgrconfig"
    23  	"github.com/google/syzkaller/pkg/osutil"
    24  	"github.com/google/syzkaller/pkg/report"
    25  	"github.com/google/syzkaller/pkg/report/crash"
    26  	"github.com/google/syzkaller/pkg/tool"
    27  	"github.com/google/syzkaller/pkg/vcs"
    28  	"github.com/google/syzkaller/sys/targets"
    29  	"github.com/google/syzkaller/vm"
    30  )
    31  
    32  type Env interface {
    33  	BuildSyzkaller(string, string) (string, error)
    34  	BuildKernel(*BuildKernelConfig) (string, build.ImageDetails, error)
    35  	Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]EnvTestResult, error)
    36  }
    37  
    38  type env struct {
    39  	cfg           *mgrconfig.Config
    40  	optionalFlags bool
    41  	buildSem      *Semaphore
    42  	testSem       *Semaphore
    43  }
    44  
    45  type BuildKernelConfig struct {
    46  	CompilerBin  string
    47  	LinkerBin    string
    48  	CcacheBin    string
    49  	UserspaceDir string
    50  	CmdlineFile  string
    51  	SysctlFile   string
    52  	KernelConfig []byte
    53  }
    54  
    55  func NewEnv(cfg *mgrconfig.Config, buildSem, testSem *Semaphore) (Env, error) {
    56  	if !vm.AllowsOvercommit(cfg.Type) {
    57  		return nil, fmt.Errorf("test instances are not supported for %v VMs", cfg.Type)
    58  	}
    59  	if cfg.Workdir == "" {
    60  		return nil, fmt.Errorf("workdir path is empty")
    61  	}
    62  	if cfg.KernelSrc == "" {
    63  		return nil, fmt.Errorf("kernel src path is empty")
    64  	}
    65  	if cfg.Syzkaller == "" {
    66  		return nil, fmt.Errorf("syzkaller path is empty")
    67  	}
    68  	if err := osutil.MkdirAll(cfg.Workdir); err != nil {
    69  		return nil, fmt.Errorf("failed to create tmp dir: %w", err)
    70  	}
    71  	env := &env{
    72  		cfg:           cfg,
    73  		optionalFlags: true,
    74  		buildSem:      buildSem,
    75  		testSem:       testSem,
    76  	}
    77  	return env, nil
    78  }
    79  
    80  func (env *env) BuildSyzkaller(repoURL, commit string) (string, error) {
    81  	if env.buildSem != nil {
    82  		env.buildSem.Wait()
    83  		defer env.buildSem.Signal()
    84  	}
    85  	cfg := env.cfg
    86  	srcIndex := strings.LastIndex(cfg.Syzkaller, "/src/")
    87  	if srcIndex == -1 {
    88  		return "", fmt.Errorf("syzkaller path %q is not in GOPATH", cfg.Syzkaller)
    89  	}
    90  	repo := vcs.NewSyzkallerRepo(cfg.Syzkaller)
    91  	if _, err := repo.CheckoutCommit(repoURL, commit); err != nil {
    92  		return "", fmt.Errorf("failed to checkout syzkaller repo: %w", err)
    93  	}
    94  	// The following commit ("syz-fuzzer: support optional flags") adds support for optional flags
    95  	// in syz-fuzzer and syz-execprog. This is required to invoke older binaries with newer flags
    96  	// without failing due to unknown flags.
    97  	optionalFlags, err := repo.Contains("64435345f0891706a7e0c7885f5f7487581e6005")
    98  	if err != nil {
    99  		return "", fmt.Errorf("optional flags check failed: %w", err)
   100  	}
   101  	env.optionalFlags = optionalFlags
   102  	cmd := osutil.Command(MakeBin, "target")
   103  	cmd.Dir = cfg.Syzkaller
   104  	goEnvOptions := []string{
   105  		"GOPATH=" + cfg.Syzkaller[:srcIndex],
   106  		"GO111MODULE=auto",
   107  	}
   108  	cmd.Env = append(append([]string{}, os.Environ()...), goEnvOptions...)
   109  	cmd.Env = append(cmd.Env,
   110  		"TARGETOS="+cfg.TargetOS,
   111  		"TARGETVMARCH="+cfg.TargetVMArch,
   112  		"TARGETARCH="+cfg.TargetArch,
   113  		// Since we can be building very old revisions for bisection here,
   114  		// make the build as permissive as possible.
   115  		// Newer compilers tend to produce more warnings also kernel headers may be broken, e.g.:
   116  		// ebtables.h:197:19: error: invalid conversion from ‘void*’ to ‘ebt_entry_target*’
   117  		"CFLAGS=-fpermissive -w",
   118  	)
   119  
   120  	// We collect the potentially useful debug info here unconditionally, because we will
   121  	// only figure out later whether we actually need it (e.g. if the patch testing fails).
   122  	goEnvCmd := osutil.Command("go", "env")
   123  	goEnvCmd.Dir = cfg.Syzkaller
   124  	goEnvCmd.Env = append(append([]string{}, os.Environ()...), goEnvOptions...)
   125  	goEnvOut, goEnvErr := osutil.Run(time.Hour, goEnvCmd)
   126  	gitStatusOut, gitStatusErr := osutil.RunCmd(time.Hour, cfg.Syzkaller, "git", "status")
   127  	// Compile syzkaller.
   128  	buildOutput, buildErr := osutil.Run(time.Hour, cmd)
   129  	buildLog := fmt.Sprintf("go env (err=%v)\n%s\ngit status (err=%v)\n%s\n\n%s",
   130  		goEnvErr, goEnvOut, gitStatusErr, gitStatusOut, buildOutput)
   131  	if buildErr != nil {
   132  		return buildLog, fmt.Errorf("syzkaller build failed: %w\n%s", buildErr, buildLog)
   133  	}
   134  	return buildLog, nil
   135  }
   136  
   137  func (env *env) BuildKernel(buildCfg *BuildKernelConfig) (
   138  	string, build.ImageDetails, error) {
   139  	if env.buildSem != nil {
   140  		env.buildSem.Wait()
   141  		defer env.buildSem.Signal()
   142  	}
   143  	imageDir := filepath.Join(env.cfg.Workdir, "image")
   144  	params := build.Params{
   145  		TargetOS:     env.cfg.TargetOS,
   146  		TargetArch:   env.cfg.TargetVMArch,
   147  		VMType:       env.cfg.Type,
   148  		KernelDir:    env.cfg.KernelSrc,
   149  		OutputDir:    imageDir,
   150  		Compiler:     buildCfg.CompilerBin,
   151  		Linker:       buildCfg.LinkerBin,
   152  		Ccache:       buildCfg.CcacheBin,
   153  		UserspaceDir: buildCfg.UserspaceDir,
   154  		CmdlineFile:  buildCfg.CmdlineFile,
   155  		SysctlFile:   buildCfg.SysctlFile,
   156  		Config:       buildCfg.KernelConfig,
   157  	}
   158  	details, err := build.Image(params)
   159  	if err != nil {
   160  		return "", details, err
   161  	}
   162  	if err := SetConfigImage(env.cfg, imageDir, true); err != nil {
   163  		return "", details, err
   164  	}
   165  	kernelConfigFile := filepath.Join(imageDir, "kernel.config")
   166  	if !osutil.IsExist(kernelConfigFile) {
   167  		kernelConfigFile = ""
   168  	}
   169  	return kernelConfigFile, details, nil
   170  }
   171  
   172  func SetConfigImage(cfg *mgrconfig.Config, imageDir string, reliable bool) error {
   173  	cfg.KernelObj = filepath.Join(imageDir, "obj")
   174  	cfg.Image = filepath.Join(imageDir, "image")
   175  	if keyFile := filepath.Join(imageDir, "key"); osutil.IsExist(keyFile) {
   176  		cfg.SSHKey = keyFile
   177  	}
   178  	vmConfig := make(map[string]interface{})
   179  	if err := json.Unmarshal(cfg.VM, &vmConfig); err != nil {
   180  		return fmt.Errorf("failed to parse VM config: %w", err)
   181  	}
   182  	if cfg.Type == "qemu" || cfg.Type == "vmm" {
   183  		if kernel := filepath.Join(imageDir, "kernel"); osutil.IsExist(kernel) {
   184  			vmConfig["kernel"] = kernel
   185  		}
   186  		if initrd := filepath.Join(imageDir, "initrd"); osutil.IsExist(initrd) {
   187  			vmConfig["initrd"] = initrd
   188  		}
   189  	}
   190  	if cfg.Type == "gce" {
   191  		// Don't use preemptible VMs for image testing, patch testing and bisection.
   192  		vmConfig["preemptible"] = !reliable
   193  	}
   194  	vmCfg, err := json.Marshal(vmConfig)
   195  	if err != nil {
   196  		return fmt.Errorf("failed to serialize VM config: %w", err)
   197  	}
   198  	cfg.VM = vmCfg
   199  	return nil
   200  }
   201  
   202  func OverrideVMCount(cfg *mgrconfig.Config, n int) error {
   203  	vmConfig := make(map[string]interface{})
   204  	if err := json.Unmarshal(cfg.VM, &vmConfig); err != nil {
   205  		return fmt.Errorf("failed to parse VM config: %w", err)
   206  	}
   207  	if vmConfig["count"] == nil || !vm.AllowsOvercommit(cfg.Type) {
   208  		return nil
   209  	}
   210  	vmConfig["count"] = n
   211  	vmCfg, err := json.Marshal(vmConfig)
   212  	if err != nil {
   213  		return fmt.Errorf("failed to serialize VM config: %w", err)
   214  	}
   215  	cfg.VM = vmCfg
   216  	return nil
   217  }
   218  
   219  type TestError struct {
   220  	Boot   bool // says if the error happened during booting or during instance testing
   221  	Infra  bool // whether the problem is related to some infrastructure problems
   222  	Title  string
   223  	Output []byte
   224  	Report *report.Report
   225  }
   226  
   227  func (err *TestError) Error() string {
   228  	return err.Title
   229  }
   230  
   231  type CrashError struct {
   232  	Report *report.Report
   233  }
   234  
   235  func (err *CrashError) Error() string {
   236  	return err.Report.Title
   237  }
   238  
   239  // Test boots numVMs VMs, tests basic kernel operation, and optionally tests the provided reproducer.
   240  // TestError is returned if there is a problem with kernel/image (crash, reboot loop, etc).
   241  // CrashError is returned if the reproducer crashes kernel.
   242  func (env *env) Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]EnvTestResult, error) {
   243  	if env.testSem != nil {
   244  		env.testSem.Wait()
   245  		defer env.testSem.Signal()
   246  	}
   247  	if err := mgrconfig.Complete(env.cfg); err != nil {
   248  		return nil, err
   249  	}
   250  	reporter, err := report.NewReporter(env.cfg)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	vmPool, err := vm.Create(env.cfg, false)
   255  	if err != nil {
   256  		return nil, fmt.Errorf("failed to create VM pool: %w", err)
   257  	}
   258  	if n := vmPool.Count(); numVMs > n {
   259  		numVMs = n
   260  	}
   261  	res := make(chan EnvTestResult, numVMs)
   262  	for i := 0; i < numVMs; i++ {
   263  		inst := &inst{
   264  			cfg:           env.cfg,
   265  			optionalFlags: env.optionalFlags,
   266  			reporter:      reporter,
   267  			vmPool:        vmPool,
   268  			vmIndex:       i,
   269  			reproSyz:      reproSyz,
   270  			reproOpts:     reproOpts,
   271  			reproC:        reproC,
   272  		}
   273  		go func() { res <- inst.test() }()
   274  	}
   275  	var ret []EnvTestResult
   276  	for i := 0; i < numVMs; i++ {
   277  		ret = append(ret, <-res)
   278  	}
   279  	return ret, nil
   280  }
   281  
   282  type inst struct {
   283  	cfg           *mgrconfig.Config
   284  	optionalFlags bool
   285  	reporter      *report.Reporter
   286  	vmPool        *vm.Pool
   287  	vm            *vm.Instance
   288  	vmIndex       int
   289  	reproSyz      []byte
   290  	reproOpts     []byte
   291  	reproC        []byte
   292  }
   293  
   294  type EnvTestResult struct {
   295  	Error     error
   296  	RawOutput []byte
   297  }
   298  
   299  func (inst *inst) test() EnvTestResult {
   300  	vmInst, err := inst.vmPool.Create(inst.vmIndex)
   301  	if err != nil {
   302  		testErr := &TestError{
   303  			Boot:  true,
   304  			Title: err.Error(),
   305  		}
   306  		ret := EnvTestResult{
   307  			Error: testErr,
   308  		}
   309  		var bootErr vm.BootErrorer
   310  		if errors.As(err, &bootErr) {
   311  			testErr.Title, testErr.Output = bootErr.BootError()
   312  			ret.RawOutput = testErr.Output
   313  			rep := inst.reporter.Parse(testErr.Output)
   314  			if rep != nil && rep.Type == crash.UnexpectedReboot {
   315  				// Avoid detecting any boot crash as "unexpected kernel reboot".
   316  				rep = inst.reporter.ParseFrom(testErr.Output, rep.SkipPos)
   317  			}
   318  			if rep == nil {
   319  				rep = &report.Report{
   320  					Title:  testErr.Title,
   321  					Output: testErr.Output,
   322  				}
   323  			}
   324  			if err := inst.reporter.Symbolize(rep); err != nil {
   325  				// TODO(dvyukov): send such errors to dashboard.
   326  				log.Logf(0, "failed to symbolize report: %v", err)
   327  			}
   328  			testErr.Report = rep
   329  			testErr.Title = rep.Title
   330  		} else {
   331  			testErr.Infra = true
   332  			var infraErr vm.InfraErrorer
   333  			if errors.As(err, &infraErr) {
   334  				// In case there's more info available.
   335  				testErr.Title, testErr.Output = infraErr.InfraError()
   336  			}
   337  		}
   338  		return ret
   339  	}
   340  	defer vmInst.Close()
   341  	inst.vm = vmInst
   342  	ret := EnvTestResult{}
   343  	if ret.Error = inst.testInstance(); ret.Error != nil {
   344  		return ret
   345  	}
   346  	if len(inst.reproSyz) != 0 || len(inst.reproC) != 0 {
   347  		ret.RawOutput, ret.Error = inst.testRepro()
   348  	}
   349  	return ret
   350  }
   351  
   352  // testInstance tests basic operation of the provided VM
   353  // (that we can copy binaries, run binaries, they can connect to host, run syzkaller programs, etc).
   354  // TestError is returned if there is a problem with the kernel (e.g. crash).
   355  func (inst *inst) testInstance() error {
   356  	ln, err := net.Listen("tcp", ":")
   357  	if err != nil {
   358  		return fmt.Errorf("failed to open listening socket: %w", err)
   359  	}
   360  	defer ln.Close()
   361  	acceptErr := make(chan error, 1)
   362  	go func() {
   363  		conn, err := ln.Accept()
   364  		if err == nil {
   365  			conn.Close()
   366  		}
   367  		acceptErr <- err
   368  	}()
   369  	fwdAddr, err := inst.vm.Forward(ln.Addr().(*net.TCPAddr).Port)
   370  	if err != nil {
   371  		return fmt.Errorf("failed to setup port forwarding: %w", err)
   372  	}
   373  
   374  	fuzzerBin, err := inst.vm.Copy(inst.cfg.FuzzerBin)
   375  	if err != nil {
   376  		return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)}
   377  	}
   378  
   379  	// If ExecutorBin is provided, it means that syz-executor is already in the image,
   380  	// so no need to copy it.
   381  	executorBin := inst.cfg.SysTarget.ExecutorBin
   382  	if executorBin == "" {
   383  		executorBin, err = inst.vm.Copy(inst.cfg.ExecutorBin)
   384  		if err != nil {
   385  			return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)}
   386  		}
   387  	}
   388  
   389  	cmd := OldFuzzerCmd(fuzzerBin, executorBin, targets.TestOS, inst.cfg.TargetOS, inst.cfg.TargetArch, fwdAddr,
   390  		inst.cfg.Sandbox, inst.cfg.SandboxArg, 0, inst.cfg.Cover, true, inst.optionalFlags, inst.cfg.Timeouts.Slowdown)
   391  	timeout := 10 * time.Minute * inst.cfg.Timeouts.Scale
   392  	_, rep, err := inst.vm.Run(timeout, inst.reporter, cmd)
   393  	if err != nil {
   394  		return fmt.Errorf("failed to run binary in VM: %w", err)
   395  	}
   396  	if rep != nil {
   397  		if err := inst.reporter.Symbolize(rep); err != nil {
   398  			// TODO(dvyukov): send such errors to dashboard.
   399  			log.Logf(0, "failed to symbolize report: %v", err)
   400  		}
   401  		return &TestError{
   402  			Title:  rep.Title,
   403  			Report: rep,
   404  		}
   405  	}
   406  	select {
   407  	case err := <-acceptErr:
   408  		return err
   409  	case <-time.After(10 * time.Second):
   410  		return fmt.Errorf("test machine failed to connect to host")
   411  	}
   412  }
   413  
   414  func (inst *inst) testRepro() ([]byte, error) {
   415  	var err error
   416  	execProg, err := SetupExecProg(inst.vm, inst.cfg, inst.reporter, &OptionalConfig{
   417  		OldFlagsCompatMode: !inst.optionalFlags,
   418  	})
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  	transformError := func(res *RunResult, err error) ([]byte, error) {
   423  		if err != nil {
   424  			return nil, err
   425  		}
   426  		if res != nil && res.Report != nil {
   427  			return res.Output, &CrashError{Report: res.Report}
   428  		}
   429  		return res.Output, nil
   430  	}
   431  	out := []byte{}
   432  	if len(inst.reproSyz) > 0 {
   433  		var opts csource.Options
   434  		opts, err = csource.DeserializeOptions(inst.reproOpts)
   435  		if err != nil {
   436  			return nil, err
   437  		}
   438  		// Combine repro options and default options in a way that increases chances to reproduce the crash.
   439  		// First, we always enable threaded/collide as it should be [almost] strictly better.
   440  		// Executor does not support empty sandbox, so we use none instead.
   441  		// Finally, always use repeat and multiple procs.
   442  		if opts.Sandbox == "" {
   443  			opts.Sandbox = "none"
   444  		}
   445  		opts.Repeat, opts.Threaded = true, true
   446  		out, err = transformError(execProg.RunSyzProg(inst.reproSyz,
   447  			inst.cfg.Timeouts.NoOutputRunningTime, opts))
   448  	}
   449  	if err == nil && len(inst.reproC) > 0 {
   450  		// We should test for more than full "no output" timeout, but the problem is that C reproducers
   451  		// don't print anything, so we will get a false "no output" crash.
   452  		out, err = transformError(execProg.RunCProgRaw(inst.reproC, inst.cfg.Target,
   453  			inst.cfg.Timeouts.NoOutput/2))
   454  	}
   455  	return out, err
   456  }
   457  
   458  type OptionalFuzzerArgs struct {
   459  	Slowdown   int
   460  	SandboxArg int
   461  	PprofPort  int
   462  }
   463  
   464  type FuzzerCmdArgs struct {
   465  	Fuzzer    string
   466  	Executor  string
   467  	Name      string
   468  	OS        string
   469  	Arch      string
   470  	FwdAddr   string
   471  	Sandbox   string
   472  	Procs     int
   473  	Verbosity int
   474  	Cover     bool
   475  	Debug     bool
   476  	Test      bool
   477  	Optional  *OptionalFuzzerArgs
   478  }
   479  
   480  func FuzzerCmd(args *FuzzerCmdArgs) string {
   481  	osArg := ""
   482  	if targets.Get(args.OS, args.Arch).HostFuzzer {
   483  		// Only these OSes need the flag, because the rest assume host OS.
   484  		// But speciying OS for all OSes breaks patch testing on syzbot
   485  		// because old execprog does not have os flag.
   486  		osArg = " -os=" + args.OS
   487  	}
   488  	verbosityArg := ""
   489  	if args.Verbosity != 0 {
   490  		verbosityArg = fmt.Sprintf(" -vv=%v", args.Verbosity)
   491  	}
   492  	optionalArg := ""
   493  	if args.Optional != nil {
   494  		flags := []tool.Flag{
   495  			{Name: "slowdown", Value: fmt.Sprint(args.Optional.Slowdown)},
   496  			{Name: "sandbox_arg", Value: fmt.Sprint(args.Optional.SandboxArg)},
   497  			{Name: "pprof_port", Value: fmt.Sprint(args.Optional.PprofPort)},
   498  		}
   499  		optionalArg = " " + tool.OptionalFlags(flags)
   500  	}
   501  	return fmt.Sprintf("%v -executor=%v -name=%v -arch=%v%v -manager=%v -sandbox=%v"+
   502  		" -procs=%v -cover=%v -debug=%v -test=%v%v%v",
   503  		args.Fuzzer, args.Executor, args.Name, args.Arch, osArg, args.FwdAddr, args.Sandbox,
   504  		args.Procs, args.Cover, args.Debug, args.Test, verbosityArg, optionalArg)
   505  }
   506  
   507  func OldFuzzerCmd(fuzzer, executor, name, OS, arch, fwdAddr, sandbox string, sandboxArg, procs int,
   508  	cover, test, optionalFlags bool, slowdown int) string {
   509  	var optional *OptionalFuzzerArgs
   510  	if optionalFlags {
   511  		optional = &OptionalFuzzerArgs{Slowdown: slowdown, SandboxArg: sandboxArg}
   512  	}
   513  	return FuzzerCmd(&FuzzerCmdArgs{Fuzzer: fuzzer, Executor: executor, Name: name,
   514  		OS: OS, Arch: arch, FwdAddr: fwdAddr, Sandbox: sandbox,
   515  		Procs: procs, Verbosity: 0, Cover: cover, Debug: false, Test: test,
   516  		Optional: optional})
   517  }
   518  
   519  func ExecprogCmd(execprog, executor, OS, arch, sandbox string, sandboxArg int, repeat, threaded, collide bool,
   520  	procs, faultCall, faultNth int, optionalFlags bool, slowdown int, progFile string) string {
   521  	repeatCount := 1
   522  	if repeat {
   523  		repeatCount = 0
   524  	}
   525  	osArg := ""
   526  	if targets.Get(OS, arch).HostFuzzer {
   527  		osArg = " -os=" + OS
   528  	}
   529  	optionalArg := ""
   530  
   531  	if faultCall >= 0 {
   532  		optionalArg = fmt.Sprintf(" -fault_call=%v -fault_nth=%v",
   533  			faultCall, faultNth)
   534  	}
   535  
   536  	if optionalFlags {
   537  		optionalArg += " " + tool.OptionalFlags([]tool.Flag{
   538  			{Name: "slowdown", Value: fmt.Sprint(slowdown)},
   539  			{Name: "sandboxArg", Value: fmt.Sprint(sandboxArg)},
   540  		})
   541  	}
   542  
   543  	return fmt.Sprintf("%v -executor=%v -arch=%v%v -sandbox=%v"+
   544  		" -procs=%v -repeat=%v -threaded=%v -collide=%v -cover=0%v %v",
   545  		execprog, executor, arch, osArg, sandbox,
   546  		procs, repeatCount, threaded, collide,
   547  		optionalArg, progFile)
   548  }
   549  
   550  var MakeBin = func() string {
   551  	if runtime.GOOS == targets.FreeBSD || runtime.GOOS == targets.OpenBSD {
   552  		return "gmake"
   553  	}
   554  	return "make"
   555  }()
   556  
   557  func RunnerCmd(prog, fwdAddr, os, arch string, poolIdx, vmIdx int, threaded, newEnv bool) string {
   558  	return fmt.Sprintf("%s -addr=%s -os=%s -arch=%s -pool=%d -vm=%d "+
   559  		"-threaded=%t -new-env=%t", prog, fwdAddr, os, arch, poolIdx, vmIdx, threaded, newEnv)
   560  }
   561  
   562  type Semaphore struct {
   563  	ch chan struct{}
   564  }
   565  
   566  func NewSemaphore(count int) *Semaphore {
   567  	s := &Semaphore{
   568  		ch: make(chan struct{}, count),
   569  	}
   570  	for i := 0; i < count; i++ {
   571  		s.Signal()
   572  	}
   573  	return s
   574  }
   575  
   576  func (s *Semaphore) Wait() {
   577  	<-s.ch
   578  }
   579  
   580  func (s *Semaphore) WaitC() <-chan struct{} {
   581  	return s.ch
   582  }
   583  
   584  func (s *Semaphore) Available() int {
   585  	return len(s.ch)
   586  }
   587  
   588  func (s *Semaphore) Signal() {
   589  	if av := s.Available(); av == cap(s.ch) {
   590  		// Not super reliable, but let it be here just in case.
   591  		panic(fmt.Sprintf("semaphore capacity (%d) is exceeded (%d)", cap(s.ch), av))
   592  	}
   593  	s.ch <- struct{}{}
   594  }