diff options
Diffstat (limited to 'pkg/ipc/ipc.go')
| -rw-r--r-- | pkg/ipc/ipc.go | 838 |
1 files changed, 0 insertions, 838 deletions
diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go deleted file mode 100644 index c09137e3b..000000000 --- a/pkg/ipc/ipc.go +++ /dev/null @@ -1,838 +0,0 @@ -// Copyright 2015 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 ipc - -import ( - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "slices" - "strings" - "sync" - "time" - "unsafe" - - "github.com/google/syzkaller/pkg/cover" - "github.com/google/syzkaller/pkg/csource" - "github.com/google/syzkaller/pkg/flatrpc" - "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/pkg/signal" - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys/targets" -) - -// Config is the configuration for Env. -type Config struct { - // Path to executor binary. - Executor string - - UseForkServer bool // use extended protocol with handshake - RateLimit bool // rate limit start of new processes for host fuzzer mode - - Timeouts targets.Timeouts - - CoverFilter []uint64 -} - -type Env struct { - in []byte - out []byte - - cmd *command - inFile *os.File - outFile *os.File - bin []string - linkedBin string - pid int - config *Config -} - -const ( - outputSize = 16 << 20 - - statusFail = 67 - - // Comparison types masks taken from KCOV headers. - compSizeMask = 6 - compSize8 = 6 - compConstMask = 1 - - extraReplyIndex = 0xffffffff // uint32(-1) -) - -func SandboxToFlags(sandbox string) (flatrpc.ExecEnv, error) { - switch sandbox { - case "none": - return 0, nil - case "setuid": - return flatrpc.ExecEnvSandboxSetuid, nil - case "namespace": - return flatrpc.ExecEnvSandboxNamespace, nil - case "android": - return flatrpc.ExecEnvSandboxAndroid, nil - default: - return 0, fmt.Errorf("sandbox must contain one of none/setuid/namespace/android") - } -} - -func FlagsToSandbox(flags flatrpc.ExecEnv) string { - if flags&flatrpc.ExecEnvSandboxSetuid != 0 { - return "setuid" - } else if flags&flatrpc.ExecEnvSandboxNamespace != 0 { - return "namespace" - } else if flags&flatrpc.ExecEnvSandboxAndroid != 0 { - return "android" - } - return "none" -} - -func FeaturesToFlags(features flatrpc.Feature, manual csource.Features) flatrpc.ExecEnv { - for feat := range flatrpc.EnumNamesFeature { - opt := FlatRPCFeaturesToCSource[feat] - if opt != "" && manual != nil && !manual[opt].Enabled { - features &= ^feat - } - } - var flags flatrpc.ExecEnv - if manual == nil || manual["net_reset"].Enabled { - flags |= flatrpc.ExecEnvEnableNetReset - } - if manual == nil || manual["cgroups"].Enabled { - flags |= flatrpc.ExecEnvEnableCgroups - } - if manual == nil || manual["close_fds"].Enabled { - flags |= flatrpc.ExecEnvEnableCloseFds - } - if features&flatrpc.FeatureExtraCoverage != 0 { - flags |= flatrpc.ExecEnvExtraCover - } - if features&flatrpc.FeatureDelayKcovMmap != 0 { - flags |= flatrpc.ExecEnvDelayKcovMmap - } - if features&flatrpc.FeatureNetInjection != 0 { - flags |= flatrpc.ExecEnvEnableTun - } - if features&flatrpc.FeatureNetDevices != 0 { - flags |= flatrpc.ExecEnvEnableNetDev - } - if features&flatrpc.FeatureDevlinkPCI != 0 { - flags |= flatrpc.ExecEnvEnableDevlinkPCI - } - if features&flatrpc.FeatureNicVF != 0 { - flags |= flatrpc.ExecEnvEnableNicVF - } - if features&flatrpc.FeatureVhciInjection != 0 { - flags |= flatrpc.ExecEnvEnableVhciInjection - } - if features&flatrpc.FeatureWifiEmulation != 0 { - flags |= flatrpc.ExecEnvEnableWifi - } - return flags -} - -var FlatRPCFeaturesToCSource = map[flatrpc.Feature]string{ - flatrpc.FeatureNetInjection: "tun", - flatrpc.FeatureNetDevices: "net_dev", - flatrpc.FeatureDevlinkPCI: "devlink_pci", - flatrpc.FeatureNicVF: "nic_vf", - flatrpc.FeatureVhciInjection: "vhci", - flatrpc.FeatureWifiEmulation: "wifi", - flatrpc.FeatureUSBEmulation: "usb", - flatrpc.FeatureBinFmtMisc: "binfmt_misc", - flatrpc.FeatureLRWPANEmulation: "ieee802154", - flatrpc.FeatureSwap: "swap", -} - -func MakeEnv(config *Config, pid int) (*Env, error) { - if config.Timeouts.Slowdown == 0 || config.Timeouts.Scale == 0 || - config.Timeouts.Syscall == 0 || config.Timeouts.Program == 0 { - return nil, fmt.Errorf("ipc.MakeEnv: uninitialized timeouts (%+v)", config.Timeouts) - } - var inf, outf *os.File - var inmem, outmem []byte - var err error - inf, inmem, err = osutil.CreateMemMappedFile(prog.ExecBufferSize) - if err != nil { - return nil, err - } - defer func() { - if inf != nil { - osutil.CloseMemMappedFile(inf, inmem) - } - }() - outf, outmem, err = osutil.CreateMemMappedFile(outputSize) - if err != nil { - return nil, err - } - defer func() { - if outf != nil { - osutil.CloseMemMappedFile(outf, outmem) - } - }() - env := &Env{ - in: inmem, - out: outmem, - inFile: inf, - outFile: outf, - bin: append(strings.Split(config.Executor, " "), "exec"), - pid: pid, - config: config, - } - if len(env.bin) == 0 { - return nil, fmt.Errorf("binary is empty string") - } - env.bin[0] = osutil.Abs(env.bin[0]) // we are going to chdir - // Append pid to binary name. - // E.g. if binary is 'syz-executor' and pid=15, - // we create a link from 'syz-executor.15' to 'syz-executor' and use 'syz-executor.15' as binary. - // This allows to easily identify program that lead to a crash in the log. - // Log contains pid in "executing program 15" and crashes usually contain "Comm: syz-executor.15". - // Note: pkg/report knowns about this and converts "syz-executor.15" back to "syz-executor". - base := filepath.Base(env.bin[0]) - pidStr := fmt.Sprintf(".%v", pid) - const maxLen = 16 // TASK_COMM_LEN is currently set to 16 - if len(base)+len(pidStr) >= maxLen { - // Remove beginning of file name, in tests temp files have unique numbers at the end. - base = base[len(base)+len(pidStr)-maxLen+1:] - } - binCopy := filepath.Join(filepath.Dir(env.bin[0]), base+pidStr) - if err := os.Link(env.bin[0], binCopy); err == nil { - env.bin[0] = binCopy - env.linkedBin = binCopy - } - inf = nil - outf = nil - return env, nil -} - -func (env *Env) Close() error { - if env.cmd != nil { - env.cmd.close() - } - if env.linkedBin != "" { - os.Remove(env.linkedBin) - } - var err1, err2 error - if env.inFile != nil { - err1 = osutil.CloseMemMappedFile(env.inFile, env.in) - } - if env.outFile != nil { - err2 = osutil.CloseMemMappedFile(env.outFile, env.out) - } - switch { - case err1 != nil: - return err1 - case err2 != nil: - return err2 - default: - return nil - } -} - -// Exec starts executor binary to execute program stored in progData in exec encoding -// and returns information about the execution: -// output: process output -// info: per-call info -// hanged: program hanged and was killed -// err0: failed to start the process or bug in executor itself. -func (env *Env) ExecProg(opts *flatrpc.ExecOpts, progData []byte) ( - output []byte, info *flatrpc.ProgInfo, hanged bool, err0 error) { - ncalls, err := prog.ExecCallCount(progData) - if err != nil { - err0 = err - return - } - // Copy-in serialized program. - copy(env.in, progData) - // Zero out the first two words (ncmd and nsig), so that we don't have garbage there - // if executor crashes before writing non-garbage there. - for i := 0; i < 4; i++ { - env.out[i] = 0 - } - - err0 = env.RestartIfNeeded(opts) - if err0 != nil { - return - } - - start := osutil.MonotonicNano() - output, hanged, err0 = env.cmd.exec(opts) - elapsed := osutil.MonotonicNano() - start - if err0 != nil { - env.cmd.close() - env.cmd = nil - return - } - - info, err0 = env.parseOutput(opts, ncalls) - if info != nil { - info.Elapsed = uint64(elapsed) - info.Freshness = env.cmd.freshness - } - env.cmd.freshness++ - if !env.config.UseForkServer { - env.cmd.close() - env.cmd = nil - } - return -} - -func (env *Env) Exec(opts *flatrpc.ExecOpts, p *prog.Prog) ( - output []byte, info *flatrpc.ProgInfo, hanged bool, err0 error) { - progData, err := p.SerializeForExec() - if err != nil { - err0 = err - return - } - return env.ExecProg(opts, progData) -} - -func (env *Env) ForceRestart() { - if env.cmd != nil { - env.cmd.close() - env.cmd = nil - } -} - -// RestartIfNeeded brings up an executor process if it was stopped. -func (env *Env) RestartIfNeeded(opts *flatrpc.ExecOpts) error { - if env.cmd != nil { - if env.cmd.flags == opts.EnvFlags && env.cmd.sandboxArg == opts.SandboxArg { - return nil - } - env.ForceRestart() - } - if env.config.RateLimit { - rateLimiterOnce.Do(func() { - rateLimiter = time.NewTicker(1 * time.Second).C - }) - <-rateLimiter - } - var err error - env.cmd, err = env.makeCommand(opts, "./") - return err -} - -var ( - rateLimiterOnce sync.Once - rateLimiter <-chan time.Time -) - -func (env *Env) parseOutput(opts *flatrpc.ExecOpts, ncalls int) (*flatrpc.ProgInfo, error) { - out := env.out - ncmd, ok := readUint32(&out) - if !ok { - return nil, fmt.Errorf("failed to read number of calls") - } - info := flatrpc.EmptyProgInfo(ncalls) - extraParts := make([]flatrpc.CallInfo, 0) - for i := uint32(0); i < ncmd; i++ { - if len(out) < int(unsafe.Sizeof(callReply{})) { - return nil, fmt.Errorf("failed to read call %v reply", i) - } - reply := *(*callReply)(unsafe.Pointer(&out[0])) - out = out[unsafe.Sizeof(callReply{}):] - var inf *flatrpc.CallInfo - if reply.magic != outMagic { - return nil, fmt.Errorf("bad reply magic 0x%x", reply.magic) - } - if reply.index != extraReplyIndex { - if int(reply.index) >= len(info.Calls) { - return nil, fmt.Errorf("bad call %v index %v/%v", i, reply.index, len(info.Calls)) - } - inf = info.Calls[reply.index] - if inf.Flags != 0 || inf.Signal != nil { - return nil, fmt.Errorf("duplicate reply for call %v/%v/%v", i, reply.index, reply.num) - } - inf.Error = int32(reply.errno) - inf.Flags = flatrpc.CallFlag(reply.flags) - } else { - extraParts = append(extraParts, flatrpc.CallInfo{}) - inf = &extraParts[len(extraParts)-1] - } - if inf.Signal, ok = readUint64Array(&out, reply.signalSize); !ok { - return nil, fmt.Errorf("call %v/%v/%v: signal overflow: %v/%v", - i, reply.index, reply.num, reply.signalSize, len(out)) - } - if inf.Cover, ok = readUint64Array(&out, reply.coverSize); !ok { - return nil, fmt.Errorf("call %v/%v/%v: cover overflow: %v/%v", - i, reply.index, reply.num, reply.coverSize, len(out)) - } - comps, err := readComps(&out, reply.compsSize) - if err != nil { - return nil, err - } - inf.Comps = comps - } - if len(extraParts) == 0 { - return info, nil - } - info.Extra = convertExtra(extraParts, opts.ExecFlags&flatrpc.ExecFlagDedupCover != 0) - return info, nil -} - -func convertExtra(extraParts []flatrpc.CallInfo, dedupCover bool) *flatrpc.CallInfo { - var extra flatrpc.CallInfo - if dedupCover { - extraCover := make(cover.Cover) - for _, part := range extraParts { - extraCover.Merge(part.Cover) - } - extra.Cover = extraCover.Serialize() - } else { - for _, part := range extraParts { - extra.Cover = append(extra.Cover, part.Cover...) - } - } - extraSignal := make(signal.Signal) - for _, part := range extraParts { - extraSignal.Merge(signal.FromRaw(part.Signal, 0)) - } - extra.Signal = make([]uint64, len(extraSignal)) - i := 0 - for s := range extraSignal { - extra.Signal[i] = uint64(s) - i++ - } - return &extra -} - -func readComps(outp *[]byte, compsSize uint32) ([]*flatrpc.Comparison, error) { - comps := make([]*flatrpc.Comparison, 0, 2*compsSize) - for i := uint32(0); i < compsSize; i++ { - typ, ok := readUint32(outp) - if !ok { - return nil, fmt.Errorf("failed to read comp %v", i) - } - if typ > compConstMask|compSizeMask { - return nil, fmt.Errorf("bad comp %v type %v", i, typ) - } - var op1, op2 uint64 - var ok1, ok2 bool - if typ&compSizeMask == compSize8 { - op1, ok1 = readUint64(outp) - op2, ok2 = readUint64(outp) - } else { - var tmp1, tmp2 uint32 - tmp1, ok1 = readUint32(outp) - tmp2, ok2 = readUint32(outp) - op1, op2 = uint64(int64(int32(tmp1))), uint64(int64(int32(tmp2))) - } - if !ok1 || !ok2 { - return nil, fmt.Errorf("failed to read comp %v op", i) - } - if op1 == op2 { - continue // it's useless to store such comparisons - } - comps = append(comps, &flatrpc.Comparison{Op1: op2, Op2: op1}) - if (typ & compConstMask) != 0 { - // If one of the operands was const, then this operand is always - // placed first in the instrumented callbacks. Such an operand - // could not be an argument of our syscalls (because otherwise - // it wouldn't be const), thus we simply ignore it. - continue - } - comps = append(comps, &flatrpc.Comparison{Op1: op1, Op2: op2}) - } - return comps, nil -} - -func readUint32(outp *[]byte) (uint32, bool) { - out := *outp - if len(out) < 4 { - return 0, false - } - v := prog.HostEndian.Uint32(out) - *outp = out[4:] - return v, true -} - -func readUint64(outp *[]byte) (uint64, bool) { - out := *outp - if len(out) < 8 { - return 0, false - } - v := prog.HostEndian.Uint64(out) - *outp = out[8:] - return v, true -} - -func readUint64Array(outp *[]byte, size uint32) ([]uint64, bool) { - if size == 0 { - return nil, true - } - out := *outp - dataSize := int(size * 8) - if dataSize > len(out) { - return nil, false - } - res := unsafe.Slice((*uint64)(unsafe.Pointer(&out[0])), size) - *outp = out[dataSize:] - // Detach the resulting array from the original data. - return slices.Clone(res), true -} - -type command struct { - pid int - config *Config - flags flatrpc.ExecEnv - sandboxArg int64 - timeout time.Duration - cmd *exec.Cmd - dir string - readDone chan []byte - exited chan error - inrp *os.File - outwp *os.File - outmem []byte - freshness uint64 -} - -const ( - inMagic = uint64(0xbadc0ffeebadface) - outMagic = uint32(0xbadf00d) -) - -type handshakeReq struct { - magic uint64 - flags uint64 // env flags - pid uint64 - sandboxArg uint64 - coverFilterSize uint64 - // Followed by [coverFilterSize]uint64 filter. -} - -type handshakeReply struct { - magic uint32 -} - -type executeReq struct { - magic uint64 - envFlags uint64 // env flags - execFlags uint64 // exec flags - pid uint64 - syscallTimeoutMS uint64 - programTimeoutMS uint64 - slowdownScale uint64 -} - -type executeReply struct { - magic uint32 - // If done is 0, then this is call completion message followed by callReply. - // If done is 1, then program execution is finished and status is set. - done uint32 - status uint32 -} - -type callReply struct { - magic uint32 - index uint32 // call index in the program - num uint32 // syscall number (for cross-checking) - errno uint32 - flags uint32 // see CallFlags - signalSize uint32 - coverSize uint32 - compsSize uint32 - // signal/cover/comps follow -} - -func (env *Env) makeCommand(opts *flatrpc.ExecOpts, tmpDir string) (*command, error) { - dir, err := os.MkdirTemp(tmpDir, "syzkaller-testdir") - if err != nil { - return nil, fmt.Errorf("failed to create temp dir: %w", err) - } - dir = osutil.Abs(dir) - - timeout := env.config.Timeouts.Program - if env.config.UseForkServer { - // Executor has an internal timeout and protects against most hangs when fork server is enabled, - // so we use quite large timeout. Executor can be slow due to global locks in namespaces - // and other things, so let's better wait than report false misleading crashes. - timeout *= 5 - } - - c := &command{ - pid: env.pid, - config: env.config, - flags: opts.EnvFlags, - sandboxArg: opts.SandboxArg, - timeout: timeout, - dir: dir, - outmem: env.out, - } - defer func() { - if c != nil { - c.close() - } - }() - - if err := os.Chmod(dir, 0777); err != nil { - return nil, fmt.Errorf("failed to chmod temp dir: %w", err) - } - - // Output capture pipe. - rp, wp, err := os.Pipe() - if err != nil { - return nil, fmt.Errorf("failed to create pipe: %w", err) - } - defer wp.Close() - - // executor->ipc command pipe. - inrp, inwp, err := os.Pipe() - if err != nil { - return nil, fmt.Errorf("failed to create pipe: %w", err) - } - defer inwp.Close() - c.inrp = inrp - - // ipc->executor command pipe. - outrp, outwp, err := os.Pipe() - if err != nil { - return nil, fmt.Errorf("failed to create pipe: %w", err) - } - defer outrp.Close() - c.outwp = outwp - - c.readDone = make(chan []byte, 1) - - cmd := osutil.Command(env.bin[0], env.bin[1:]...) - if env.inFile != nil && env.outFile != nil { - cmd.ExtraFiles = []*os.File{env.inFile, env.outFile} - } - cmd.Dir = dir - // Tell ASAN to not mess with our NONFAILING. - cmd.Env = append(append([]string{}, os.Environ()...), "ASAN_OPTIONS=handle_segv=0 allow_user_segv_handler=1") - cmd.Stdin = outrp - cmd.Stdout = inwp - if c.flags&flatrpc.ExecEnvDebug != 0 { - close(c.readDone) - cmd.Stderr = os.Stdout - } else { - cmd.Stderr = wp - go func(c *command) { - // Read out output in case executor constantly prints something. - const bufSize = 128 << 10 - output := make([]byte, bufSize) - var size uint64 - for { - n, err := rp.Read(output[size:]) - if n > 0 { - size += uint64(n) - if size >= bufSize*3/4 { - copy(output, output[size-bufSize/2:size]) - size = bufSize / 2 - } - } - if err != nil { - rp.Close() - c.readDone <- output[:size] - close(c.readDone) - return - } - } - }(c) - } - if err := cmd.Start(); err != nil { - return nil, fmt.Errorf("failed to start executor binary: %w", err) - } - c.exited = make(chan error, 1) - c.cmd = cmd - go func(c *command) { - err := c.cmd.Wait() - c.exited <- err - close(c.exited) - // Avoid a livelock if cmd.Stderr has been leaked to another alive process. - rp.SetDeadline(time.Now().Add(5 * time.Second)) - }(c) - wp.Close() - // Note: we explicitly close inwp before calling handshake even though we defer it above. - // If we don't do it and executor exits before writing handshake reply, - // reading from inrp will hang since we hold another end of the pipe open. - inwp.Close() - - if c.config.UseForkServer { - if err := c.handshake(); err != nil { - return nil, err - } - } - tmp := c - c = nil // disable defer above - return tmp, nil -} - -func (c *command) close() { - if c.cmd != nil { - c.cmd.Process.Kill() - c.wait() - } - osutil.RemoveAll(c.dir) - if c.inrp != nil { - c.inrp.Close() - } - if c.outwp != nil { - c.outwp.Close() - } -} - -// handshake sends handshakeReq and waits for handshakeReply. -func (c *command) handshake() error { - req := &handshakeReq{ - magic: inMagic, - flags: uint64(c.flags), - pid: uint64(c.pid), - sandboxArg: uint64(c.sandboxArg), - coverFilterSize: uint64(len(c.config.CoverFilter)), - } - reqData := (*[unsafe.Sizeof(*req)]byte)(unsafe.Pointer(req))[:] - if _, err := c.outwp.Write(reqData); err != nil { - return c.handshakeError(fmt.Errorf("failed to write control pipe: %w", err)) - } - if req.coverFilterSize != 0 { - ptr := (*byte)(unsafe.Pointer(&c.config.CoverFilter[0])) - size := uintptr(req.coverFilterSize) * unsafe.Sizeof(c.config.CoverFilter[0]) - coverFilter := unsafe.Slice(ptr, size) - if _, err := c.outwp.Write(coverFilter); err != nil { - return c.handshakeError(fmt.Errorf("failed to write control pipe: %w", err)) - } - } - - read := make(chan error, 1) - go func() { - reply := &handshakeReply{} - replyData := (*[unsafe.Sizeof(*reply)]byte)(unsafe.Pointer(reply))[:] - if _, err := io.ReadFull(c.inrp, replyData); err != nil { - read <- err - return - } - if reply.magic != outMagic { - read <- fmt.Errorf("bad handshake reply magic 0x%x", reply.magic) - return - } - read <- nil - }() - // Sandbox setup can take significant time. - timeout := time.NewTimer(time.Minute * c.config.Timeouts.Scale) - select { - case err := <-read: - timeout.Stop() - if err != nil { - return c.handshakeError(err) - } - return nil - case <-timeout.C: - return c.handshakeError(fmt.Errorf("not serving")) - } -} - -func (c *command) handshakeError(err error) error { - c.cmd.Process.Kill() - output := <-c.readDone - err = fmt.Errorf("executor %v: %w\n%s", c.pid, err, output) - c.wait() - return err -} - -func (c *command) wait() error { - return <-c.exited -} - -func (c *command) exec(opts *flatrpc.ExecOpts) (output []byte, hanged bool, err0 error) { - if c.flags != opts.EnvFlags || c.sandboxArg != opts.SandboxArg { - panic("wrong command") - } - req := &executeReq{ - magic: inMagic, - envFlags: uint64(c.flags), - execFlags: uint64(opts.ExecFlags), - pid: uint64(c.pid), - syscallTimeoutMS: uint64(c.config.Timeouts.Syscall / time.Millisecond), - programTimeoutMS: uint64(c.config.Timeouts.Program / time.Millisecond), - slowdownScale: uint64(c.config.Timeouts.Scale), - } - reqData := (*[unsafe.Sizeof(*req)]byte)(unsafe.Pointer(req))[:] - if _, err := c.outwp.Write(reqData); err != nil { - output = <-c.readDone - err0 = fmt.Errorf("executor %v: failed to write control pipe: %w", c.pid, err) - return - } - // At this point program is executing. - - done := make(chan bool) - hang := make(chan bool) - go func() { - t := time.NewTimer(c.timeout) - select { - case <-t.C: - c.cmd.Process.Kill() - hang <- true - case <-done: - t.Stop() - hang <- false - } - }() - exitStatus := -1 - completedCalls := (*uint32)(unsafe.Pointer(&c.outmem[0])) - outmem := c.outmem[4:] - for { - reply := &executeReply{} - replyData := (*[unsafe.Sizeof(*reply)]byte)(unsafe.Pointer(reply))[:] - if _, err := io.ReadFull(c.inrp, replyData); err != nil { - break - } - if reply.magic != outMagic { - fmt.Fprintf(os.Stderr, "executor %v: got bad reply magic 0x%x\n", c.pid, reply.magic) - os.Exit(1) - } - if reply.done != 0 { - exitStatus = int(reply.status) - break - } - callReply := &callReply{} - callReplyData := (*[unsafe.Sizeof(*callReply)]byte)(unsafe.Pointer(callReply))[:] - if _, err := io.ReadFull(c.inrp, callReplyData); err != nil { - break - } - if callReply.signalSize != 0 || callReply.coverSize != 0 || callReply.compsSize != 0 { - // This is unsupported yet. - fmt.Fprintf(os.Stderr, "executor %v: got call reply with coverage\n", c.pid) - os.Exit(1) - } - copy(outmem, callReplyData) - outmem = outmem[len(callReplyData):] - *completedCalls++ - } - close(done) - if exitStatus == 0 { - // Program was OK. - <-hang - return - } - c.cmd.Process.Kill() - output = <-c.readDone - err := c.wait() - if err != nil { - output = append(output, err.Error()...) - output = append(output, '\n') - } - if <-hang { - hanged = true - return - } - if exitStatus == -1 { - if c.cmd.ProcessState == nil { - exitStatus = statusFail - } else { - exitStatus = osutil.ProcessExitStatus(c.cmd.ProcessState) - } - } - // Ignore all other errors. - // Without fork server executor can legitimately exit (program contains exit_group), - // with fork server the top process can exit with statusFail if it wants special handling. - if exitStatus == statusFail { - err0 = fmt.Errorf("executor %v: exit status %d err %w\n%s", c.pid, exitStatus, err, output) - } - return -} |
