From 5e933e8c7b82e170b667885d3b99098e2e86f29d Mon Sep 17 00:00:00 2001 From: Joey Jiao Date: Tue, 2 Mar 2021 09:31:32 +0800 Subject: all: support coverage of kernel modules The PCs returned for dynamic loaded module (DLKM) is not parsed in coverage page, these PCs are dropped. The commit is to use DLKM modules' load address and symbol file to restore the PC and show coverage data of DLKM. Introduced new config module_obj to specify module directories. Example of config: "module_obj": [ "module_path1" "module_path2" ] For linux target, before Manager.Connect run, load addresses are getting from /proc/modules in order to group PCs into modules. And so, if modules are under kernel_obj or module_obj dir, their addresses and paths can be generated automatically. kernel_obj is searched before module_obj dir and the first found ko object is always used. Also note that kaslr needs to be disabled. --- pkg/cover/backend/backend.go | 29 ++- pkg/cover/backend/elf.go | 429 ++++++++++++++++++++++++++++++------ pkg/cover/backend/gvisor.go | 11 +- pkg/cover/report.go | 29 ++- pkg/cover/report_test.go | 2 +- pkg/host/machine_info.go | 10 + pkg/host/machine_info_linux.go | 20 ++ pkg/host/machine_info_linux_test.go | 8 + pkg/mgrconfig/config.go | 6 +- pkg/rpctype/rpctype.go | 17 +- 10 files changed, 463 insertions(+), 98 deletions(-) (limited to 'pkg') diff --git a/pkg/cover/backend/backend.go b/pkg/cover/backend/backend.go index 22823021d..0b28cbab2 100644 --- a/pkg/cover/backend/backend.go +++ b/pkg/cover/backend/backend.go @@ -9,12 +9,19 @@ import ( "github.com/google/syzkaller/sys/targets" ) +type KernelModule struct { + Name string + Path string + Addr uint64 +} + type Impl struct { Units []*CompileUnit Symbols []*Symbol Frames []Frame - Symbolize func(pcs []uint64) ([]Frame, error) + Symbolize func(pcs []uint64, obj string) ([]Frame, error) RestorePC func(pc uint32) uint64 + Modules map[string]KernelModule } type CompileUnit struct { @@ -38,9 +45,10 @@ type ObjectUnit struct { } type Frame struct { - PC uint64 - Name string - Path string + Module string + PC uint64 + Name string + Path string Range } @@ -53,12 +61,19 @@ type Range struct { const LineEnd = 1 << 30 -func Make(target *targets.Target, vm, objDir, srcDir, buildDir string) (*Impl, error) { +func Make(target *targets.Target, vm, objDir, srcDir, buildDir string, + moduleObj []string, modules map[string]KernelModule) (*Impl, error) { if objDir == "" { return nil, fmt.Errorf("kernel obj directory is not specified") } if vm == "gvisor" { - return makeGvisor(target, objDir, srcDir, buildDir) + return makeGvisor(target, objDir, srcDir, buildDir, modules) } - return makeELF(target, objDir, srcDir, buildDir) + return makeELF(target, objDir, srcDir, buildDir, moduleObj, modules) +} + +var GroupPCsByModule = func(pcs []uint64, modules map[string]KernelModule) map[string][]uint64 { + groupPCs := make(map[string][]uint64) + groupPCs[""] = pcs + return groupPCs } diff --git a/pkg/cover/backend/elf.go b/pkg/cover/backend/elf.go index eba6fd7ca..635bfdc12 100644 --- a/pkg/cover/backend/elf.go +++ b/pkg/cover/backend/elf.go @@ -11,84 +11,293 @@ import ( "encoding/binary" "fmt" "io/ioutil" + "os" "path/filepath" "runtime" "sort" "strconv" "strings" + "unsafe" + "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/pkg/symbolizer" "github.com/google/syzkaller/sys/targets" ) -func makeELF(target *targets.Target, objDir, srcDir, buildDir string) (*Impl, error) { +func init() { + GroupPCsByModule = groupPCsByModule +} + +func makeELF(target *targets.Target, objDir, srcDir, buildDir string, + moduleObj []string, modules map[string]KernelModule) (*Impl, error) { + var allCoverPoints [2][]uint64 + var allSymbols []*Symbol + var allRanges []pcRange + var allUnits []*CompileUnit + + if _, ok := modules[""]; !ok { + if target.OS == targets.Linux { + dirs := append([]string{objDir}, moduleObj...) + getKernelModules(dirs, modules) + } + } kernelObject := filepath.Join(objDir, target.KernelObject) - file, err := elf.Open(kernelObject) - if err != nil { - return nil, err + modules[""] = KernelModule{ + Path: kernelObject, } - // Here and below index 0 refers to coverage callbacks (__sanitizer_cov_trace_pc) - // and index 1 refers to comparison callbacks (__sanitizer_cov_trace_cmp*). - var coverPoints [2][]uint64 - var symbols []*Symbol - var textAddr uint64 - errc := make(chan error, 1) - go func() { - symbols1, textAddr1, tracePC, traceCmp, err := readSymbols(file) + + for _, module := range modules { + if module.Path == "" { + continue + } + file, err := elf.Open(module.Path) if err != nil { + return nil, err + } + + // Here and below index 0 refers to coverage callbacks (__sanitizer_cov_trace_pc) + // and index 1 refers to comparison callbacks (__sanitizer_cov_trace_cmp*). + var coverPoints [2][]uint64 + var symbols []*Symbol + var textAddr uint64 + errc := make(chan error, 1) + go func() { + symbols1, textAddr1, tracePC, traceCmp, err := readSymbols(file, module) + if err != nil { + errc <- err + return + } + symbols, textAddr = symbols1, textAddr1 + if target.OS == targets.FreeBSD { + // On FreeBSD .text address in ELF is 0, but .text is actually mapped at 0xffffffff. + textAddr = ^uint64(0) + } + if module.Name == "" { + modules[""] = KernelModule{ + Name: "", + Addr: textAddr, + Path: kernelObject, + } + } + if target.Arch == targets.AMD64 { + if module.Name == "" { + coverPoints, err = readCoverPoints(file, tracePC, traceCmp, module) + } else { + coverPoints, err = readCoverPoints(file, 0, nil, module) + } + } else { + coverPoints, err = objdump(target, module.Path) + if module.Name != "" { + for i, pcs := range coverPoints { + for j, pc := range pcs { + coverPoints[i][j] = module.Addr + pc + } + } + } + } errc <- err - return + }() + ranges, units, err := readTextRanges(file, module) + if err != nil { + return nil, err } - symbols, textAddr = symbols1, textAddr1 - if target.OS == targets.FreeBSD { - // On FreeBSD .text address in ELF is 0, but .text is actually mapped at 0xffffffff. - textAddr = ^uint64(0) + if err := <-errc; err != nil { + return nil, err } - if target.Arch == targets.AMD64 { - coverPoints, err = readCoverPoints(file, tracePC, traceCmp) - } else { - coverPoints, err = objdump(target, kernelObject) + + if module.Name == "" && len(coverPoints[0]) == 0 { + return nil, fmt.Errorf("%v doesn't contain coverage callbacks (set CONFIG_KCOV=y)", module.Path) } - errc <- err - }() - ranges, units, err := readTextRanges(file) - if err != nil { - return nil, err - } - if err := <-errc; err != nil { - return nil, err + + allCoverPoints[0] = append(allCoverPoints[0], coverPoints[0]...) + allCoverPoints[1] = append(allCoverPoints[1], coverPoints[1]...) + allSymbols = append(allSymbols, symbols...) + allRanges = append(allRanges, ranges...) + allUnits = append(allUnits, units...) } - if len(coverPoints[0]) == 0 { - return nil, fmt.Errorf("%v doesn't contain coverage callbacks (set CONFIG_KCOV=y)", kernelObject) + + sort.Slice(allSymbols, func(i, j int) bool { + return allSymbols[i].Start < allSymbols[j].Start + }) + sort.Slice(allRanges, func(i, j int) bool { + return allRanges[i].start < allRanges[j].start + }) + for k := range allCoverPoints { + sort.Slice(allCoverPoints[k], func(i, j int) bool { + return allCoverPoints[k][i] < allCoverPoints[k][j] + }) } - symbols = buildSymbols(symbols, ranges, coverPoints) + + allSymbols = buildSymbols(allSymbols, allRanges, allCoverPoints) nunit := 0 - for _, unit := range units { + for _, unit := range allUnits { if len(unit.PCs) == 0 { continue // drop the unit } unit.Name, unit.Path = cleanPath(unit.Name, objDir, srcDir, buildDir) - units[nunit] = unit + allUnits[nunit] = unit nunit++ } - units = units[:nunit] - if len(symbols) == 0 || len(units) == 0 { + allUnits = allUnits[:nunit] + if len(allSymbols) == 0 || len(allUnits) == 0 { return nil, fmt.Errorf("failed to parse DWARF (set CONFIG_DEBUG_INFO=y?)") } impl := &Impl{ - Units: units, - Symbols: symbols, - Symbolize: func(pcs []uint64) ([]Frame, error) { - return symbolize(target, objDir, srcDir, buildDir, kernelObject, pcs) + Units: allUnits, + Symbols: allSymbols, + Symbolize: func(pcs []uint64, obj string) ([]Frame, error) { + return symbolize(target, objDir, srcDir, buildDir, obj, pcs) }, RestorePC: func(pc uint32) uint64 { - return PreviousInstructionPC(target, RestorePC(pc, uint32(textAddr>>32))) + return PreviousInstructionPC(target, RestorePC(pc, uint32(modules[""].Addr>>32))) }, + Modules: modules, } return impl, nil } +func getKernelModules(dirs []string, modules map[string]KernelModule) { + files := findModulePaths(dirs) + for _, path := range files { + name := strings.TrimSuffix(filepath.Base(path), ".ko") + if module, ok := modules[name]; ok { + if module.Path != "" { + continue + } + modules[name] = KernelModule{ + Name: name, + Addr: module.Addr, + Path: path, + } + continue + } + name, err := getModuleName(path) + if err != nil { + log.Logf(0, "failed to get module name for %v: %v", path, err) + continue + } + if name == "" { + continue + } + if module, ok := modules[name]; ok { + if module.Path != "" { + continue + } + modules[name] = KernelModule{ + Name: name, + Addr: module.Addr, + Path: path, + } + } + } + log.Logf(0, "kernel modules: %v", modules) +} + +func findModulePaths(dirs []string) []string { + var files []string + for _, path := range dirs { + mfiles, err := walkModulePath(path) + if err != nil { + log.Logf(0, "failed to find modules in %v: %v", path, err) + continue + } + files = append(files, mfiles...) + } + return files +} + +func walkModulePath(dir string) ([]string, error) { + files := []string{} + err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + if filepath.Ext(path) == ".ko" { + files = append(files, path) + } + return nil + }) + return files, err +} + +func getModuleName(path string) (string, error) { + file, err := elf.Open(path) + if err != nil { + return "", fmt.Errorf("failed to open %v: %v", path, err) + } + defer file.Close() + section := file.Section(".modinfo") + if section == nil { + return "", fmt.Errorf("no .modinfo section") + } + data, err := section.Data() + if err != nil { + return "", fmt.Errorf("failed to read .modinfo") + } + name := searchModuleName(data) + if name == "" { + section = file.Section(".gnu.linkonce.this_module") + if section == nil { + return "", fmt.Errorf("no .gnu.linkonce.this_module section") + } + data, err = section.Data() + if err != nil { + return "", fmt.Errorf("failed to read .gnu.linkonce.this_module: %v", err) + } + name = string(data) + } + return name, nil +} + +func searchModuleName(data []byte) string { + data = append([]byte{0}, data...) + key := []byte("\x00name=") + pos := bytes.Index(data, key) + if pos == -1 { + return "" + } + end := bytes.IndexByte(data[pos+len(key):], 0) + if end == -1 { + return "" + } + end = pos + len(key) + end + if end > len(data) { + return "" + } + return string(data[pos+len(key) : end]) +} + +func groupPCsByModule(pcs []uint64, modules map[string]KernelModule) map[string][]uint64 { + var smodules []KernelModule + groupPCs := make(map[string][]uint64) + for _, module := range modules { + groupPCs[module.Name] = make([]uint64, 0) + smodules = append(smodules, module) + } + sort.Slice(smodules, func(i, j int) bool { + return smodules[i].Addr > smodules[j].Addr + }) + for _, pc := range pcs { + if pc > smodules[0].Addr { + if smodules[0].Name == "" { + groupPCs[smodules[0].Name] = append(groupPCs[smodules[0].Name], pc) + } else { + groupPCs[smodules[0].Name] = append(groupPCs[smodules[0].Name], pc-smodules[0].Addr) + } + } else { + for i := 0; i < len(modules)-1; i++ { + if pc < smodules[i].Addr && pc >= smodules[i+1].Addr { + if smodules[i+1].Name == "" { + groupPCs[smodules[i+1].Name] = append(groupPCs[smodules[i+1].Name], pc) + } else { + groupPCs[smodules[i+1].Name] = append(groupPCs[smodules[i+1].Name], pc-smodules[i+1].Addr) + } + break + } + } + } + } + return groupPCs +} + type pcRange struct { start uint64 end uint64 @@ -155,7 +364,7 @@ func buildSymbols(symbols []*Symbol, ranges []pcRange, coverPoints [2][]uint64) return symbols } -func readSymbols(file *elf.File) ([]*Symbol, uint64, uint64, map[uint64]bool, error) { +func readSymbols(file *elf.File, module KernelModule) ([]*Symbol, uint64, uint64, map[uint64]bool, error) { text := file.Section(".text") if text == nil { return nil, 0, 0, nil, fmt.Errorf("no .text section in the object file") @@ -171,14 +380,18 @@ func readSymbols(file *elf.File) ([]*Symbol, uint64, uint64, map[uint64]bool, er if symb.Value < text.Addr || symb.Value+symb.Size > text.Addr+text.Size { continue } + start := symb.Value + if module.Name != "" { + start += module.Addr + } symbols = append(symbols, &Symbol{ ObjectUnit: ObjectUnit{ Name: symb.Name, }, - Start: symb.Value, - End: symb.Value + symb.Size, + Start: start, + End: start + symb.Size, }) - if strings.HasPrefix(symb.Name, "__sanitizer_cov_trace_") { + if module.Name == "" && strings.HasPrefix(symb.Name, "__sanitizer_cov_trace_") { if symb.Name == "__sanitizer_cov_trace_pc" { tracePC = symb.Value } else { @@ -186,7 +399,7 @@ func readSymbols(file *elf.File) ([]*Symbol, uint64, uint64, map[uint64]bool, er } } } - if tracePC == 0 { + if module.Name == "" && tracePC == 0 { return nil, 0, 0, nil, fmt.Errorf("no __sanitizer_cov_trace_pc symbol in the object file") } sort.Slice(symbols, func(i, j int) bool { @@ -195,15 +408,18 @@ func readSymbols(file *elf.File) ([]*Symbol, uint64, uint64, map[uint64]bool, er return symbols, text.Addr, tracePC, traceCmp, nil } -func readTextRanges(file *elf.File) ([]pcRange, []*CompileUnit, error) { +func readTextRanges(file *elf.File, module KernelModule) ([]pcRange, []*CompileUnit, error) { text := file.Section(".text") if text == nil { return nil, nil, fmt.Errorf("no .text section in the object file") } kaslr := file.Section(".rela.text") != nil debugInfo, err := file.DWARF() - if err != nil { + if err != nil && module.Name == "" { return nil, nil, fmt.Errorf("failed to parse DWARF: %v (set CONFIG_DEBUG_INFO=y?)", err) + } else if err != nil { + log.Logf(0, "ignore module %v which doesn't have DEBUG_INFO", module.Name) + return nil, nil, nil } var ranges []pcRange var units []*CompileUnit @@ -249,7 +465,11 @@ func readTextRanges(file *elf.File) ([]pcRange, []*CompileUnit, error) { } } } - ranges = append(ranges, pcRange{r[0], r[1], unit}) + if module.Name == "" { + ranges = append(ranges, pcRange{r[0], r[1], unit}) + } else { + ranges = append(ranges, pcRange{r[0] + module.Addr, r[1] + module.Addr, unit}) + } } r.SkipChildren() } @@ -333,35 +553,98 @@ func symbolize(target *targets.Target, objDir, srcDir, buildDir, obj string, pcs return frames, nil } -// readCoverPoints finds all coverage points (calls of __sanitizer_cov_trace_pc) in the object file. -// Currently it is amd64-specific: looks for e8 opcode and correct offset. -// Running objdump on the whole object file is too slow. -func readCoverPoints(file *elf.File, tracePC uint64, traceCmp map[uint64]bool) ([2][]uint64, error) { - var pcs [2][]uint64 - text := file.Section(".text") - if text == nil { - return pcs, fmt.Errorf("no .text section in the object file") +func getCovRels(file *elf.File) ([2][]elf.Rela64, error) { + var rRels [2][]elf.Rela64 + rels, err := getRelocations(file) + if err != nil { + return rRels, err + } + for _, rel := range rels { + name, err := getRelSymbolName(file, elf.R_SYM64(rel.Info)) + if err != nil { + return rRels, err + } + if strings.Contains(name, "__sanitizer_cov_trace_pc") { + rRels[0] = append(rRels[0], rel) + } else if strings.Contains(name, "__sanitizer_cov_trace_") { + rRels[1] = append(rRels[1], rel) + } + } + return rRels, nil +} + +func getRelocations(file *elf.File) ([]elf.Rela64, error) { + var allRels []elf.Rela64 + for _, s := range file.Sections { + if s.Type == 4 { + var rel elf.Rela64 + r := s.Open() + rels := make([]elf.Rela64, s.Size/uint64(unsafe.Sizeof(rel))) + if err := binary.Read(r, binary.LittleEndian, rels); err != nil { + return allRels, err + } + allRels = append(allRels, rels...) + } } - data, err := text.Data() + return allRels, nil +} + +func getRelSymbolName(file *elf.File, index uint32) (string, error) { + symbols, err := file.Symbols() if err != nil { - return pcs, fmt.Errorf("failed to read .text: %v", err) + return "", err } + if uint64(index-1) < uint64(len(symbols)) { + return symbols[index-1].Name, nil + } + return "", fmt.Errorf("out of index access") +} + +// readCoverPoints finds all coverage points (calls of __sanitizer_cov_trace_*) in the object file. +// Currently it is amd64-specific: looks for e8 opcode and correct offset. +// Running objdump on the whole object file is too slow. +func readCoverPoints(file *elf.File, tracePC uint64, traceCmp map[uint64]bool, + module KernelModule) ([2][]uint64, error) { + var pcs [2][]uint64 const callLen = 5 - end := len(data) - callLen + 1 - for i := 0; i < end; i++ { - pos := bytes.IndexByte(data[i:end], 0xe8) - if pos == -1 { - break + if module.Name == "" { + text := file.Section(".text") + if text == nil { + return pcs, fmt.Errorf("no .text section in the object file") } - pos += i - i = pos - off := uint64(int64(int32(binary.LittleEndian.Uint32(data[pos+1:])))) - pc := text.Addr + uint64(pos) - target := pc + off + callLen - if target == tracePC { - pcs[0] = append(pcs[0], pc) - } else if traceCmp[target] { - pcs[1] = append(pcs[1], pc) + data, err := text.Data() + if err != nil { + return pcs, fmt.Errorf("failed to read .text: %v", err) + } + end := len(data) - callLen + 1 + for i := 0; i < end; i++ { + pos := bytes.IndexByte(data[i:end], 0xe8) + if pos == -1 { + break + } + pos += i + i = pos + off := uint64(int64(int32(binary.LittleEndian.Uint32(data[pos+1:])))) + pc := text.Addr + uint64(pos) + target := pc + off + callLen + if target == tracePC { + pcs[0] = append(pcs[0], pc) + } else if traceCmp[target] { + pcs[1] = append(pcs[1], pc) + } + } + } else { + _ = tracePC + _ = traceCmp + rels, err := getCovRels(file) + if err != nil { + return pcs, err + } + for _, rel := range rels[0] { + pcs[0] = append(pcs[0], module.Addr+rel.Off-1) + } + for _, rel := range rels[1] { + pcs[1] = append(pcs[1], module.Addr+rel.Off-1) } } return pcs, nil diff --git a/pkg/cover/backend/gvisor.go b/pkg/cover/backend/gvisor.go index e82a55276..e590c4956 100644 --- a/pkg/cover/backend/gvisor.go +++ b/pkg/cover/backend/gvisor.go @@ -14,12 +14,20 @@ import ( "github.com/google/syzkaller/sys/targets" ) -func makeGvisor(target *targets.Target, objDir, srcDir, buildDir string) (*Impl, error) { +func makeGvisor(target *targets.Target, objDir, srcDir, buildDir string, + modules map[string]KernelModule) (*Impl, error) { // pkg/build stores runsc as 'vmlinux' (we pretent to be linux), but a local build will have it as 'runsc'. bin := filepath.Join(objDir, target.KernelObject) if !osutil.IsExist(bin) { bin = filepath.Join(objDir, "runsc") } + if modules == nil { + modules = make(map[string]KernelModule) + } + modules[""] = KernelModule{ + Name: "", + Path: bin, + } frames, err := gvisorSymbolize(bin, srcDir) if err != nil { return nil, err @@ -48,6 +56,7 @@ func makeGvisor(target *targets.Target, objDir, srcDir, buildDir string) (*Impl, RestorePC: func(pc uint32) uint64 { return uint64(pc) }, + Modules: modules, } return impl, nil } diff --git a/pkg/cover/report.go b/pkg/cover/report.go index d8f2115e3..94540b6c8 100644 --- a/pkg/cover/report.go +++ b/pkg/cover/report.go @@ -28,8 +28,8 @@ type Prog struct { var RestorePC = backend.RestorePC func MakeReportGenerator(target *targets.Target, vm, objDir, srcDir, buildDir string, - subsystem []Subsystem) (*ReportGenerator, error) { - impl, err := backend.Make(target, vm, objDir, srcDir, buildDir) + subsystem []Subsystem, moduleObj []string, modules map[string]backend.KernelModule) (*ReportGenerator, error) { + impl, err := backend.Make(target, vm, objDir, srcDir, buildDir, moduleObj, modules) if err != nil { return nil, err } @@ -94,7 +94,13 @@ func (rg *ReportGenerator) prepareFileMap(progs []Prog) (map[string]*file, error for _, frame := range rg.Frames { f := getFile(files, frame.Name, frame.Path) ln := f.lines[frame.StartLine] - coveredBy := progPCs[frame.PC] + var pc uint64 + if frame.Module == "" { + pc = frame.PC + } else { + pc = frame.PC + rg.Modules[frame.Module].Addr + } + coveredBy := progPCs[pc] if len(coveredBy) == 0 { f.uncovered = append(f.uncovered, frame.Range) continue @@ -175,11 +181,20 @@ func (rg *ReportGenerator) lazySymbolize(progs []Prog) error { if len(pcs) == 0 { return nil } - frames, err := rg.Symbolize(pcs) - if err != nil { - return err + groupPCs := backend.GroupPCsByModule(pcs, rg.Modules) + for name, pcs := range groupPCs { + if len(pcs) == 0 { + continue + } + frames, err := rg.Symbolize(pcs, rg.Modules[name].Path) + if err != nil { + return err + } + for i := range frames { + frames[i].Module = name + } + rg.Frames = append(rg.Frames, frames...) } - rg.Frames = append(rg.Frames, frames...) for sym := range symbolize { sym.Symbolized = true } diff --git a/pkg/cover/report_test.go b/pkg/cover/report_test.go index 0707eab64..4b0dabc8e 100644 --- a/pkg/cover/report_test.go +++ b/pkg/cover/report_test.go @@ -190,7 +190,7 @@ func generateReport(t *testing.T, target *targets.Target, test Test) ([]byte, [] }, } - rg, err := MakeReportGenerator(target, "", dir, dir, dir, subsystem) + rg, err := MakeReportGenerator(target, "", dir, dir, dir, subsystem, nil, make(map[string]backend.KernelModule)) if err != nil { return nil, nil, err } diff --git a/pkg/host/machine_info.go b/pkg/host/machine_info.go index 7a7cd2612..8bd706a15 100644 --- a/pkg/host/machine_info.go +++ b/pkg/host/machine_info.go @@ -26,9 +26,19 @@ func CollectMachineInfo() ([]byte, error) { return buf.Bytes(), nil } +func CollectModulesInfo() ([]KernelModule, error) { + return machineModulesInfo() +} + var machineInfoFuncs []machineInfoFunc +var machineModulesInfo func() ([]KernelModule, error) type machineInfoFunc struct { name string fn func(*bytes.Buffer) error } + +type KernelModule struct { + Name string + Addr uint64 +} diff --git a/pkg/host/machine_info_linux.go b/pkg/host/machine_info_linux.go index da1ee481a..eab2a1810 100644 --- a/pkg/host/machine_info_linux.go +++ b/pkg/host/machine_info_linux.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "strings" ) @@ -18,6 +19,7 @@ func init() { {"CPU Info", readCPUInfo}, {"KVM", readKVMInfo}, } + machineModulesInfo = getModulesInfo } func readCPUInfo(buffer *bytes.Buffer) error { @@ -120,3 +122,21 @@ func readKVMInfo(buffer *bytes.Buffer) error { } return nil } + +func getModulesInfo() ([]KernelModule, error) { + var addr uint64 + var modules []KernelModule + modulesText, _ := ioutil.ReadFile("/proc/modules") + re := regexp.MustCompile(`(\w+) .*(0[x|X][a-fA-F0-9]+)[^\n]*`) + matches := re.FindAllSubmatch(modulesText, -1) + for _, m := range matches { + if _, err := fmt.Sscanf(strings.TrimPrefix(strings.ToLower(string(m[2])), "0x"), "%x", &addr); err != nil { + return nil, fmt.Errorf("address parsing error in /proc/modules") + } + modules = append(modules, KernelModule{ + Name: string(m[1]), + Addr: addr, + }) + } + return modules, nil +} diff --git a/pkg/host/machine_info_linux_test.go b/pkg/host/machine_info_linux_test.go index 5075751d5..87ebbf025 100644 --- a/pkg/host/machine_info_linux_test.go +++ b/pkg/host/machine_info_linux_test.go @@ -143,6 +143,14 @@ D: d } } +func TestGetModulesInfo(t *testing.T) { + modules, err := getModulesInfo() + if err != nil { + t.Fatal(err) + } + t.Logf("modules:\n%v", modules) +} + type cannedTest struct { arch string data string diff --git a/pkg/mgrconfig/config.go b/pkg/mgrconfig/config.go index e4bfc46f8..dd67c3d8f 100644 --- a/pkg/mgrconfig/config.go +++ b/pkg/mgrconfig/config.go @@ -5,6 +5,7 @@ package mgrconfig import ( "encoding/json" + "github.com/google/syzkaller/pkg/cover" ) @@ -38,8 +39,11 @@ type Config struct { // "qemu_args": "-fda {{TEMPLATE}}/fd" WorkdirTemplate string `json:"workdir_template"` // Directory with kernel object files (e.g. `vmlinux` for linux) - // (used for report symbolization and coverage reports, optional). + // (used for report symbolization, coverage reports and in tree modules finding, optional). KernelObj string `json:"kernel_obj"` + // Directories for unstripped kernel module object files (optional) + // It's needed for out-of-tree module build in order to find ko files automatically + ModuleObj []string `json:"module_obj"` // Kernel source directory (if not set defaults to KernelObj). KernelSrc string `json:"kernel_src,omitempty"` // Location of the driectory where the kernel was built (if not set defaults to KernelSrc) diff --git a/pkg/rpctype/rpctype.go b/pkg/rpctype/rpctype.go index c87334cd5..94c62c2b3 100644 --- a/pkg/rpctype/rpctype.go +++ b/pkg/rpctype/rpctype.go @@ -27,17 +27,18 @@ type RPCCandidate struct { type ConnectArgs struct { Name string MachineInfo []byte + Modules []host.KernelModule } type ConnectRes struct { - EnabledCalls []int - GitRevision string - TargetRevision string - AllSandboxes bool - CheckResult *CheckArgs - MemoryLeakFrames []string - DataRaceFrames []string - EnabledCoverFilter bool + EnabledCalls []int + GitRevision string + TargetRevision string + AllSandboxes bool + CheckResult *CheckArgs + MemoryLeakFrames []string + DataRaceFrames []string + CoverFilterBitmap []byte } type CheckArgs struct { -- cgit mrf-deployment