aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/cover/backend/gvisor.go
blob: bb404d0cc5493f5d38e808288f4cde81c7e60b8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Copyright 2020 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 backend

import (
	"bufio"
	"fmt"
	"path/filepath"
	"regexp"
	"strconv"

	"github.com/google/syzkaller/pkg/mgrconfig"
	"github.com/google/syzkaller/pkg/osutil"
	"github.com/google/syzkaller/pkg/vminfo"
	"github.com/google/syzkaller/sys/targets"
)

func makeGvisor(target *targets.Target, kernelDirs *mgrconfig.KernelDirs, modules []*vminfo.KernelModule) (*Impl,
	error) {
	if len(modules) != 0 {
		return nil, fmt.Errorf("gvisor coverage does not support modules")
	}
	bin := filepath.Join(kernelDirs.Obj, target.KernelObject)
	// pkg/build stores runsc as 'vmlinux' (we pretent to be linux), but a local build will have it as 'runsc'.
	if !osutil.IsExist(bin) {
		bin = filepath.Join(filepath.Dir(bin), "runsc")
	}
	frames, err := gvisorSymbolize(bin, kernelDirs.Src)
	if err != nil {
		return nil, err
	}
	unitMap := make(map[string]*CompileUnit)
	for _, frame := range frames {
		unit := unitMap[frame.Name]
		if unit == nil {
			unit = &CompileUnit{
				ObjectUnit: ObjectUnit{
					Name: frame.Name,
				},
				Path: frame.Path,
			}
			unitMap[frame.Name] = unit
		}
		unit.PCs = append(unit.PCs, frame.PC)
	}
	var units []*CompileUnit
	for _, unit := range unitMap {
		units = append(units, unit)
	}
	impl := &Impl{
		Units:  units,
		Frames: frames,
	}
	return impl, nil
}

func gvisorSymbolize(bin, srcDir string) ([]*Frame, error) {
	cmd := osutil.Command(bin, "symbolize", "-all")
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	defer stdout.Close()
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	defer cmd.Wait()
	defer cmd.Process.Kill()
	var frames []*Frame
	s := bufio.NewScanner(stdout)
	for s.Scan() {
		frame, err := gvisorParseLine(s)
		if err != nil {
			return nil, err
		}
		frame.Path = filepath.Join(srcDir, frame.Name)
		if !osutil.IsExist(frame.Path) {
			// Try to locate auto-generated files.
			// Note: some files are only present under some hashed path,
			// e.g. bazel-out/k8-fastbuild-ST-4c64f0b3d5c7/bin/pkg/usermem/addr_range.go,
			// it's unclear how we can locate them. In a local run that may be under objDir,
			// but this is not the case for syz-ci.
			path := filepath.Join(srcDir, "bazel-out", "k8-fastbuild", "bin", frame.Name)
			if osutil.IsExist(path) {
				frame.Path = path
			}
		}
		frames = append(frames, frame)
	}
	if err := s.Err(); err != nil {
		return nil, err
	}
	return frames, nil
}

func gvisorParseLine(s *bufio.Scanner) (*Frame, error) {
	pc, err := strconv.ParseUint(s.Text(), 0, 64)
	if err != nil {
		return nil, fmt.Errorf("read pc %q, but no line info", pc)
	}
	if !s.Scan() {
		return nil, fmt.Errorf("read pc %q, but no line info", pc)
	}
	match := gvisorLineRe.FindStringSubmatch(s.Text())
	if match == nil {
		return nil, fmt.Errorf("failed to parse line: %q", s.Text())
	}
	var ints [4]int
	for i := range ints {
		x, err := strconv.ParseUint(match[i+3], 0, 32)
		if err != nil {
			return nil, fmt.Errorf("failed to parse number %q: %w", match[i+3], err)
		}
		ints[i] = int(x)
	}
	frame := &Frame{
		PC:   pc,
		Name: match[2],
		Range: Range{
			StartLine: ints[0],
			StartCol:  ints[1],
			EndLine:   ints[2],
			EndCol:    ints[3],
		},
	}
	return frame, nil
}

var gvisorLineRe = regexp.MustCompile(`^(.*/)?(pkg/[^:]+):([0-9]+).([0-9]+),([0-9]+).([0-9]+)$`)