mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-23 11:29:46 +00:00
syz-manager: modernize web UI
1. Use dashboard style. 2. Allow sorting of tables. 3. Show old crashes in grey. 4. Use tables instead of text output for more pages. 5. Show corpus inputs on a separate page to allow copy-pasting. 6. Use standard JS sorting instead of custom bubble sort (much faster). 7. Fix off-by one in table sorting. Fixes #694
This commit is contained in:
parent
582e1f0d1d
commit
13ab4beeef
2
Makefile
2
Makefile
@ -175,7 +175,7 @@ generate: generate_go generate_sys
|
||||
$(MAKE) format
|
||||
|
||||
generate_go: bin/syz-sysgen format_cpp
|
||||
$(GO) generate ./pkg/csource ./executor ./pkg/ifuzz ./pkg/build
|
||||
$(GO) generate ./pkg/csource ./executor ./pkg/ifuzz ./pkg/build ./pkg/html
|
||||
|
||||
generate_sys: bin/syz-sysgen
|
||||
bin/syz-sysgen
|
||||
|
@ -5,12 +5,9 @@ package dash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/syzkaller/dashboard/dashapi"
|
||||
"github.com/google/syzkaller/pkg/html"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/appengine"
|
||||
"google.golang.org/appengine/log"
|
||||
@ -90,84 +87,4 @@ func commonHeader(c context.Context, r *http.Request) *uiHeader {
|
||||
return h
|
||||
}
|
||||
|
||||
func formatTime(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format("2006/01/02 15:04")
|
||||
}
|
||||
|
||||
func formatClock(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format("15:04")
|
||||
}
|
||||
|
||||
func formatDuration(d time.Duration) string {
|
||||
if d == 0 {
|
||||
return ""
|
||||
}
|
||||
days := int(d / (24 * time.Hour))
|
||||
hours := int(d / time.Hour % 24)
|
||||
mins := int(d / time.Minute % 60)
|
||||
if days >= 10 {
|
||||
return fmt.Sprintf("%vd", days)
|
||||
} else if days != 0 {
|
||||
return fmt.Sprintf("%vd%02vh", days, hours)
|
||||
} else if hours != 0 {
|
||||
return fmt.Sprintf("%vh%02vm", hours, mins)
|
||||
}
|
||||
return fmt.Sprintf("%vm", mins)
|
||||
}
|
||||
|
||||
func formatLateness(now, t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return "never"
|
||||
}
|
||||
d := now.Sub(t)
|
||||
if d < 5*time.Minute {
|
||||
return "now"
|
||||
}
|
||||
return formatDuration(d)
|
||||
}
|
||||
|
||||
func formatReproLevel(l dashapi.ReproLevel) string {
|
||||
switch l {
|
||||
case ReproLevelSyz:
|
||||
return "syz"
|
||||
case ReproLevelC:
|
||||
return "C"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func formatStat(v int64) string {
|
||||
if v == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
|
||||
func formatShortHash(v string) string {
|
||||
const hashLen = 8
|
||||
if len(v) <= hashLen {
|
||||
return v
|
||||
}
|
||||
return v[:hashLen]
|
||||
}
|
||||
|
||||
var (
|
||||
templates = template.Must(template.New("").Funcs(templateFuncs).ParseGlob("*.html"))
|
||||
|
||||
templateFuncs = template.FuncMap{
|
||||
"formatTime": formatTime,
|
||||
"formatClock": formatClock,
|
||||
"formatDuration": formatDuration,
|
||||
"formatLateness": formatLateness,
|
||||
"formatReproLevel": formatReproLevel,
|
||||
"formatStat": formatStat,
|
||||
"formatShortHash": formatShortHash,
|
||||
}
|
||||
)
|
||||
var templates = html.CreateGlob("*.html")
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/google/syzkaller/dashboard/dashapi"
|
||||
"github.com/google/syzkaller/pkg/email"
|
||||
"github.com/google/syzkaller/pkg/html"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/appengine/datastore"
|
||||
"google.golang.org/appengine/log"
|
||||
@ -570,7 +571,7 @@ func createUIBug(c context.Context, bug *Bug, state *ReportingState, managers []
|
||||
default:
|
||||
status = fmt.Sprintf("unknown (%v)", bug.Status)
|
||||
}
|
||||
status = fmt.Sprintf("%v on %v", status, formatTime(bug.Closed))
|
||||
status = fmt.Sprintf("%v on %v", status, html.FormatTime(bug.Closed))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/google/syzkaller/dashboard/dashapi"
|
||||
"github.com/google/syzkaller/pkg/email"
|
||||
"github.com/google/syzkaller/pkg/html"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/appengine/datastore"
|
||||
"google.golang.org/appengine/log"
|
||||
@ -97,7 +98,7 @@ func needReport(c context.Context, typ string, state *ReportingState, bug *Bug)
|
||||
if !bugReporting.Reported.IsZero() && bugReporting.ReproLevel >= bug.ReproLevel {
|
||||
status = fmt.Sprintf("%v: reported%v on %v",
|
||||
reporting.DisplayTitle, reproStr(bugReporting.ReproLevel),
|
||||
formatTime(bugReporting.Reported))
|
||||
html.FormatTime(bugReporting.Reported))
|
||||
reporting, bugReporting = nil, nil
|
||||
return
|
||||
}
|
||||
@ -149,7 +150,7 @@ func needReport(c context.Context, typ string, state *ReportingState, bug *Bug)
|
||||
status = fmt.Sprintf("%v: ready to report", reporting.DisplayTitle)
|
||||
if !bugReporting.Reported.IsZero() {
|
||||
status += fmt.Sprintf(" (reported%v on %v)",
|
||||
reproStr(bugReporting.ReproLevel), formatTime(bugReporting.Reported))
|
||||
reproStr(bugReporting.ReproLevel), html.FormatTime(bugReporting.Reported))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -3,28 +3,22 @@
|
||||
|
||||
function sortTable(item, colName, conv, desc = false) {
|
||||
table = item.parentNode.parentNode.parentNode;
|
||||
rows = table.getElementsByTagName("tr");
|
||||
rows = table.rows;
|
||||
col = findColumnByName(rows[0].getElementsByTagName("th"), colName);
|
||||
values = new Array;
|
||||
values = [];
|
||||
for (i = 1; i < rows.length; i++)
|
||||
values[i] = conv(rows[i].getElementsByTagName("td")[col].textContent);
|
||||
values.push([conv(rows[i].getElementsByTagName("td")[col].textContent), rows[i]]);
|
||||
if (desc)
|
||||
desc = !isSorted(values.slice().reverse())
|
||||
else
|
||||
desc = isSorted(values);
|
||||
do {
|
||||
changed = false;
|
||||
for (i = 1; i < values.length - 1; i++) {
|
||||
v0 = values[i];
|
||||
v1 = values[i + 1];
|
||||
if (desc && v0 >= v1 || !desc && v0 <= v1)
|
||||
continue;
|
||||
changed = true;
|
||||
values[i] = v1;
|
||||
values[i + 1] = v0;
|
||||
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
|
||||
}
|
||||
} while (changed);
|
||||
values.sort(function(a, b) {
|
||||
if (a[0] == b[0]) return 0;
|
||||
if (desc && a[0] > b[0] || !desc && a[0] < b[0]) return -1;
|
||||
return 1;
|
||||
});
|
||||
for (i = 0; i < values.length; i++)
|
||||
table.appendChild(values[i][1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -37,8 +31,8 @@ function findColumnByName(headers, colName) {
|
||||
}
|
||||
|
||||
function isSorted(values) {
|
||||
for (i = 1; i < rows.length - 1; i++) {
|
||||
if (values[i] > values[i + 1])
|
||||
for (i = 0; i < values.length - 1; i++) {
|
||||
if (values[i][0] > values[i + 1][0])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -46,6 +40,7 @@ function isSorted(values) {
|
||||
|
||||
function textSort(v) { return v.toLowerCase(); }
|
||||
function numSort(v) { return -parseInt(v); }
|
||||
function floatSort(v) { return -parseFloat(v); }
|
||||
function reproSort(v) { return v == "C" ? 0 : v == "syz" ? 1 : 2; }
|
||||
function patchedSort(v) { return v == "" ? -1 : parseInt(v); }
|
||||
|
||||
|
@ -122,11 +122,27 @@ table td, table th {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.list_table .stat_name {
|
||||
width: 150pt;
|
||||
max-width: 150pt;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.list_table .stat_value {
|
||||
width: 100pt;
|
||||
max-width: 100pt;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.bad {
|
||||
color: #f00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.plain {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
221
pkg/html/generated.go
Normal file
221
pkg/html/generated.go
Normal file
@ -0,0 +1,221 @@
|
||||
package html
|
||||
|
||||
const style = `
|
||||
#topbar {
|
||||
padding: 5px 10px;
|
||||
background: #E0EBF5;
|
||||
}
|
||||
|
||||
#topbar a {
|
||||
color: #375EAB;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #375EAB;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #ccc;
|
||||
margin: 20px 5px;
|
||||
border-collapse: collapse;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
table caption {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
vertical-align: top;
|
||||
padding: 2px 8px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.position_table {
|
||||
border: 0px;
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.position_table td, .position_table tr {
|
||||
vertical-align: center;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.position_table .search {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.list_table td, .list_table th {
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.list_table th {
|
||||
background: #F4F4F4;
|
||||
}
|
||||
|
||||
.list_table tr:nth-child(2n+1) {
|
||||
background: #F4F4F4;
|
||||
}
|
||||
|
||||
.list_table tr:hover {
|
||||
background: #ffff99;
|
||||
}
|
||||
|
||||
.list_table .namespace {
|
||||
width: 100pt;
|
||||
max-width: 100pt;
|
||||
}
|
||||
|
||||
.list_table .title {
|
||||
width: 350pt;
|
||||
max-width: 350pt;
|
||||
}
|
||||
|
||||
.list_table .tag {
|
||||
font-family: monospace;
|
||||
font-size: 8pt;
|
||||
width: 40pt;
|
||||
max-width: 40pt;
|
||||
}
|
||||
|
||||
.list_table .opts {
|
||||
width: 40pt;
|
||||
max-width: 40pt;
|
||||
}
|
||||
|
||||
.list_table .status {
|
||||
width: 250pt;
|
||||
max-width: 250pt;
|
||||
}
|
||||
|
||||
.list_table .patched {
|
||||
width: 60pt;
|
||||
max-width: 60pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list_table .kernel {
|
||||
width: 60pt;
|
||||
max-width: 60pt;
|
||||
}
|
||||
|
||||
.list_table .maintainers {
|
||||
width: 150pt;
|
||||
max-width: 150pt;
|
||||
}
|
||||
|
||||
.list_table .result {
|
||||
width: 60pt;
|
||||
max-width: 60pt;
|
||||
}
|
||||
|
||||
.list_table .stat {
|
||||
width: 50pt;
|
||||
max-width: 50pt;
|
||||
font-family: monospace;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.list_table .stat_name {
|
||||
width: 150pt;
|
||||
max-width: 150pt;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.list_table .stat_value {
|
||||
width: 100pt;
|
||||
max-width: 100pt;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.bad {
|
||||
color: #f00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.plain {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width:100%;
|
||||
font-family: monospace;
|
||||
}
|
||||
`
|
||||
const js = `
|
||||
// Copyright 2018 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.
|
||||
|
||||
function sortTable(item, colName, conv, desc = false) {
|
||||
table = item.parentNode.parentNode.parentNode;
|
||||
rows = table.rows;
|
||||
col = findColumnByName(rows[0].getElementsByTagName("th"), colName);
|
||||
values = [];
|
||||
for (i = 1; i < rows.length; i++)
|
||||
values.push([conv(rows[i].getElementsByTagName("td")[col].textContent), rows[i]]);
|
||||
if (desc)
|
||||
desc = !isSorted(values.slice().reverse())
|
||||
else
|
||||
desc = isSorted(values);
|
||||
values.sort(function(a, b) {
|
||||
if (a[0] == b[0]) return 0;
|
||||
if (desc && a[0] > b[0] || !desc && a[0] < b[0]) return -1;
|
||||
return 1;
|
||||
});
|
||||
for (i = 0; i < values.length; i++)
|
||||
table.appendChild(values[i][1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
function findColumnByName(headers, colName) {
|
||||
for (i = 0; i < headers.length; i++) {
|
||||
if (headers[i].textContent == colName)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isSorted(values) {
|
||||
for (i = 0; i < values.length - 1; i++) {
|
||||
if (values[i][0] > values[i + 1][0])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function textSort(v) { return v.toLowerCase(); }
|
||||
function numSort(v) { return -parseInt(v); }
|
||||
function floatSort(v) { return -parseFloat(v); }
|
||||
function reproSort(v) { return v == "C" ? 0 : v == "syz" ? 1 : 2; }
|
||||
function patchedSort(v) { return v == "" ? -1 : parseInt(v); }
|
||||
|
||||
function timeSort(v) {
|
||||
if (v == "now")
|
||||
return 0;
|
||||
m = v.indexOf('m');
|
||||
h = v.indexOf('h');
|
||||
d = v.indexOf('d');
|
||||
if (m > 0 && h < 0)
|
||||
return parseInt(v);
|
||||
if (h > 0 && m > 0)
|
||||
return parseInt(v) * 60 + parseInt(v.substring(h + 1));
|
||||
if (d > 0 && h > 0)
|
||||
return parseInt(v) * 60 * 24 + parseInt(v.substring(d + 1)) * 60;
|
||||
if (d > 0)
|
||||
return parseInt(v) * 60 * 24;
|
||||
return 1000000000;
|
||||
}
|
||||
`
|
110
pkg/html/html.go
Normal file
110
pkg/html/html.go
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
//go:generate bash -c "echo '// AUTOGENERATED FILE' > generated.go"
|
||||
//go:generate bash -c "echo 'package html' > generated.go"
|
||||
//go:generate bash -c "echo 'const style = `' >> generated.go"
|
||||
//go:generate bash -c "cat ../../dashboard/app/static/style.css >> generated.go"
|
||||
//go:generate bash -c "echo '`' >> generated.go"
|
||||
//go:generate bash -c "echo 'const js = `' >> generated.go"
|
||||
//go:generate bash -c "cat ../../dashboard/app/static/common.js >> generated.go"
|
||||
//go:generate bash -c "echo '`' >> generated.go"
|
||||
|
||||
package html
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/syzkaller/dashboard/dashapi"
|
||||
)
|
||||
|
||||
func CreatePage(page string) *template.Template {
|
||||
const headTempl = `<style type="text/css" media="screen">%v</style><script>%v</script>`
|
||||
page = strings.Replace(page, "{{HEAD}}", fmt.Sprintf(headTempl, style, js), 1)
|
||||
return template.Must(template.New("").Funcs(funcs).Parse(page))
|
||||
}
|
||||
|
||||
func CreateGlob(glob string) *template.Template {
|
||||
return template.Must(template.New("").Funcs(funcs).ParseGlob(glob))
|
||||
}
|
||||
|
||||
var funcs = template.FuncMap{
|
||||
"formatTime": FormatTime,
|
||||
"formatClock": formatClock,
|
||||
"formatDuration": formatDuration,
|
||||
"formatLateness": formatLateness,
|
||||
"formatReproLevel": formatReproLevel,
|
||||
"formatStat": formatStat,
|
||||
"formatShortHash": formatShortHash,
|
||||
}
|
||||
|
||||
func FormatTime(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format("2006/01/02 15:04")
|
||||
}
|
||||
|
||||
func formatClock(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format("15:04")
|
||||
}
|
||||
|
||||
func formatDuration(d time.Duration) string {
|
||||
if d == 0 {
|
||||
return ""
|
||||
}
|
||||
days := int(d / (24 * time.Hour))
|
||||
hours := int(d / time.Hour % 24)
|
||||
mins := int(d / time.Minute % 60)
|
||||
if days >= 10 {
|
||||
return fmt.Sprintf("%vd", days)
|
||||
} else if days != 0 {
|
||||
return fmt.Sprintf("%vd%02vh", days, hours)
|
||||
} else if hours != 0 {
|
||||
return fmt.Sprintf("%vh%02vm", hours, mins)
|
||||
}
|
||||
return fmt.Sprintf("%vm", mins)
|
||||
}
|
||||
|
||||
func formatLateness(now, t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return "never"
|
||||
}
|
||||
d := now.Sub(t)
|
||||
if d < 5*time.Minute {
|
||||
return "now"
|
||||
}
|
||||
return formatDuration(d)
|
||||
}
|
||||
|
||||
func formatReproLevel(l dashapi.ReproLevel) string {
|
||||
switch l {
|
||||
case dashapi.ReproLevelSyz:
|
||||
return "syz"
|
||||
case dashapi.ReproLevelC:
|
||||
return "C"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func formatStat(v int64) string {
|
||||
if v == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
|
||||
func formatShortHash(v string) string {
|
||||
const hashLen = 8
|
||||
if len(v) <= hashLen {
|
||||
return v
|
||||
}
|
||||
return v[:hashLen]
|
||||
}
|
@ -6,7 +6,6 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@ -21,13 +20,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/syzkaller/pkg/cover"
|
||||
"github.com/google/syzkaller/pkg/html"
|
||||
"github.com/google/syzkaller/pkg/log"
|
||||
"github.com/google/syzkaller/pkg/osutil"
|
||||
"github.com/google/syzkaller/prog"
|
||||
)
|
||||
|
||||
const dateFormat = "Jan 02 2006 15:04:05 MST"
|
||||
|
||||
func (mgr *Manager) initHTTP() {
|
||||
http.HandleFunc("/", mgr.httpSummary)
|
||||
http.HandleFunc("/syscalls", mgr.httpSyscalls)
|
||||
@ -38,6 +36,7 @@ func (mgr *Manager) initHTTP() {
|
||||
http.HandleFunc("/file", mgr.httpFile)
|
||||
http.HandleFunc("/report", mgr.httpReport)
|
||||
http.HandleFunc("/rawcover", mgr.httpRawCover)
|
||||
http.HandleFunc("/input", mgr.httpInput)
|
||||
// Browsers like to request this, without special handler this goes to / handler.
|
||||
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
@ -83,8 +82,9 @@ func (mgr *Manager) httpSyscalls(w http.ResponseWriter, r *http.Request) {
|
||||
Cover: len(cc.cov),
|
||||
})
|
||||
}
|
||||
sort.Sort(UICallTypeArray(data.Calls))
|
||||
|
||||
sort.Slice(data.Calls, func(i, j int) bool {
|
||||
return data.Calls[i].Name < data.Calls[j].Name
|
||||
})
|
||||
if err := syscallsTemplate.Execute(w, data); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to execute template: %v", err),
|
||||
http.StatusInternalServerError)
|
||||
@ -104,7 +104,7 @@ func (mgr *Manager) collectStats() []UIStat {
|
||||
stats := []UIStat{
|
||||
{Name: "uptime", Value: fmt.Sprint(time.Since(mgr.startTime) / 1e9 * 1e9)},
|
||||
{Name: "fuzzing", Value: fmt.Sprint(mgr.fuzzingTime / 60e9 * 60e9)},
|
||||
{Name: "corpus", Value: fmt.Sprint(len(mgr.corpus))},
|
||||
{Name: "corpus", Value: fmt.Sprint(len(mgr.corpus)), Link: "/corpus"},
|
||||
{Name: "triage queue", Value: fmt.Sprint(len(mgr.candidates))},
|
||||
{Name: "cover", Value: fmt.Sprint(len(mgr.corpusCover)), Link: "/cover"},
|
||||
{Name: "signal", Value: fmt.Sprint(mgr.corpusSignal.Len())},
|
||||
@ -124,7 +124,9 @@ func (mgr *Manager) collectStats() []UIStat {
|
||||
|
||||
intStats := convertStats(mgr.stats.all(), secs)
|
||||
intStats = append(intStats, convertStats(mgr.fuzzerStats, secs)...)
|
||||
sort.Sort(UIStatArray(intStats))
|
||||
sort.Slice(intStats, func(i, j int) bool {
|
||||
return intStats[i].Name < intStats[j].Name
|
||||
})
|
||||
stats = append(stats, intStats...)
|
||||
return stats
|
||||
}
|
||||
@ -164,7 +166,7 @@ func (mgr *Manager) collectSyscallInfo() map[string]*CallCov {
|
||||
|
||||
func (mgr *Manager) httpCrash(w http.ResponseWriter, r *http.Request) {
|
||||
crashID := r.FormValue("id")
|
||||
crash := readCrash(mgr.cfg.Workdir, crashID, nil, true)
|
||||
crash := readCrash(mgr.cfg.Workdir, crashID, nil, mgr.startTime, true)
|
||||
if crash == nil {
|
||||
http.Error(w, fmt.Sprintf("failed to read crash info"), http.StatusInternalServerError)
|
||||
return
|
||||
@ -179,10 +181,11 @@ func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) {
|
||||
mgr.mu.Lock()
|
||||
defer mgr.mu.Unlock()
|
||||
|
||||
var data []UIInput
|
||||
call := r.FormValue("call")
|
||||
data := UICorpus{
|
||||
Call: r.FormValue("call"),
|
||||
}
|
||||
for sig, inp := range mgr.corpus {
|
||||
if call != inp.Call {
|
||||
if data.Call != "" && data.Call != inp.Call {
|
||||
continue
|
||||
}
|
||||
p, err := mgr.target.Deserialize(inp.Prog)
|
||||
@ -190,14 +193,19 @@ func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) {
|
||||
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),
|
||||
data.Inputs = append(data.Inputs, &UIInput{
|
||||
Sig: sig,
|
||||
Short: p.String(),
|
||||
Cover: len(inp.Cover),
|
||||
})
|
||||
}
|
||||
sort.Sort(UIInputArray(data))
|
||||
sort.Slice(data.Inputs, func(i, j int) bool {
|
||||
a, b := data.Inputs[i], data.Inputs[j]
|
||||
if a.Cover != b.Cover {
|
||||
return a.Cover > b.Cover
|
||||
}
|
||||
return a.Short < b.Short
|
||||
})
|
||||
|
||||
if err := corpusTemplate.Execute(w, data); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
|
||||
@ -308,7 +316,9 @@ func (mgr *Manager) httpPrio(w http.ResponseWriter, r *http.Request) {
|
||||
for i, p := range prios[idx] {
|
||||
data.Prios = append(data.Prios, UIPrio{mgr.target.Syscalls[i].Name, p})
|
||||
}
|
||||
sort.Sort(UIPrioArray(data.Prios))
|
||||
sort.Slice(data.Prios, func(i, j int) bool {
|
||||
return data.Prios[i].Prio > data.Prios[j].Prio
|
||||
})
|
||||
|
||||
if err := prioTemplate.Execute(w, data); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
|
||||
@ -333,6 +343,18 @@ func (mgr *Manager) httpFile(w http.ResponseWriter, r *http.Request) {
|
||||
io.Copy(w, f)
|
||||
}
|
||||
|
||||
func (mgr *Manager) httpInput(w http.ResponseWriter, r *http.Request) {
|
||||
mgr.mu.Lock()
|
||||
defer mgr.mu.Unlock()
|
||||
inp, ok := mgr.corpus[r.FormValue("sig")]
|
||||
if !ok {
|
||||
http.Error(w, "can't find the input", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Write(inp.Prog)
|
||||
}
|
||||
|
||||
func (mgr *Manager) httpReport(w http.ResponseWriter, r *http.Request) {
|
||||
mgr.mu.Lock()
|
||||
defer mgr.mu.Unlock()
|
||||
@ -413,16 +435,18 @@ func (mgr *Manager) collectCrashes(workdir string) ([]*UICrashType, error) {
|
||||
}
|
||||
var crashTypes []*UICrashType
|
||||
for _, dir := range dirs {
|
||||
crash := readCrash(workdir, dir, repros, false)
|
||||
crash := readCrash(workdir, dir, repros, mgr.startTime, false)
|
||||
if crash != nil {
|
||||
crashTypes = append(crashTypes, crash)
|
||||
}
|
||||
}
|
||||
sort.Sort(UICrashTypeArray(crashTypes))
|
||||
sort.Slice(crashTypes, func(i, j int) bool {
|
||||
return strings.ToLower(crashTypes[i].Description) < strings.ToLower(crashTypes[j].Description)
|
||||
})
|
||||
return crashTypes, nil
|
||||
}
|
||||
|
||||
func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashType {
|
||||
func readCrash(workdir, dir string, repros map[string]bool, start time.Time, full bool) *UICrashType {
|
||||
if len(dir) != 40 {
|
||||
return nil
|
||||
}
|
||||
@ -478,7 +502,7 @@ func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashT
|
||||
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)
|
||||
crash.Active = crash.Time.After(start)
|
||||
}
|
||||
tag, _ := ioutil.ReadFile(filepath.Join(crashdir, dir, "tag"+index))
|
||||
crash.Tag = string(tag)
|
||||
@ -487,13 +511,16 @@ func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashT
|
||||
crash.Report = reportFile
|
||||
}
|
||||
}
|
||||
sort.Sort(UICrashArray(crashes))
|
||||
sort.Slice(crashes, func(i, j int) bool {
|
||||
return crashes[i].Time.After(crashes[j].Time)
|
||||
})
|
||||
}
|
||||
|
||||
triaged := reproStatus(hasRepro, hasCRepro, repros[desc], reproAttempts >= maxReproAttempts)
|
||||
return &UICrashType{
|
||||
Description: desc,
|
||||
LastTime: modTime.Format(dateFormat),
|
||||
LastTime: modTime,
|
||||
Active: modTime.After(start),
|
||||
ID: dir,
|
||||
Count: len(crashes),
|
||||
Triaged: triaged,
|
||||
@ -537,7 +564,8 @@ type UISyscallsData struct {
|
||||
|
||||
type UICrashType struct {
|
||||
Description string
|
||||
LastTime string
|
||||
LastTime time.Time
|
||||
Active bool
|
||||
ID string
|
||||
Count int
|
||||
Triaged string
|
||||
@ -545,12 +573,12 @@ type UICrashType struct {
|
||||
}
|
||||
|
||||
type UICrash struct {
|
||||
Index int
|
||||
Time time.Time
|
||||
TimeStr string
|
||||
Log string
|
||||
Report string
|
||||
Tag string
|
||||
Index int
|
||||
Time time.Time
|
||||
Active bool
|
||||
Log string
|
||||
Report string
|
||||
Tag string
|
||||
}
|
||||
|
||||
type UIStat struct {
|
||||
@ -565,84 +593,57 @@ type UICallType struct {
|
||||
Cover int
|
||||
}
|
||||
|
||||
type UIInput struct {
|
||||
Short string
|
||||
Full string
|
||||
Calls int
|
||||
Cover int
|
||||
Sig string
|
||||
type UICorpus struct {
|
||||
Call string
|
||||
Inputs []*UIInput
|
||||
}
|
||||
|
||||
type UICallTypeArray []UICallType
|
||||
type UIInput struct {
|
||||
Sig string
|
||||
Short string
|
||||
Cover int
|
||||
}
|
||||
|
||||
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(`
|
||||
var summaryTemplate = html.CreatePage(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Name }} syzkaller</title>
|
||||
{{STYLE}}
|
||||
{{HEAD}}
|
||||
</head>
|
||||
<body>
|
||||
<b>{{.Name }} syzkaller</b>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<table>
|
||||
<table class="list_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}}
|
||||
<td class="stat_name">{{$s.Name}}</td>
|
||||
<td class="stat_value">
|
||||
{{if $s.Link}}
|
||||
<a href="{{$s.Link}}">{{$s.Value}}</a>
|
||||
{{else}}
|
||||
{{$s.Value}}
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
<br>
|
||||
|
||||
<table>
|
||||
<table class="list_table">
|
||||
<caption>Crashes:</caption>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Count</th>
|
||||
<th>Last Time</th>
|
||||
<th>Report</th>
|
||||
<th><a onclick="return sortTable(this, 'Description', textSort)" href="#">Description</a></th>
|
||||
<th><a onclick="return sortTable(this, 'Count', numSort)" href="#">Count</a></th>
|
||||
<th><a onclick="return sortTable(this, 'Last Time', textSort, true)" href="#">Last Time</a></th>
|
||||
<th><a onclick="return sortTable(this, 'Report', textSort)" href="#">Report</a></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 class="title"><a href="/crash?id={{$c.ID}}">{{$c.Description}}</a></td>
|
||||
<td class="stat {{if not $c.Active}}inactive{{end}}">{{$c.Count}}</td>
|
||||
<td class="time {{if not $c.Active}}inactive{{end}}">{{formatTime $c.LastTime}}</td>
|
||||
<td>
|
||||
{{if $c.Triaged}}
|
||||
<a href="/report?id={{$c.ID}}">{{$c.Triaged}}</a>
|
||||
@ -651,11 +652,10 @@ var summaryTemplate = template.Must(template.New("").Parse(addStyle(`
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
<br>
|
||||
|
||||
<b>Log:</b>
|
||||
<br>
|
||||
<textarea id="log_textarea" readonly rows="20">
|
||||
<textarea id="log_textarea" readonly rows="20" wrap=off>
|
||||
{{.Log}}
|
||||
</textarea>
|
||||
<script>
|
||||
@ -663,44 +663,52 @@ var summaryTemplate = template.Must(template.New("").Parse(addStyle(`
|
||||
textarea.scrollTop = textarea.scrollHeight;
|
||||
</script>
|
||||
</body></html>
|
||||
`)))
|
||||
`)
|
||||
|
||||
var syscallsTemplate = template.Must(template.New("").Parse(addStyle(`
|
||||
var syscallsTemplate = html.CreatePage(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Name }} syzkaller</title>
|
||||
{{STYLE}}
|
||||
{{HEAD}}
|
||||
</head>
|
||||
<body>
|
||||
<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(`
|
||||
<table class="list_table">
|
||||
<caption>Per-syscall coverage:</caption>
|
||||
<tr>
|
||||
<th><a onclick="return sortTable(this, 'Syscall', textSort)" href="#">Syscall</a></th>
|
||||
<th><a onclick="return sortTable(this, 'Inputs', numSort)" href="#">Inputs</a></th>
|
||||
<th><a onclick="return sortTable(this, 'Coverage', numSort)" href="#">Coverage</a></th>
|
||||
<th>Prio</th>
|
||||
</tr>
|
||||
{{range $c := $.Calls}}
|
||||
<tr>
|
||||
<td>{{$c.Name}}</td>
|
||||
<td><a href='/corpus?call={{$c.Name}}'>{{$c.Inputs}}</a></td>
|
||||
<td><a href='/cover?call={{$c.Name}}'>{{$c.Cover}}</a></td>
|
||||
<td><a href='/prio?call={{$c.Name}}'>prio</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</body></html>
|
||||
`)
|
||||
|
||||
var crashTemplate = html.CreatePage(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Description}}</title>
|
||||
{{STYLE}}
|
||||
{{HEAD}}
|
||||
</head>
|
||||
<body>
|
||||
<b>{{.Description}}</b>
|
||||
<br><br>
|
||||
|
||||
{{if .Triaged}}
|
||||
Report: <a href="/report?id={{.ID}}">{{.Triaged}}</a>
|
||||
{{end}}
|
||||
<br><br>
|
||||
|
||||
<table>
|
||||
<table class="list_table">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Log</th>
|
||||
@ -712,34 +720,43 @@ Report: <a href="/report?id={{.ID}}">{{.Triaged}}</a>
|
||||
<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>
|
||||
<td>
|
||||
{{if $c.Report}}
|
||||
<a href="/file?name={{$c.Report}}">report</a></td>
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="time {{if not $c.Active}}inactive{{end}}">{{formatTime $c.Time}}</td>
|
||||
<td class="tag {{if not $c.Active}}inactive{{end}}" title="{{$c.Tag}}">{{formatShortHash $c.Tag}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</body></html>
|
||||
`)))
|
||||
`)
|
||||
|
||||
var corpusTemplate = template.Must(template.New("").Parse(addStyle(`
|
||||
var corpusTemplate = html.CreatePage(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>syzkaller corpus</title>
|
||||
{{STYLE}}
|
||||
{{HEAD}}
|
||||
</head>
|
||||
<body>
|
||||
{{range $c := $}}
|
||||
<span title="{{$c.Full}}">{{$c.Short}}</span>
|
||||
<a href='/cover?input={{$c.Sig}}'>cover:{{$c.Cover}}</a>
|
||||
<br>
|
||||
{{end}}
|
||||
|
||||
<table class="list_table">
|
||||
<caption>Corpus{{if $.Call}} for {{$.Call}}{{end}}:</caption>
|
||||
<tr>
|
||||
<th>Coverage</th>
|
||||
<th>Program</th>
|
||||
</tr>
|
||||
{{range $inp := $.Inputs}}
|
||||
<tr>
|
||||
<td><a href='/cover?input={{$inp.Sig}}'>{{$inp.Cover}}</a></td>
|
||||
<td><a href="/input?sig={{$inp.Sig}}">{{$inp.Short}}</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</body></html>
|
||||
`)))
|
||||
`)
|
||||
|
||||
type UIPrioData struct {
|
||||
Call string
|
||||
@ -751,26 +768,29 @@ type UIPrio struct {
|
||||
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(`
|
||||
var prioTemplate = html.CreatePage(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>syzkaller priorities</title>
|
||||
{{STYLE}}
|
||||
{{HEAD}}
|
||||
</head>
|
||||
<body>
|
||||
Priorities for {{$.Call}} <br> <br>
|
||||
{{range $p := $.Prios}}
|
||||
{{printf "%.4f\t%s" $p.Prio $p.Call}} <br>
|
||||
{{end}}
|
||||
<table class="list_table">
|
||||
<caption>Priorities for {{$.Call}}:</caption>
|
||||
<tr>
|
||||
<th><a onclick="return sortTable(this, 'Prio', floatSort)" href="#">Prio</a></th>
|
||||
<th><a onclick="return sortTable(this, 'Call', textSort)" href="#">Call</a></th>
|
||||
</tr>
|
||||
{{range $p := $.Prios}}
|
||||
<tr>
|
||||
<td>{{printf "%.4f" $p.Prio}}</td>
|
||||
<td><a href='/prio?call={{$p.Call}}'>{{$p.Call}}</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</body></html>
|
||||
`)))
|
||||
`)
|
||||
|
||||
type UIFallbackCoverData struct {
|
||||
Calls []UIFallbackCall
|
||||
@ -782,15 +802,15 @@ type UIFallbackCall struct {
|
||||
Errnos []int
|
||||
}
|
||||
|
||||
var fallbackCoverTemplate = template.Must(template.New("").Parse(addStyle(`
|
||||
var fallbackCoverTemplate = html.CreatePage(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>syzkaller coverage</title>
|
||||
{{STYLE}}
|
||||
{{HEAD}}
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<table class="list_table">
|
||||
<tr>
|
||||
<th>Call</th>
|
||||
<th>Successful</th>
|
||||
@ -805,31 +825,4 @@ var fallbackCoverTemplate = template.Must(template.New("").Parse(addStyle(`
|
||||
{{end}}
|
||||
</table>
|
||||
</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>
|
||||
`
|
||||
`)
|
||||
|
Loading…
Reference in New Issue
Block a user