diff options
| -rw-r--r-- | pkg/cover/backend/dwarf.go | 24 | ||||
| -rw-r--r-- | pkg/cover/backend/elf.go | 14 | ||||
| -rw-r--r-- | pkg/cover/html.go | 100 | ||||
| -rw-r--r-- | pkg/cover/report_test.go | 8 | ||||
| -rw-r--r-- | syz-manager/html.go | 105 | ||||
| -rw-r--r-- | tools/syz-cover/syz-cover.go | 4 |
6 files changed, 153 insertions, 102 deletions
diff --git a/pkg/cover/backend/dwarf.go b/pkg/cover/backend/dwarf.go index 23a1e8ce9..7a9abd221 100644 --- a/pkg/cover/backend/dwarf.go +++ b/pkg/cover/backend/dwarf.go @@ -7,6 +7,7 @@ import ( "bufio" "bytes" "debug/dwarf" + "debug/elf" "encoding/binary" "fmt" "io/ioutil" @@ -30,25 +31,30 @@ type containerFns struct { } type Arch struct { - callLen int - opcodeOffset int - opcodes [2]byte - target func(arch *Arch, insn []byte, pc uint64, opcode byte) uint64 + callLen int + relaOffset uint64 + opcodeOffset int + opcodes [2]byte + callRelocType uint64 + target func(arch *Arch, insn []byte, pc uint64, opcode byte) uint64 } var arches = map[string]Arch{ targets.AMD64: { - callLen: 5, - opcodes: [2]byte{0xe8, 0xe8}, + callLen: 5, + relaOffset: 1, + opcodes: [2]byte{0xe8, 0xe8}, + callRelocType: uint64(elf.R_X86_64_PLT32), target: func(arch *Arch, insn []byte, pc uint64, opcode byte) uint64 { off := uint64(int64(int32(binary.LittleEndian.Uint32(insn[1:])))) return pc + off + uint64(arch.callLen) }, }, targets.ARM64: { - callLen: 4, - opcodeOffset: 3, - opcodes: [2]byte{0x94, 0x97}, + callLen: 4, + opcodeOffset: 3, + opcodes: [2]byte{0x94, 0x97}, + callRelocType: uint64(elf.R_AARCH64_CALL26), target: func(arch *Arch, insn []byte, pc uint64, opcode byte) uint64 { off := uint64(binary.LittleEndian.Uint32(insn)) & ((1 << 24) - 1) if opcode == arch.opcodes[1] { diff --git a/pkg/cover/backend/elf.go b/pkg/cover/backend/elf.go index 94e6fdd8c..227356eef 100644 --- a/pkg/cover/backend/elf.go +++ b/pkg/cover/backend/elf.go @@ -40,7 +40,9 @@ func elfReadSymbols(module *Module, info *symbolInfo) ([]*Symbol, error) { if err != nil { return nil, fmt.Errorf("failed to read ELF symbols: %v", err) } - info.textAddr = text.Addr + if module.Name == "" { + info.textAddr = text.Addr + } var symbols []*Symbol for i, symb := range allSymbols { text := symb.Value >= text.Addr && symb.Value+symb.Size <= text.Addr+text.Size @@ -133,7 +135,8 @@ func elfReadModuleCoverPoints(target *targets.Target, module *Module, info *symb if err != nil { return pcs, err } - offset := uint64(arches[target.Arch].opcodeOffset) + callRelocType := arches[target.Arch].callRelocType + relaOffset := arches[target.Arch].relaOffset for _, s := range file.Sections { if s.Type != elf.SHT_RELA { // nolint: misspell continue @@ -146,10 +149,11 @@ func elfReadModuleCoverPoints(target *targets.Target, module *Module, info *symb } return pcs, err } - // Note: this assumes that call instruction is 1 byte. - pc := module.Addr + rel.Off - 1 + if (rel.Info & 0xffffffff) != callRelocType { + continue + } + pc := module.Addr + rel.Off - relaOffset index := int(elf.R_SYM64(rel.Info)) - 1 - pc -= offset if info.tracePCIdx[index] { pcs[0] = append(pcs[0], pc) } else if info.traceCmpIdx[index] { diff --git a/pkg/cover/html.go b/pkg/cover/html.go index 63b1506c1..7afe8b54c 100644 --- a/pkg/cover/html.go +++ b/pkg/cover/html.go @@ -21,9 +21,43 @@ import ( "github.com/google/syzkaller/pkg/cover/backend" "github.com/google/syzkaller/pkg/mgrconfig" + "github.com/google/syzkaller/sys/targets" ) -func (rg *ReportGenerator) DoHTML(w io.Writer, progs []Prog) error { +func fixUpPCs(target string, progs []Prog, coverFilter map[uint32]uint32) []Prog { + if coverFilter != nil { + for _, prog := range progs { + var nPCs []uint64 + for _, pc := range prog.PCs { + if coverFilter[uint32(pc)] != 0 { + nPCs = append(nPCs, pc) + } + } + prog.PCs = nPCs + } + } + + // On arm64 as PLT is enabled by default, .text section is loaded after .plt section, + // so there is 0x18 bytes offset from module load address for .text section + // we need to remove the 0x18 bytes offset in order to correct module symbol address + if target == targets.ARM64 { + for _, prog := range progs { + var nPCs []uint64 + for _, pc := range prog.PCs { + // TODO: avoid to hardcode the address + if pc < 0xffffffd010000000 { + pc -= 0x18 + } + nPCs = append(nPCs, pc) + } + prog.PCs = nPCs + } + } + return progs +} + +func (rg *ReportGenerator) DoHTML(w io.Writer, progs []Prog, coverFilter map[uint32]uint32) error { + progs = fixUpPCs(rg.target.Arch, progs, coverFilter) files, err := rg.prepareFileMap(progs) if err != nil { return err @@ -106,7 +140,8 @@ func (rg *ReportGenerator) DoHTML(w io.Writer, progs []Prog) error { return coverTemplate.Execute(w, d) } -func (rg *ReportGenerator) DoRawCoverFiles(w http.ResponseWriter, progs []Prog) error { +func (rg *ReportGenerator) DoRawCoverFiles(w http.ResponseWriter, progs []Prog, coverFilter map[uint32]uint32) error { + progs = fixUpPCs(rg.target.Arch, progs, coverFilter) if err := rg.lazySymbolize(progs); err != nil { return err } @@ -125,6 +160,58 @@ func (rg *ReportGenerator) DoRawCoverFiles(w http.ResponseWriter, progs []Prog) return nil } +func (rg *ReportGenerator) DoRawCover(w http.ResponseWriter, progs []Prog, coverFilter map[uint32]uint32) { + progs = fixUpPCs(rg.target.Arch, progs, coverFilter) + var pcs []uint64 + uniquePCs := make(map[uint64]bool) + for _, prog := range progs { + for _, pc := range prog.PCs { + if uniquePCs[pc] { + continue + } + uniquePCs[pc] = true + pcs = append(pcs, pc) + } + } + sort.Slice(pcs, func(i, j int) bool { + return pcs[i] < pcs[j] + }) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + buf := bufio.NewWriter(w) + for _, pc := range pcs { + fmt.Fprintf(buf, "0x%x\n", pc) + } + buf.Flush() +} + +func (rg *ReportGenerator) DoFilterPCs(w http.ResponseWriter, progs []Prog, coverFilter map[uint32]uint32) { + progs = fixUpPCs(rg.target.Arch, progs, coverFilter) + var pcs []uint64 + uniquePCs := make(map[uint64]bool) + for _, prog := range progs { + for _, pc := range prog.PCs { + if uniquePCs[pc] { + continue + } + uniquePCs[pc] = true + if coverFilter[uint32(pc)] != 0 { + pcs = append(pcs, pc) + } + } + } + sort.Slice(pcs, func(i, j int) bool { + return pcs[i] < pcs[j] + }) + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + buf := bufio.NewWriter(w) + for _, pc := range pcs { + fmt.Fprintf(buf, "0x%x\n", pc) + } + buf.Flush() +} + type fileStats struct { Name string CoveredLines int @@ -200,7 +287,8 @@ func (rg *ReportGenerator) convertToStats(progs []Prog) ([]fileStats, error) { return data, nil } -func (rg *ReportGenerator) DoCSVFiles(w io.Writer, progs []Prog) error { +func (rg *ReportGenerator) DoCSVFiles(w io.Writer, progs []Prog, coverFilter map[uint32]uint32) error { + progs = fixUpPCs(rg.target.Arch, progs, coverFilter) data, err := rg.convertToStats(progs) if err != nil { return err @@ -298,7 +386,8 @@ func groupCoverByFilePrefixes(datas []fileStats, subsystems []mgrconfig.Subsyste return d } -func (rg *ReportGenerator) DoHTMLTable(w io.Writer, progs []Prog) error { +func (rg *ReportGenerator) DoHTMLTable(w io.Writer, progs []Prog, coverFilter map[uint32]uint32) error { + progs = fixUpPCs(rg.target.Arch, progs, coverFilter) data, err := rg.convertToStats(progs) if err != nil { return err @@ -316,7 +405,8 @@ var csvHeader = []string{ "Total PCs", } -func (rg *ReportGenerator) DoCSV(w io.Writer, progs []Prog) error { +func (rg *ReportGenerator) DoCSV(w io.Writer, progs []Prog, coverFilter map[uint32]uint32) error { + progs = fixUpPCs(rg.target.Arch, progs, coverFilter) files, err := rg.prepareFileMap(progs) if err != nil { return err diff --git a/pkg/cover/report_test.go b/pkg/cover/report_test.go index 9b6b9e56c..3461fcf58 100644 --- a/pkg/cover/report_test.go +++ b/pkg/cover/report_test.go @@ -224,20 +224,20 @@ func generateReport(t *testing.T, target *targets.Target, test Test) ([]byte, [] test.Progs = append(test.Progs, Prog{Data: "main", PCs: pcs}) } html := new(bytes.Buffer) - if err := rg.DoHTML(html, test.Progs); err != nil { + if err := rg.DoHTML(html, test.Progs, nil); err != nil { return nil, nil, err } htmlTable := new(bytes.Buffer) - if err := rg.DoHTMLTable(htmlTable, test.Progs); err != nil { + if err := rg.DoHTMLTable(htmlTable, test.Progs, nil); err != nil { return nil, nil, err } _ = htmlTable csv := new(bytes.Buffer) - if err := rg.DoCSV(csv, test.Progs); err != nil { + if err := rg.DoCSV(csv, test.Progs, nil); err != nil { return nil, nil, err } csvFiles := new(bytes.Buffer) - if err := rg.DoCSVFiles(csvFiles, test.Progs); err != nil { + if err := rg.DoCSVFiles(csvFiles, test.Progs, nil); err != nil { return nil, nil, err } _ = csvFiles diff --git a/syz-manager/html.go b/syz-manager/html.go index 26b611aa1..46c062752 100644 --- a/syz-manager/html.go +++ b/syz-manager/html.go @@ -4,7 +4,6 @@ package main import ( - "bufio" "bytes" "encoding/json" "fmt" @@ -242,6 +241,8 @@ const ( DoCSV DoCSVFiles DoRawCoverFiles + DoRawCover + DoFilterPCs ) func (mgr *Manager) httpCover(w http.ResponseWriter, r *http.Request) { @@ -279,23 +280,12 @@ func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request, funcF } mgr.mu.Lock() - convert := coverToPCs - if r.FormValue("filter") != "" && mgr.coverFilter != nil { - convert = func(rg *cover.ReportGenerator, cover []uint32) (ret []uint64) { - for _, pc := range coverToPCs(rg, cover) { - if mgr.coverFilter[uint32(pc)] != 0 { - ret = append(ret, pc) - } - } - return ret - } - } var progs []cover.Prog if sig := r.FormValue("input"); sig != "" { inp := mgr.corpus[sig] progs = append(progs, cover.Prog{ Data: string(inp.Prog), - PCs: convert(rg, inp.Cover), + PCs: coverToPCs(rg, inp.Cover), }) } else { call := r.FormValue("call") @@ -305,12 +295,32 @@ func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request, funcF } progs = append(progs, cover.Prog{ Data: string(inp.Prog), - PCs: convert(rg, inp.Cover), + PCs: coverToPCs(rg, inp.Cover), }) } } mgr.mu.Unlock() + var coverFilter map[uint32]uint32 + if r.FormValue("filter") != "" { + coverFilter = mgr.coverFilter + } + + if funcFlag == DoRawCoverFiles { + if err := rg.DoRawCoverFiles(w, progs, coverFilter); err != nil { + http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) + return + } + runtime.GC() + return + } else if funcFlag == DoRawCover { + rg.DoRawCover(w, progs, coverFilter) + return + } else if funcFlag == DoFilterPCs { + rg.DoFilterPCs(w, progs, coverFilter) + return + } + do := rg.DoHTML if funcFlag == DoHTMLTable { do = rg.DoHTMLTable @@ -318,15 +328,9 @@ func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request, funcF do = rg.DoCSV } else if funcFlag == DoCSVFiles { do = rg.DoCSVFiles - } else if funcFlag == DoRawCoverFiles { - if err := rg.DoRawCoverFiles(w, progs); err != nil { - http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) - return - } - runtime.GC() - return } - if err := do(w, progs); err != nil { + + if err := do(w, progs, coverFilter); err != nil { http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) return } @@ -469,31 +473,7 @@ func (mgr *Manager) httpReport(w http.ResponseWriter, r *http.Request) { } func (mgr *Manager) httpRawCover(w http.ResponseWriter, r *http.Request) { - // Note: initCover is executed without mgr.mu because it takes very long time - // (but it only reads config and it protected by initCoverOnce). - rg, err := getReportGenerator(mgr.cfg, mgr.modules) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - mgr.mu.Lock() - defer mgr.mu.Unlock() - - var cov cover.Cover - for _, inp := range mgr.corpus { - cov.Merge(inp.Cover) - } - pcs := coverToPCs(rg, cov.Serialize()) - sort.Slice(pcs, func(i, j int) bool { - return pcs[i] < pcs[j] - }) - - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - buf := bufio.NewWriter(w) - for _, pc := range pcs { - fmt.Fprintf(buf, "0x%x\n", pc) - } - buf.Flush() + mgr.httpCoverCover(w, r, DoRawCover, false) } func (mgr *Manager) httpRawCoverFiles(w http.ResponseWriter, r *http.Request) { @@ -505,36 +485,7 @@ func (mgr *Manager) httpFilterPCs(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "cover is not filtered in config.\n") return } - // Note: initCover is executed without mgr.mu because it takes very long time - // (but it only reads config and it protected by initCoverOnce). - rg, err := getReportGenerator(mgr.cfg, mgr.modules) - if err != nil { - http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) - return - } - mgr.mu.Lock() - defer mgr.mu.Unlock() - - var cov cover.Cover - for _, inp := range mgr.corpus { - cov.Merge(inp.Cover) - } - pcs := make([]uint64, 0, len(cov)) - for _, pc := range coverToPCs(rg, cov.Serialize()) { - if mgr.coverFilter[uint32(pc)] != 0 { - pcs = append(pcs, pc) - } - } - sort.Slice(pcs, func(i, j int) bool { - return pcs[i] < pcs[j] - }) - - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - buf := bufio.NewWriter(w) - for _, pc := range pcs { - fmt.Fprintf(buf, "0x%x\n", pc) - } - buf.Flush() + mgr.httpCoverCover(w, r, DoFilterPCs, false) } func (mgr *Manager) collectCrashes(workdir string) ([]*UICrashType, error) { diff --git a/tools/syz-cover/syz-cover.go b/tools/syz-cover/syz-cover.go index 5c7a27ecb..42e592bd9 100644 --- a/tools/syz-cover/syz-cover.go +++ b/tools/syz-cover/syz-cover.go @@ -75,7 +75,7 @@ func main() { progs := []cover.Prog{{PCs: pcs}} buf := new(bytes.Buffer) if *flagExport != "" { - if err := rg.DoCSV(buf, progs); err != nil { + if err := rg.DoCSV(buf, progs, nil); err != nil { tool.Fail(err) } if err := osutil.WriteFile(*flagExport, buf.Bytes()); err != nil { @@ -83,7 +83,7 @@ func main() { } return } - if err := rg.DoHTML(buf, progs); err != nil { + if err := rg.DoHTML(buf, progs, nil); err != nil { tool.Fail(err) } fn, err := osutil.TempFile("syz-cover") |
