1// Copyright 2015 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 main 5 6import ( 7 "bufio" 8 "fmt" 9 "html/template" 10 "io" 11 "io/ioutil" 12 "net" 13 "net/http" 14 _ "net/http/pprof" 15 "os" 16 "path/filepath" 17 "runtime" 18 "sort" 19 "strconv" 20 "strings" 21 "time" 22 23 "github.com/google/syzkaller/pkg/cover" 24 "github.com/google/syzkaller/pkg/log" 25 "github.com/google/syzkaller/pkg/osutil" 26 "github.com/google/syzkaller/prog" 27) 28 29const dateFormat = "Jan 02 2006 15:04:05 MST" 30 31func (mgr *Manager) initHTTP() { 32 http.HandleFunc("/", mgr.httpSummary) 33 http.HandleFunc("/syscalls", mgr.httpSyscalls) 34 http.HandleFunc("/corpus", mgr.httpCorpus) 35 http.HandleFunc("/crash", mgr.httpCrash) 36 http.HandleFunc("/cover", mgr.httpCover) 37 http.HandleFunc("/prio", mgr.httpPrio) 38 http.HandleFunc("/file", mgr.httpFile) 39 http.HandleFunc("/report", mgr.httpReport) 40 http.HandleFunc("/rawcover", mgr.httpRawCover) 41 // Browsers like to request this, without special handler this goes to / handler. 42 http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {}) 43 44 ln, err := net.Listen("tcp4", mgr.cfg.HTTP) 45 if err != nil { 46 log.Fatalf("failed to listen on %v: %v", mgr.cfg.HTTP, err) 47 } 48 log.Logf(0, "serving http on http://%v", ln.Addr()) 49 go func() { 50 err := http.Serve(ln, nil) 51 log.Fatalf("failed to serve http: %v", err) 52 }() 53} 54 55func (mgr *Manager) httpSummary(w http.ResponseWriter, r *http.Request) { 56 data := &UISummaryData{ 57 Name: mgr.cfg.Name, 58 Log: log.CachedLogOutput(), 59 Stats: mgr.collectStats(), 60 } 61 62 var err error 63 if data.Crashes, err = mgr.collectCrashes(mgr.cfg.Workdir); err != nil { 64 http.Error(w, fmt.Sprintf("failed to collect crashes: %v", err), http.StatusInternalServerError) 65 return 66 } 67 68 if err := summaryTemplate.Execute(w, data); err != nil { 69 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), 70 http.StatusInternalServerError) 71 return 72 } 73} 74 75func (mgr *Manager) httpSyscalls(w http.ResponseWriter, r *http.Request) { 76 data := &UISyscallsData{ 77 Name: mgr.cfg.Name, 78 } 79 for c, cc := range mgr.collectSyscallInfo() { 80 data.Calls = append(data.Calls, UICallType{ 81 Name: c, 82 Inputs: cc.count, 83 Cover: len(cc.cov), 84 }) 85 } 86 sort.Sort(UICallTypeArray(data.Calls)) 87 88 if err := syscallsTemplate.Execute(w, data); err != nil { 89 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), 90 http.StatusInternalServerError) 91 return 92 } 93} 94 95type CallCov struct { 96 count int 97 cov cover.Cover 98} 99 100func (mgr *Manager) collectStats() []UIStat { 101 mgr.mu.Lock() 102 defer mgr.mu.Unlock() 103 104 stats := []UIStat{ 105 {Name: "uptime", Value: fmt.Sprint(time.Since(mgr.startTime) / 1e9 * 1e9)}, 106 {Name: "fuzzing", Value: fmt.Sprint(mgr.fuzzingTime / 60e9 * 60e9)}, 107 {Name: "corpus", Value: fmt.Sprint(len(mgr.corpus))}, 108 {Name: "triage queue", Value: fmt.Sprint(len(mgr.candidates))}, 109 {Name: "cover", Value: fmt.Sprint(len(mgr.corpusCover)), Link: "/cover"}, 110 {Name: "signal", Value: fmt.Sprint(mgr.corpusSignal.Len())}, 111 } 112 if mgr.checkResult != nil { 113 stats = append(stats, UIStat{ 114 Name: "syscalls", 115 Value: fmt.Sprint(len(mgr.checkResult.EnabledCalls[mgr.cfg.Sandbox])), 116 Link: "/syscalls", 117 }) 118 } 119 120 secs := uint64(1) 121 if !mgr.firstConnect.IsZero() { 122 secs = uint64(time.Since(mgr.firstConnect))/1e9 + 1 123 } 124 125 intStats := convertStats(mgr.stats.all(), secs) 126 intStats = append(intStats, convertStats(mgr.fuzzerStats, secs)...) 127 sort.Sort(UIStatArray(intStats)) 128 stats = append(stats, intStats...) 129 return stats 130} 131 132func convertStats(stats map[string]uint64, secs uint64) []UIStat { 133 var intStats []UIStat 134 for k, v := range stats { 135 val := fmt.Sprintf("%v", v) 136 if x := v / secs; x >= 10 { 137 val += fmt.Sprintf(" (%v/sec)", x) 138 } else if x := v * 60 / secs; x >= 10 { 139 val += fmt.Sprintf(" (%v/min)", x) 140 } else { 141 x := v * 60 * 60 / secs 142 val += fmt.Sprintf(" (%v/hour)", x) 143 } 144 intStats = append(intStats, UIStat{Name: k, Value: val}) 145 } 146 return intStats 147} 148 149func (mgr *Manager) collectSyscallInfo() map[string]*CallCov { 150 mgr.mu.Lock() 151 defer mgr.mu.Unlock() 152 153 calls := make(map[string]*CallCov) 154 for _, inp := range mgr.corpus { 155 if calls[inp.Call] == nil { 156 calls[inp.Call] = new(CallCov) 157 } 158 cc := calls[inp.Call] 159 cc.count++ 160 cc.cov.Merge(inp.Cover) 161 } 162 return calls 163} 164 165func (mgr *Manager) httpCrash(w http.ResponseWriter, r *http.Request) { 166 crashID := r.FormValue("id") 167 crash := readCrash(mgr.cfg.Workdir, crashID, nil, true) 168 if crash == nil { 169 http.Error(w, fmt.Sprintf("failed to read crash info"), http.StatusInternalServerError) 170 return 171 } 172 if err := crashTemplate.Execute(w, crash); err != nil { 173 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) 174 return 175 } 176} 177 178func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) { 179 mgr.mu.Lock() 180 defer mgr.mu.Unlock() 181 182 var data []UIInput 183 call := r.FormValue("call") 184 for sig, inp := range mgr.corpus { 185 if call != inp.Call { 186 continue 187 } 188 p, err := mgr.target.Deserialize(inp.Prog) 189 if err != nil { 190 http.Error(w, fmt.Sprintf("failed to deserialize program: %v", err), http.StatusInternalServerError) 191 return 192 } 193 data = append(data, UIInput{ 194 Short: p.String(), 195 Full: string(inp.Prog), 196 Cover: len(inp.Cover), 197 Sig: sig, 198 }) 199 } 200 sort.Sort(UIInputArray(data)) 201 202 if err := corpusTemplate.Execute(w, data); err != nil { 203 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) 204 return 205 } 206} 207 208func (mgr *Manager) httpCover(w http.ResponseWriter, r *http.Request) { 209 mgr.mu.Lock() 210 defer mgr.mu.Unlock() 211 212 if mgr.checkResult == nil { 213 http.Error(w, fmt.Sprintf("machine is not checked yet"), http.StatusInternalServerError) 214 return 215 } 216 if mgr.cfg.Cover { 217 mgr.httpCoverCover(w, r) 218 } else { 219 mgr.httpCoverFallback(w, r) 220 } 221} 222 223func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request) { 224 if mgr.cfg.KernelObj == "" { 225 http.Error(w, fmt.Sprintf("no kernel_obj in config file"), http.StatusInternalServerError) 226 return 227 } 228 var cov cover.Cover 229 if sig := r.FormValue("input"); sig != "" { 230 cov.Merge(mgr.corpus[sig].Cover) 231 } else { 232 call := r.FormValue("call") 233 for _, inp := range mgr.corpus { 234 if call == "" || call == inp.Call { 235 cov.Merge(inp.Cover) 236 } 237 } 238 } 239 240 if err := generateCoverHTML(w, mgr.cfg.KernelObj, mgr.cfg.KernelSrc, mgr.cfg.TargetVMArch, cov); err != nil { 241 http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) 242 return 243 } 244 runtime.GC() 245} 246 247func (mgr *Manager) httpCoverFallback(w http.ResponseWriter, r *http.Request) { 248 calls := make(map[int][]int) 249 for s := range mgr.maxSignal { 250 id, errno := prog.DecodeFallbackSignal(uint32(s)) 251 calls[id] = append(calls[id], errno) 252 } 253 data := &UIFallbackCoverData{} 254 for _, id := range mgr.checkResult.EnabledCalls[mgr.cfg.Sandbox] { 255 errnos := calls[id] 256 sort.Ints(errnos) 257 successful := 0 258 for len(errnos) != 0 && errnos[0] == 0 { 259 successful++ 260 errnos = errnos[1:] 261 } 262 data.Calls = append(data.Calls, UIFallbackCall{ 263 Name: mgr.target.Syscalls[id].Name, 264 Successful: successful, 265 Errnos: errnos, 266 }) 267 } 268 sort.Slice(data.Calls, func(i, j int) bool { 269 return data.Calls[i].Name < data.Calls[j].Name 270 }) 271 272 if err := fallbackCoverTemplate.Execute(w, data); err != nil { 273 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) 274 return 275 } 276} 277 278func (mgr *Manager) httpPrio(w http.ResponseWriter, r *http.Request) { 279 mgr.mu.Lock() 280 defer mgr.mu.Unlock() 281 282 mgr.minimizeCorpus() 283 call := r.FormValue("call") 284 idx := -1 285 for i, c := range mgr.target.Syscalls { 286 if c.CallName == call { 287 idx = i 288 break 289 } 290 } 291 if idx == -1 { 292 http.Error(w, fmt.Sprintf("unknown call: %v", call), http.StatusInternalServerError) 293 return 294 } 295 296 data := &UIPrioData{Call: call} 297 for i, p := range mgr.prios[idx] { 298 data.Prios = append(data.Prios, UIPrio{mgr.target.Syscalls[i].Name, p}) 299 } 300 sort.Sort(UIPrioArray(data.Prios)) 301 302 if err := prioTemplate.Execute(w, data); err != nil { 303 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) 304 return 305 } 306} 307 308func (mgr *Manager) httpFile(w http.ResponseWriter, r *http.Request) { 309 file := filepath.Clean(r.FormValue("name")) 310 if !strings.HasPrefix(file, "crashes/") && !strings.HasPrefix(file, "corpus/") { 311 http.Error(w, "oh, oh, oh!", http.StatusInternalServerError) 312 return 313 } 314 file = filepath.Join(mgr.cfg.Workdir, file) 315 f, err := os.Open(file) 316 if err != nil { 317 http.Error(w, "failed to open the file", http.StatusInternalServerError) 318 return 319 } 320 defer f.Close() 321 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 322 io.Copy(w, f) 323} 324 325func (mgr *Manager) httpReport(w http.ResponseWriter, r *http.Request) { 326 mgr.mu.Lock() 327 defer mgr.mu.Unlock() 328 329 crashID := r.FormValue("id") 330 desc, err := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "description")) 331 if err != nil { 332 http.Error(w, "failed to read description file", http.StatusInternalServerError) 333 return 334 } 335 tag, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.tag")) 336 prog, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.prog")) 337 cprog, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.cprog")) 338 rep, _ := ioutil.ReadFile(filepath.Join(mgr.crashdir, crashID, "repro.report")) 339 340 commitDesc := "" 341 if len(tag) != 0 { 342 commitDesc = fmt.Sprintf(" on commit %s.", trimNewLines(tag)) 343 } 344 fmt.Fprintf(w, "Syzkaller hit '%s' bug%s.\n\n", trimNewLines(desc), commitDesc) 345 if len(rep) != 0 { 346 fmt.Fprintf(w, "%s\n\n", rep) 347 } 348 if len(prog) == 0 && len(cprog) == 0 { 349 fmt.Fprintf(w, "The bug is not reproducible.\n") 350 } else { 351 fmt.Fprintf(w, "Syzkaller reproducer:\n%s\n\n", prog) 352 if len(cprog) != 0 { 353 fmt.Fprintf(w, "C reproducer:\n%s\n\n", cprog) 354 } 355 } 356} 357 358func (mgr *Manager) httpRawCover(w http.ResponseWriter, r *http.Request) { 359 mgr.mu.Lock() 360 defer mgr.mu.Unlock() 361 362 initCoverOnce.Do(func() { initCoverError = initCover(mgr.cfg.KernelObj, mgr.cfg.TargetArch) }) 363 if initCoverError != nil { 364 http.Error(w, initCoverError.Error(), http.StatusInternalServerError) 365 return 366 } 367 368 var cov cover.Cover 369 for _, inp := range mgr.corpus { 370 cov.Merge(inp.Cover) 371 } 372 pcs := make([]uint64, 0, len(cov)) 373 for pc := range cov { 374 fullPC := cover.RestorePC(pc, initCoverVMOffset) 375 prevPC := previousInstructionPC(mgr.cfg.TargetVMArch, fullPC) 376 pcs = append(pcs, prevPC) 377 } 378 sort.Slice(pcs, func(i, j int) bool { 379 return pcs[i] < pcs[j] 380 }) 381 382 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 383 buf := bufio.NewWriter(w) 384 for _, pc := range pcs { 385 fmt.Fprintf(buf, "0x%x\n", pc) 386 } 387 buf.Flush() 388} 389 390func (mgr *Manager) collectCrashes(workdir string) ([]*UICrashType, error) { 391 // Note: mu is not locked here. 392 reproReply := make(chan map[string]bool) 393 mgr.reproRequest <- reproReply 394 repros := <-reproReply 395 396 crashdir := filepath.Join(workdir, "crashes") 397 dirs, err := osutil.ListDir(crashdir) 398 if err != nil { 399 return nil, err 400 } 401 var crashTypes []*UICrashType 402 for _, dir := range dirs { 403 crash := readCrash(workdir, dir, repros, false) 404 if crash != nil { 405 crashTypes = append(crashTypes, crash) 406 } 407 } 408 sort.Sort(UICrashTypeArray(crashTypes)) 409 return crashTypes, nil 410} 411 412func readCrash(workdir, dir string, repros map[string]bool, full bool) *UICrashType { 413 if len(dir) != 40 { 414 return nil 415 } 416 crashdir := filepath.Join(workdir, "crashes") 417 descFile, err := os.Open(filepath.Join(crashdir, dir, "description")) 418 if err != nil { 419 return nil 420 } 421 defer descFile.Close() 422 descBytes, err := ioutil.ReadAll(descFile) 423 if err != nil || len(descBytes) == 0 { 424 return nil 425 } 426 desc := string(trimNewLines(descBytes)) 427 stat, err := descFile.Stat() 428 if err != nil { 429 return nil 430 } 431 modTime := stat.ModTime() 432 descFile.Close() 433 434 files, err := osutil.ListDir(filepath.Join(crashdir, dir)) 435 if err != nil { 436 return nil 437 } 438 var crashes []*UICrash 439 reproAttempts := 0 440 hasRepro, hasCRepro := false, false 441 reports := make(map[string]bool) 442 for _, f := range files { 443 if strings.HasPrefix(f, "log") { 444 index, err := strconv.ParseUint(f[3:], 10, 64) 445 if err == nil { 446 crashes = append(crashes, &UICrash{ 447 Index: int(index), 448 }) 449 } 450 } else if strings.HasPrefix(f, "report") { 451 reports[f] = true 452 } else if f == "repro.prog" { 453 hasRepro = true 454 } else if f == "repro.cprog" { 455 hasCRepro = true 456 } else if f == "repro.report" { 457 } else if f == "repro0" || f == "repro1" || f == "repro2" { 458 reproAttempts++ 459 } 460 } 461 462 if full { 463 for _, crash := range crashes { 464 index := strconv.Itoa(crash.Index) 465 crash.Log = filepath.Join("crashes", dir, "log"+index) 466 if stat, err := os.Stat(filepath.Join(workdir, crash.Log)); err == nil { 467 crash.Time = stat.ModTime() 468 crash.TimeStr = crash.Time.Format(dateFormat) 469 } 470 tag, _ := ioutil.ReadFile(filepath.Join(crashdir, dir, "tag"+index)) 471 crash.Tag = string(tag) 472 reportFile := filepath.Join("crashes", dir, "report"+index) 473 if osutil.IsExist(filepath.Join(workdir, reportFile)) { 474 crash.Report = reportFile 475 } 476 } 477 sort.Sort(UICrashArray(crashes)) 478 } 479 480 triaged := reproStatus(hasRepro, hasCRepro, repros[desc], reproAttempts >= maxReproAttempts) 481 return &UICrashType{ 482 Description: desc, 483 LastTime: modTime.Format(dateFormat), 484 ID: dir, 485 Count: len(crashes), 486 Triaged: triaged, 487 Crashes: crashes, 488 } 489} 490 491func reproStatus(hasRepro, hasCRepro, reproducing, nonReproducible bool) string { 492 status := "" 493 if hasRepro { 494 status = "has repro" 495 if hasCRepro { 496 status = "has C repro" 497 } 498 } else if reproducing { 499 status = "reproducing" 500 } else if nonReproducible { 501 status = "non-reproducible" 502 } 503 return status 504} 505 506func trimNewLines(data []byte) []byte { 507 for len(data) > 0 && data[len(data)-1] == '\n' { 508 data = data[:len(data)-1] 509 } 510 return data 511} 512 513type UISummaryData struct { 514 Name string 515 Stats []UIStat 516 Crashes []*UICrashType 517 Log string 518} 519 520type UISyscallsData struct { 521 Name string 522 Calls []UICallType 523} 524 525type UICrashType struct { 526 Description string 527 LastTime string 528 ID string 529 Count int 530 Triaged string 531 Crashes []*UICrash 532} 533 534type UICrash struct { 535 Index int 536 Time time.Time 537 TimeStr string 538 Log string 539 Report string 540 Tag string 541} 542 543type UIStat struct { 544 Name string 545 Value string 546 Link string 547} 548 549type UICallType struct { 550 Name string 551 Inputs int 552 Cover int 553} 554 555type UIInput struct { 556 Short string 557 Full string 558 Calls int 559 Cover int 560 Sig string 561} 562 563type UICallTypeArray []UICallType 564 565func (a UICallTypeArray) Len() int { return len(a) } 566func (a UICallTypeArray) Less(i, j int) bool { return a[i].Name < a[j].Name } 567func (a UICallTypeArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 568 569type UIInputArray []UIInput 570 571func (a UIInputArray) Len() int { return len(a) } 572func (a UIInputArray) Less(i, j int) bool { return a[i].Cover > a[j].Cover } 573func (a UIInputArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 574 575type UIStatArray []UIStat 576 577func (a UIStatArray) Len() int { return len(a) } 578func (a UIStatArray) Less(i, j int) bool { return a[i].Name < a[j].Name } 579func (a UIStatArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 580 581type UICrashTypeArray []*UICrashType 582 583func (a UICrashTypeArray) Len() int { return len(a) } 584func (a UICrashTypeArray) Less(i, j int) bool { return a[i].Description < a[j].Description } 585func (a UICrashTypeArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 586 587type UICrashArray []*UICrash 588 589func (a UICrashArray) Len() int { return len(a) } 590func (a UICrashArray) Less(i, j int) bool { return a[i].Time.After(a[j].Time) } 591func (a UICrashArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 592 593var summaryTemplate = template.Must(template.New("").Parse(addStyle(` 594<!doctype html> 595<html> 596<head> 597 <title>{{.Name }} syzkaller</title> 598 {{STYLE}} 599</head> 600<body> 601<b>{{.Name }} syzkaller</b> 602<br> 603<br> 604 605<table> 606 <caption>Stats:</caption> 607 {{range $s := $.Stats}} 608 <tr> 609 <td>{{$s.Name}}</td> 610 {{if $s.Link}} 611 <td><a href="{{$s.Link}}">{{$s.Value}}</a></td> 612 {{else}} 613 <td>{{$s.Value}}</td> 614 {{end}} 615 </tr> 616 {{end}} 617</table> 618<br> 619 620<table> 621 <caption>Crashes:</caption> 622 <tr> 623 <th>Description</th> 624 <th>Count</th> 625 <th>Last Time</th> 626 <th>Report</th> 627 </tr> 628 {{range $c := $.Crashes}} 629 <tr> 630 <td><a href="/crash?id={{$c.ID}}">{{$c.Description}}</a></td> 631 <td>{{$c.Count}}</td> 632 <td>{{$c.LastTime}}</td> 633 <td> 634 {{if $c.Triaged}} 635 <a href="/report?id={{$c.ID}}">{{$c.Triaged}}</a> 636 {{end}} 637 </td> 638 </tr> 639 {{end}} 640</table> 641<br> 642 643<b>Log:</b> 644<br> 645<textarea id="log_textarea" readonly rows="20"> 646{{.Log}} 647</textarea> 648<script> 649 var textarea = document.getElementById("log_textarea"); 650 textarea.scrollTop = textarea.scrollHeight; 651</script> 652</body></html> 653`))) 654 655var syscallsTemplate = template.Must(template.New("").Parse(addStyle(` 656<!doctype html> 657<html> 658<head> 659 <title>{{.Name }} syzkaller</title> 660 {{STYLE}} 661</head> 662<body> 663<b>Per-call coverage:</b> 664<br> 665{{range $c := $.Calls}} 666 {{$c.Name}} 667 <a href='/corpus?call={{$c.Name}}'>inputs:{{$c.Inputs}}</a> 668 <a href='/cover?call={{$c.Name}}'>cover:{{$c.Cover}}</a> 669 <a href='/prio?call={{$c.Name}}'>prio</a> <br> 670{{end}} 671</body></html> 672`))) 673 674var crashTemplate = template.Must(template.New("").Parse(addStyle(` 675<!doctype html> 676<html> 677<head> 678 <title>{{.Description}}</title> 679 {{STYLE}} 680</head> 681<body> 682<b>{{.Description}}</b> 683<br><br> 684 685{{if .Triaged}} 686Report: <a href="/report?id={{.ID}}">{{.Triaged}}</a> 687{{end}} 688<br><br> 689 690<table> 691 <tr> 692 <th>#</th> 693 <th>Log</th> 694 <th>Report</th> 695 <th>Time</th> 696 <th>Tag</th> 697 </tr> 698 {{range $c := $.Crashes}} 699 <tr> 700 <td>{{$c.Index}}</td> 701 <td><a href="/file?name={{$c.Log}}">log</a></td> 702 {{if $c.Report}} 703 <td><a href="/file?name={{$c.Report}}">report</a></td> 704 {{else}} 705 <td></td> 706 {{end}} 707 <td>{{$c.TimeStr}}</td> 708 <td>{{$c.Tag}}</td> 709 </tr> 710 {{end}} 711</table> 712</body></html> 713`))) 714 715var corpusTemplate = template.Must(template.New("").Parse(addStyle(` 716<!doctype html> 717<html> 718<head> 719 <title>syzkaller corpus</title> 720 {{STYLE}} 721</head> 722<body> 723{{range $c := $}} 724 <span title="{{$c.Full}}">{{$c.Short}}</span> 725 <a href='/cover?input={{$c.Sig}}'>cover:{{$c.Cover}}</a> 726 <br> 727{{end}} 728</body></html> 729`))) 730 731type UIPrioData struct { 732 Call string 733 Prios []UIPrio 734} 735 736type UIPrio struct { 737 Call string 738 Prio float32 739} 740 741type UIPrioArray []UIPrio 742 743func (a UIPrioArray) Len() int { return len(a) } 744func (a UIPrioArray) Less(i, j int) bool { return a[i].Prio > a[j].Prio } 745func (a UIPrioArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 746 747var prioTemplate = template.Must(template.New("").Parse(addStyle(` 748<!doctype html> 749<html> 750<head> 751 <title>syzkaller priorities</title> 752 {{STYLE}} 753</head> 754<body> 755Priorities for {{$.Call}} <br> <br> 756{{range $p := $.Prios}} 757 {{printf "%.4f\t%s" $p.Prio $p.Call}} <br> 758{{end}} 759</body></html> 760`))) 761 762type UIFallbackCoverData struct { 763 Calls []UIFallbackCall 764} 765 766type UIFallbackCall struct { 767 Name string 768 Successful int 769 Errnos []int 770} 771 772var fallbackCoverTemplate = template.Must(template.New("").Parse(addStyle(` 773<!doctype html> 774<html> 775<head> 776 <title>syzkaller coverage</title> 777 {{STYLE}} 778</head> 779<body> 780<table> 781 <tr> 782 <th>Call</th> 783 <th>Successful</th> 784 <th>Errnos</th> 785 </tr> 786 {{range $c := $.Calls}} 787 <tr> 788 <td>{{$c.Name}}</td> 789 <td>{{if $c.Successful}}{{$c.Successful}}{{end}}</td> 790 <td>{{range $e := $c.Errnos}}{{$e}} {{end}}</td> 791 </tr> 792 {{end}} 793</table> 794</body></html> 795`))) 796 797func addStyle(html string) string { 798 return strings.Replace(html, "{{STYLE}}", htmlStyle, -1) 799} 800 801const htmlStyle = ` 802 <style type="text/css" media="screen"> 803 table { 804 border-collapse:collapse; 805 border:1px solid; 806 } 807 table caption { 808 font-weight: bold; 809 } 810 table td { 811 border:1px solid; 812 padding: 3px; 813 } 814 table th { 815 border:1px solid; 816 padding: 3px; 817 } 818 textarea { 819 width:100%; 820 } 821 </style> 822` 823