mirror of
https://github.com/reactos/syzkaller.git
synced 2024-12-02 08:26:32 +00:00
8af91f61b4
Currently hub allows managers to exchange programs from corpus. But reproducers are not exchanged and we don't know if a crash happens on other managers as well or not. Allow hub to exchange reproducers. Reproducers are stored in a separate db file with own sequence numbers. This allows to throttle distribution of reproducers to managers, so that they are not overloaded with reproducers and don't lose them on restarts. Based on patch by Andrey Konovalov: https://github.com/google/syzkaller/pull/325 Fixes #282
668 lines
16 KiB
Go
668 lines
16 KiB
Go
// 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 main
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
_ "net/http/pprof"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/syzkaller/pkg/cover"
|
|
. "github.com/google/syzkaller/pkg/log"
|
|
"github.com/google/syzkaller/pkg/osutil"
|
|
"github.com/google/syzkaller/pkg/report"
|
|
"github.com/google/syzkaller/prog"
|
|
"github.com/google/syzkaller/sys"
|
|
)
|
|
|
|
const dateFormat = "Jan 02 2006 15:04:05 MST"
|
|
|
|
func (mgr *Manager) initHttp() {
|
|
http.HandleFunc("/", mgr.httpSummary)
|
|
http.HandleFunc("/corpus", mgr.httpCorpus)
|
|
http.HandleFunc("/crash", mgr.httpCrash)
|
|
http.HandleFunc("/cover", mgr.httpCover)
|
|
http.HandleFunc("/prio", mgr.httpPrio)
|
|
http.HandleFunc("/file", mgr.httpFile)
|
|
http.HandleFunc("/report", mgr.httpReport)
|
|
|
|
ln, err := net.Listen("tcp4", mgr.cfg.Http)
|
|
if err != nil {
|
|
Fatalf("failed to listen on %v: %v", mgr.cfg.Http, err)
|
|
}
|
|
Logf(0, "serving http on http://%v", ln.Addr())
|
|
go func() {
|
|
err := http.Serve(ln, nil)
|
|
Fatalf("failed to serve http: %v", err)
|
|
}()
|
|
}
|
|
|
|
func (mgr *Manager) httpSummary(w http.ResponseWriter, r *http.Request) {
|
|
data := &UISummaryData{
|
|
Name: mgr.cfg.Name,
|
|
}
|
|
|
|
var err error
|
|
if data.Crashes, err = collectCrashes(mgr.cfg.Workdir); err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to collect crashes: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
mgr.mu.Lock()
|
|
defer mgr.mu.Unlock()
|
|
|
|
data.Stats = append(data.Stats, UIStat{Name: "uptime", Value: fmt.Sprint(time.Since(mgr.startTime) / 1e9 * 1e9)})
|
|
data.Stats = append(data.Stats, UIStat{Name: "fuzzing", Value: fmt.Sprint(mgr.fuzzingTime / 60e9 * 60e9)})
|
|
data.Stats = append(data.Stats, UIStat{Name: "corpus", Value: fmt.Sprint(len(mgr.corpus))})
|
|
data.Stats = append(data.Stats, UIStat{Name: "triage queue", Value: fmt.Sprint(len(mgr.candidates))})
|
|
data.Stats = append(data.Stats, UIStat{Name: "cover", Value: fmt.Sprint(len(mgr.corpusCover)), Link: "/cover"})
|
|
data.Stats = append(data.Stats, UIStat{Name: "signal", Value: fmt.Sprint(len(mgr.corpusSignal))})
|
|
|
|
type CallCov struct {
|
|
count int
|
|
cov cover.Cover
|
|
}
|
|
calls := make(map[string]*CallCov)
|
|
for _, inp := range mgr.corpus {
|
|
if calls[inp.Call] == nil {
|
|
calls[inp.Call] = new(CallCov)
|
|
}
|
|
cc := calls[inp.Call]
|
|
cc.count++
|
|
cc.cov = cover.Union(cc.cov, cover.Cover(inp.Cover))
|
|
}
|
|
|
|
secs := uint64(1)
|
|
if !mgr.firstConnect.IsZero() {
|
|
secs = uint64(time.Since(mgr.firstConnect))/1e9 + 1
|
|
}
|
|
|
|
var cov cover.Cover
|
|
for c, cc := range calls {
|
|
cov = cover.Union(cov, cc.cov)
|
|
data.Calls = append(data.Calls, UICallType{
|
|
Name: c,
|
|
Inputs: cc.count,
|
|
Cover: len(cc.cov),
|
|
})
|
|
}
|
|
sort.Sort(UICallTypeArray(data.Calls))
|
|
|
|
var intStats []UIStat
|
|
for k, v := range mgr.stats {
|
|
val := fmt.Sprintf("%v", v)
|
|
if x := v / secs; x >= 10 {
|
|
val += fmt.Sprintf(" (%v/sec)", x)
|
|
} else if x := v * 60 / secs; x >= 10 {
|
|
val += fmt.Sprintf(" (%v/min)", x)
|
|
} else {
|
|
x := v * 60 * 60 / secs
|
|
val += fmt.Sprintf(" (%v/hour)", x)
|
|
}
|
|
intStats = append(intStats, UIStat{Name: k, Value: val})
|
|
}
|
|
sort.Sort(UIStatArray(intStats))
|
|
data.Stats = append(data.Stats, intStats...)
|
|
data.Log = CachedLogOutput()
|
|
|
|
if err := summaryTemplate.Execute(w, data); err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (mgr *Manager) httpCrash(w http.ResponseWriter, r *http.Request) {
|
|
crashID := r.FormValue("id")
|
|
crash := readCrash(mgr.cfg.Workdir, crashID, true)
|
|
if crash == nil {
|
|
http.Error(w, fmt.Sprintf("failed to read crash info"), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if err := crashTemplate.Execute(w, crash); err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) {
|
|
mgr.mu.Lock()
|
|
defer mgr.mu.Unlock()
|
|
|
|
var data []UIInput
|
|
call := r.FormValue("call")
|
|
for sig, inp := range mgr.corpus {
|
|
if call != inp.Call {
|
|
continue
|
|
}
|
|
p, err := prog.Deserialize(inp.Prog)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to deserialize program: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
data = append(data, UIInput{
|
|
Short: p.String(),
|
|
Full: string(inp.Prog),
|
|
Cover: len(inp.Cover),
|
|
Sig: sig,
|
|
})
|
|
}
|
|
sort.Sort(UIInputArray(data))
|
|
|
|
if err := corpusTemplate.Execute(w, data); err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (mgr *Manager) httpCover(w http.ResponseWriter, r *http.Request) {
|
|
mgr.mu.Lock()
|
|
defer mgr.mu.Unlock()
|
|
|
|
var cov cover.Cover
|
|
if sig := r.FormValue("input"); sig != "" {
|
|
cov = mgr.corpus[sig].Cover
|
|
} else {
|
|
call := r.FormValue("call")
|
|
for _, inp := range mgr.corpus {
|
|
if call == "" || call == inp.Call {
|
|
cov = cover.Union(cov, cover.Cover(inp.Cover))
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := generateCoverHtml(w, mgr.cfg.Vmlinux, cov); err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
runtime.GC()
|
|
}
|
|
|
|
func (mgr *Manager) httpPrio(w http.ResponseWriter, r *http.Request) {
|
|
mgr.mu.Lock()
|
|
defer mgr.mu.Unlock()
|
|
|
|
mgr.minimizeCorpus()
|
|
call := r.FormValue("call")
|
|
idx := -1
|
|
for i, c := range sys.Calls {
|
|
if c.CallName == call {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx == -1 {
|
|
http.Error(w, fmt.Sprintf("unknown call: %v", call), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
data := &UIPrioData{Call: call}
|
|
for i, p := range mgr.prios[idx] {
|
|
data.Prios = append(data.Prios, UIPrio{sys.Calls[i].Name, p})
|
|
}
|
|
sort.Sort(UIPrioArray(data.Prios))
|
|
|
|
if err := prioTemplate.Execute(w, data); err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (mgr *Manager) httpFile(w http.ResponseWriter, r *http.Request) {
|
|
mgr.mu.Lock()
|
|
defer mgr.mu.Unlock()
|
|
|
|
file := filepath.Clean(r.FormValue("name"))
|
|
if !strings.HasPrefix(file, "crashes/") && !strings.HasPrefix(file, "corpus/") {
|
|
http.Error(w, "oh, oh, oh!", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
file = filepath.Join(mgr.cfg.Workdir, file)
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
http.Error(w, "failed to open the file", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
io.Copy(w, f)
|
|
}
|
|
|
|
func (mgr *Manager) httpReport(w http.ResponseWriter, r *http.Request) {
|
|
mgr.mu.Lock()
|
|
defer mgr.mu.Unlock()
|
|
|
|
crashID := r.FormValue("id")
|
|
desc, err := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "description"))
|
|
if err != nil {
|
|
http.Error(w, "failed to read description file", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
tag, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.tag"))
|
|
prog, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.prog"))
|
|
cprog, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.cprog"))
|
|
rep, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.report"))
|
|
log, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.stats.log"))
|
|
stats, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.stats"))
|
|
|
|
commitDesc := ""
|
|
if len(tag) != 0 {
|
|
commitDesc = fmt.Sprintf(" on commit %s.", trimNewLines(tag))
|
|
}
|
|
fmt.Fprintf(w, "Syzkaller hit '%s' bug%s.\n\n", trimNewLines(desc), commitDesc)
|
|
if len(rep) != 0 {
|
|
guiltyFile := report.ExtractGuiltyFile(rep)
|
|
if guiltyFile != "" {
|
|
fmt.Fprintf(w, "Guilty file: %v\n\n", guiltyFile)
|
|
maintainers, err := report.GetMaintainers(mgr.cfg.Kernel_Src, guiltyFile)
|
|
if err == nil {
|
|
fmt.Fprintf(w, "Maintainers: %v\n\n", maintainers)
|
|
} else {
|
|
fmt.Fprintf(w, "Failed to extract maintainers: %v\n\n", err)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "%s\n\n", rep)
|
|
}
|
|
if len(prog) == 0 && len(cprog) == 0 {
|
|
fmt.Fprintf(w, "The bug is not reproducible.\n")
|
|
} else {
|
|
fmt.Fprintf(w, "Syzkaller reproducer:\n%s\n\n", prog)
|
|
if len(cprog) != 0 {
|
|
fmt.Fprintf(w, "C reproducer:\n%s\n\n", cprog)
|
|
}
|
|
}
|
|
if len(stats) > 0 {
|
|
fmt.Fprintf(w, "Reproducing stats:\n%s\n\n", stats)
|
|
}
|
|
if len(log) > 0 {
|
|
fmt.Fprintf(w, "Reproducing log:\n%s\n\n", log)
|
|
}
|
|
}
|
|
|
|
func collectCrashes(workdir string) ([]*UICrashType, error) {
|
|
crashdir := filepath.Join(workdir, "crashes")
|
|
dirs, err := osutil.ListDir(crashdir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var crashTypes []*UICrashType
|
|
for _, dir := range dirs {
|
|
crash := readCrash(workdir, dir, false)
|
|
if crash != nil {
|
|
crashTypes = append(crashTypes, crash)
|
|
}
|
|
}
|
|
sort.Sort(UICrashTypeArray(crashTypes))
|
|
return crashTypes, nil
|
|
}
|
|
|
|
func readCrash(workdir, dir string, full bool) *UICrashType {
|
|
if len(dir) != 40 {
|
|
return nil
|
|
}
|
|
crashdir := filepath.Join(workdir, "crashes")
|
|
descFile, err := os.Open(filepath.Join(crashdir, dir, "description"))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer descFile.Close()
|
|
desc, err := ioutil.ReadAll(descFile)
|
|
if err != nil || len(desc) == 0 {
|
|
return nil
|
|
}
|
|
desc = trimNewLines(desc)
|
|
stat, err := descFile.Stat()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
modTime := stat.ModTime()
|
|
descFile.Close()
|
|
|
|
files, err := osutil.ListDir(filepath.Join(crashdir, dir))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var crashes []*UICrash
|
|
reproAttempts := 0
|
|
hasRepro, hasCRepro := false, false
|
|
reports := make(map[string]bool)
|
|
for _, f := range files {
|
|
if strings.HasPrefix(f, "log") {
|
|
index, err := strconv.ParseUint(f[3:], 10, 64)
|
|
if err == nil {
|
|
crashes = append(crashes, &UICrash{
|
|
Index: int(index),
|
|
})
|
|
}
|
|
} else if strings.HasPrefix(f, "report") {
|
|
reports[f] = true
|
|
} else if f == "repro.prog" {
|
|
hasRepro = true
|
|
} else if f == "repro.cprog" {
|
|
hasCRepro = true
|
|
} else if f == "repro.report" {
|
|
} else if f == "repro0" || f == "repro1" || f == "repro2" {
|
|
reproAttempts++
|
|
}
|
|
}
|
|
|
|
if full {
|
|
for _, crash := range crashes {
|
|
index := strconv.Itoa(crash.Index)
|
|
crash.Log = filepath.Join("crashes", dir, "log"+index)
|
|
if stat, err := os.Stat(filepath.Join(workdir, crash.Log)); err == nil {
|
|
crash.Time = stat.ModTime()
|
|
crash.TimeStr = crash.Time.Format(dateFormat)
|
|
}
|
|
tag, _ := ioutil.ReadFile(filepath.Join(crashdir, dir, "tag"+index))
|
|
crash.Tag = string(tag)
|
|
reportFile := filepath.Join("crashes", dir, "report"+index)
|
|
if osutil.IsExist(filepath.Join(workdir, reportFile)) {
|
|
crash.Report = reportFile
|
|
}
|
|
}
|
|
sort.Sort(UICrashArray(crashes))
|
|
}
|
|
|
|
triaged := ""
|
|
if hasRepro {
|
|
if hasCRepro {
|
|
triaged = "has C repro"
|
|
} else {
|
|
triaged = "has repro"
|
|
}
|
|
} else if reproAttempts >= maxReproAttempts {
|
|
triaged = "non-reproducible"
|
|
}
|
|
return &UICrashType{
|
|
Description: string(desc),
|
|
LastTime: modTime.Format(dateFormat),
|
|
ID: dir,
|
|
Count: len(crashes),
|
|
Triaged: triaged,
|
|
Crashes: crashes,
|
|
}
|
|
}
|
|
|
|
func trimNewLines(data []byte) []byte {
|
|
for len(data) > 0 && data[len(data)-1] == '\n' {
|
|
data = data[:len(data)-1]
|
|
}
|
|
return data
|
|
}
|
|
|
|
type UISummaryData struct {
|
|
Name string
|
|
Stats []UIStat
|
|
Calls []UICallType
|
|
Crashes []*UICrashType
|
|
Log string
|
|
}
|
|
|
|
type UICrashType struct {
|
|
Description string
|
|
LastTime string
|
|
ID string
|
|
Count int
|
|
Triaged string
|
|
Crashes []*UICrash
|
|
}
|
|
|
|
type UICrash struct {
|
|
Index int
|
|
Time time.Time
|
|
TimeStr string
|
|
Log string
|
|
Report string
|
|
Tag string
|
|
}
|
|
|
|
type UIStat struct {
|
|
Name string
|
|
Value string
|
|
Link string
|
|
}
|
|
|
|
type UICallType struct {
|
|
Name string
|
|
Inputs int
|
|
Cover int
|
|
}
|
|
|
|
type UIInput struct {
|
|
Short string
|
|
Full string
|
|
Calls int
|
|
Cover int
|
|
Sig string
|
|
}
|
|
|
|
type UICallTypeArray []UICallType
|
|
|
|
func (a UICallTypeArray) Len() int { return len(a) }
|
|
func (a UICallTypeArray) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
|
func (a UICallTypeArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
type UIInputArray []UIInput
|
|
|
|
func (a UIInputArray) Len() int { return len(a) }
|
|
func (a UIInputArray) Less(i, j int) bool { return a[i].Cover > a[j].Cover }
|
|
func (a UIInputArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
type UIStatArray []UIStat
|
|
|
|
func (a UIStatArray) Len() int { return len(a) }
|
|
func (a UIStatArray) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
|
func (a UIStatArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
type UICrashTypeArray []*UICrashType
|
|
|
|
func (a UICrashTypeArray) Len() int { return len(a) }
|
|
func (a UICrashTypeArray) Less(i, j int) bool { return a[i].Description < a[j].Description }
|
|
func (a UICrashTypeArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
type UICrashArray []*UICrash
|
|
|
|
func (a UICrashArray) Len() int { return len(a) }
|
|
func (a UICrashArray) Less(i, j int) bool { return a[i].Time.After(a[j].Time) }
|
|
func (a UICrashArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
var summaryTemplate = template.Must(template.New("").Parse(addStyle(`
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<title>{{.Name }} syzkaller</title>
|
|
{{STYLE}}
|
|
</head>
|
|
<body>
|
|
<b>{{.Name }} syzkaller</b>
|
|
<br>
|
|
<br>
|
|
|
|
<table>
|
|
<caption>Stats:</caption>
|
|
{{range $s := $.Stats}}
|
|
<tr>
|
|
<td>{{$s.Name}}</td>
|
|
{{if $s.Link}}
|
|
<td><a href="{{$s.Link}}">{{$s.Value}}</a></td>
|
|
{{else}}
|
|
<td>{{$s.Value}}</td>
|
|
{{end}}
|
|
</tr>
|
|
{{end}}
|
|
</table>
|
|
<br>
|
|
|
|
<table>
|
|
<caption>Crashes:</caption>
|
|
<tr>
|
|
<th>Description</th>
|
|
<th>Count</th>
|
|
<th>Last Time</th>
|
|
<th>Report</th>
|
|
</tr>
|
|
{{range $c := $.Crashes}}
|
|
<tr>
|
|
<td><a href="/crash?id={{$c.ID}}">{{$c.Description}}</a></td>
|
|
<td>{{$c.Count}}</td>
|
|
<td>{{$c.LastTime}}</td>
|
|
<td>
|
|
{{if $c.Triaged}}
|
|
<a href="/report?id={{$c.ID}}">{{$c.Triaged}}</a>
|
|
{{end}}
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</table>
|
|
<br>
|
|
|
|
<b>Log:</b>
|
|
<br>
|
|
<textarea id="log_textarea" readonly rows="20">
|
|
{{.Log}}
|
|
</textarea>
|
|
<script>
|
|
var textarea = document.getElementById("log_textarea");
|
|
textarea.scrollTop = textarea.scrollHeight;
|
|
</script>
|
|
<br>
|
|
<br>
|
|
|
|
<b>Per-call coverage:</b>
|
|
<br>
|
|
{{range $c := $.Calls}}
|
|
{{$c.Name}}
|
|
<a href='/corpus?call={{$c.Name}}'>inputs:{{$c.Inputs}}</a>
|
|
<a href='/cover?call={{$c.Name}}'>cover:{{$c.Cover}}</a>
|
|
<a href='/prio?call={{$c.Name}}'>prio</a> <br>
|
|
{{end}}
|
|
</body></html>
|
|
`)))
|
|
|
|
var crashTemplate = template.Must(template.New("").Parse(addStyle(`
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<title>{{.Description}}</title>
|
|
{{STYLE}}
|
|
</head>
|
|
<body>
|
|
<b>{{.Description}}</b>
|
|
<br><br>
|
|
|
|
{{if .Triaged}}
|
|
Report: <a href="/report?id={{.ID}}">{{.Triaged}}</a>
|
|
{{end}}
|
|
<br><br>
|
|
|
|
<table>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Log</th>
|
|
<th>Report</th>
|
|
<th>Time</th>
|
|
<th>Tag</th>
|
|
</tr>
|
|
{{range $c := $.Crashes}}
|
|
<tr>
|
|
<td>{{$c.Index}}</td>
|
|
<td><a href="/file?name={{$c.Log}}">log</a></td>
|
|
{{if $c.Report}}
|
|
<td><a href="/file?name={{$c.Report}}">report</a></td>
|
|
{{else}}
|
|
<td></td>
|
|
{{end}}
|
|
<td>{{$c.TimeStr}}</td>
|
|
<td>{{$c.Tag}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</table>
|
|
</body></html>
|
|
`)))
|
|
|
|
var corpusTemplate = template.Must(template.New("").Parse(addStyle(`
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<title>syzkaller corpus</title>
|
|
{{STYLE}}
|
|
</head>
|
|
<body>
|
|
{{range $c := $}}
|
|
<span title="{{$c.Full}}">{{$c.Short}}</span>
|
|
<a href='/cover?input={{$c.Sig}}'>cover:{{$c.Cover}}</a>
|
|
<br>
|
|
{{end}}
|
|
</body></html>
|
|
`)))
|
|
|
|
type UIPrioData struct {
|
|
Call string
|
|
Prios []UIPrio
|
|
}
|
|
|
|
type UIPrio struct {
|
|
Call string
|
|
Prio float32
|
|
}
|
|
|
|
type UIPrioArray []UIPrio
|
|
|
|
func (a UIPrioArray) Len() int { return len(a) }
|
|
func (a UIPrioArray) Less(i, j int) bool { return a[i].Prio > a[j].Prio }
|
|
func (a UIPrioArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
var prioTemplate = template.Must(template.New("").Parse(addStyle(`
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<title>syzkaller priorities</title>
|
|
{{STYLE}}
|
|
</head>
|
|
<body>
|
|
Priorities for {{$.Call}} <br> <br>
|
|
{{range $p := $.Prios}}
|
|
{{printf "%.4f\t%s" $p.Prio $p.Call}} <br>
|
|
{{end}}
|
|
</body></html>
|
|
`)))
|
|
|
|
func addStyle(html string) string {
|
|
return strings.Replace(html, "{{STYLE}}", htmlStyle, -1)
|
|
}
|
|
|
|
const htmlStyle = `
|
|
<style type="text/css" media="screen">
|
|
table {
|
|
border-collapse:collapse;
|
|
border:1px solid;
|
|
}
|
|
table caption {
|
|
font-weight: bold;
|
|
}
|
|
table td {
|
|
border:1px solid;
|
|
padding: 3px;
|
|
}
|
|
table th {
|
|
border:1px solid;
|
|
padding: 3px;
|
|
}
|
|
textarea {
|
|
width:100%;
|
|
}
|
|
</style>
|
|
`
|