1// Copyright 2017 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
4package report
5
6import (
7	"bufio"
8	"bytes"
9	"fmt"
10	"path/filepath"
11	"regexp"
12	"strconv"
13	"strings"
14
15	"github.com/google/syzkaller/pkg/symbolizer"
16	"github.com/ianlancetaylor/demangle"
17)
18
19type fuchsia struct {
20	obj     string
21	ignores []*regexp.Regexp
22}
23
24var (
25	zirconPanic      = []byte("ZIRCON KERNEL PANIC")
26	zirconPanicShort = []byte("KERNEL PANIC")
27	zirconKernelHang = []byte("stopping other cpus")
28	zirconRIP        = regexp.MustCompile(` RIP: (0x[0-9a-f]+) `)
29	zirconBT         = regexp.MustCompile(`^bt#[0-9]+: (0x[0-9a-f]+)`)
30	zirconReportEnd  = []byte("Halted")
31	zirconUnrelated  = []*regexp.Regexp{
32		regexp.MustCompile(`^\[\d+\.\d+\] \d+\.\d+`),
33		regexp.MustCompile(`stopping other cpus`),
34		regexp.MustCompile(`^halting cpu`),
35		regexp.MustCompile(`^dso: `),
36		regexp.MustCompile(`^UPTIME: `),
37		regexp.MustCompile(`^BUILDID `),
38		regexp.MustCompile(`^Halting\.\.\.`),
39	}
40	zirconSkip = []*regexp.Regexp{
41		regexp.MustCompile("^platform_halt$"),
42		regexp.MustCompile("^exception_die$"),
43		regexp.MustCompile("^_panic$"),
44	}
45)
46
47func ctorFuchsia(kernelSrc, kernelObj string, ignores []*regexp.Regexp) (Reporter, []string, error) {
48	ctx := &fuchsia{
49		ignores: ignores,
50	}
51	if kernelObj != "" {
52		ctx.obj = filepath.Join(kernelObj, "zircon.elf")
53	}
54	suppressions := []string{
55		"fatal exception: process /tmp/syz-fuzzer", // OOM presumably
56	}
57	return ctx, suppressions, nil
58}
59
60func (ctx *fuchsia) ContainsCrash(output []byte) bool {
61	return bytes.Contains(output, zirconPanic) ||
62		bytes.Contains(output, zirconKernelHang)
63}
64
65func (ctx *fuchsia) Parse(output []byte) *Report {
66	rep := &Report{
67		Output: output,
68		EndPos: len(output),
69	}
70	wantLocation := true
71	if pos := bytes.Index(output, zirconPanic); pos != -1 {
72		rep.Title = string(zirconPanicShort)
73		rep.StartPos = pos
74	} else if pos := bytes.Index(output, zirconKernelHang); pos != -1 {
75		rep.Title = string(zirconKernelHang)
76		rep.StartPos = pos
77		wantLocation = false // these tend to produce random locations
78	} else {
79		return nil
80	}
81	symb := symbolizer.NewSymbolizer()
82	defer symb.Close()
83	where := ""
84	for s := bufio.NewScanner(bytes.NewReader(output[rep.StartPos:])); s.Scan(); {
85		line := s.Bytes()
86		if len(line) == 0 || matchesAny(line, zirconUnrelated) {
87			continue
88		}
89		if bytes.Equal(line, zirconReportEnd) {
90			break
91		}
92		if bytes.Contains(line, []byte("DEBUG ASSERT FAILED")) {
93			rep.Title = "ASSERT FAILED"
94		}
95		if bytes.Contains(line, []byte("Supervisor Page Fault exception")) {
96			rep.Title = "Supervisor fault"
97		}
98		if bytes.Contains(line, []byte("recursion in interrupt handler")) {
99			rep.Title = "recursion in interrupt handler"
100		}
101		if bytes.Contains(line, []byte("double fault")) {
102			rep.Title = "double fault"
103		}
104		if match := zirconRIP.FindSubmatchIndex(line); match != nil {
105			ctx.processPC(rep, symb, line, match, false, &where)
106		} else if match := zirconBT.FindSubmatchIndex(line); match != nil {
107			if ctx.processPC(rep, symb, line, match, true, &where) {
108				continue
109			}
110		}
111		rep.Report = append(rep.Report, line...)
112		rep.Report = append(rep.Report, '\n')
113	}
114	if wantLocation && where != "" {
115		rep.Title = fmt.Sprintf("%v in %v", rep.Title, where)
116	}
117	return rep
118}
119
120func (ctx *fuchsia) processPC(rep *Report, symb *symbolizer.Symbolizer,
121	line []byte, match []int, call bool, where *string) bool {
122	if ctx.obj == "" {
123		return false
124	}
125	prefix := line[match[0]:match[1]]
126	pcStart := match[2] - match[0]
127	pcEnd := match[3] - match[0]
128	pcStr := prefix[pcStart:pcEnd]
129	pc, err := strconv.ParseUint(string(pcStr), 0, 64)
130	if err != nil {
131		return false
132	}
133	shortPC := pc & 0xfffffff
134	pc = 0xffffffff80000000 | shortPC
135	if call {
136		pc--
137	}
138	frames, err := symb.Symbolize(ctx.obj, pc)
139	if err != nil || len(frames) == 0 {
140		return false
141	}
142	for _, frame := range frames {
143		file := ctx.trimFile(frame.File)
144		name := demangle.Filter(frame.Func, demangle.NoParams, demangle.NoTemplateParams)
145		if strings.Contains(name, "<lambda(") {
146			// demangle produces super long (full) names for lambdas.
147			name = "lambda"
148		}
149		if *where == "" && !matchesAny([]byte(name), zirconSkip) {
150			*where = name
151		}
152		id := "[ inline ]"
153		if !frame.Inline {
154			id = fmt.Sprintf("0x%08x", shortPC)
155		}
156		start := replace(append([]byte{}, prefix...), pcStart, pcEnd, []byte(id))
157		frameLine := fmt.Sprintf("%s %v %v:%v\n", start, name, file, frame.Line)
158		rep.Report = append(rep.Report, frameLine...)
159	}
160	return true
161}
162
163func (ctx *fuchsia) trimFile(file string) string {
164	const (
165		prefix1 = "zircon/kernel/"
166		prefix2 = "zircon/"
167	)
168	if pos := strings.LastIndex(file, prefix1); pos != -1 {
169		return file[pos+len(prefix1):]
170	}
171	if pos := strings.LastIndex(file, prefix2); pos != -1 {
172		return file[pos+len(prefix2):]
173	}
174	return file
175}
176
177func (ctx *fuchsia) Symbolize(rep *Report) error {
178	return nil
179}
180