From 9fdbd7e3a5dd9b0c3927bf2374d8fd71c241b811 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Thu, 12 Jun 2025 17:00:08 +0200 Subject: pkg/report: refactor Linux report symbolization Parse and assemble Linux backtrace lines independently of whether vmlinux is present. Refactor the code to make it easier to insert more postprocessing actions. --- pkg/report/linux.go | 133 +++++++++++++++++++++++++++++++++-------------- pkg/report/linux_test.go | 9 ++-- 2 files changed, 99 insertions(+), 43 deletions(-) (limited to 'pkg/report') diff --git a/pkg/report/linux.go b/pkg/report/linux.go index 153e71ce2..eb6e58e51 100644 --- a/pkg/report/linux.go +++ b/pkg/report/linux.go @@ -380,11 +380,17 @@ func (ctx *linux) extractContext(line []byte) string { } func (ctx *linux) Symbolize(rep *Report) error { + var symbFunc symbFuncCb if ctx.vmlinux != "" { - if err := ctx.symbolize(rep); err != nil { - return err + symb := symbolizer.Make(ctx.config.target) + defer symb.Close() + symbFunc = func(bin string, pc uint64) ([]symbolizer.Frame, error) { + return ctx.symbolizerCache.Symbolize(symb.Symbolize, bin, pc) } } + if err := ctx.symbolize(rep, symbFunc); err != nil { + return err + } rep.Report = ctx.decompileOpcodes(rep.Report, rep) // Skip getting maintainers for Android fuzzing since the kernel source @@ -406,17 +412,25 @@ func (ctx *linux) Symbolize(rep *Report) error { return nil } -func (ctx *linux) symbolize(rep *Report) error { - symb := symbolizer.Make(ctx.config.target) - defer symb.Close() - symbFunc := func(bin string, pc uint64) ([]symbolizer.Frame, error) { - return ctx.symbolizerCache.Symbolize(symb.Symbolize, bin, pc) - } +type symbFuncCb = func(string, uint64) ([]symbolizer.Frame, error) + +func (ctx *linux) symbolize(rep *Report, symbFunc symbFuncCb) error { var symbolized []byte prefix := rep.reportPrefixLen for _, line := range bytes.SplitAfter(rep.Report, []byte("\n")) { - line := bytes.Clone(line) - newLine := symbolizeLine(symbFunc, ctx, line) + var newLine []byte + parsed, ok := parseLinuxBacktraceLine(line) + if ok { + lines := []linuxBacktraceLine{parsed} + if symbFunc != nil { + lines = symbolizeLine(symbFunc, ctx, parsed) + } + for _, line := range lines { + newLine = append(newLine, line.Assemble()...) + } + } else { + newLine = line + } if prefix > len(symbolized) { prefix += len(newLine) - len(line) } @@ -436,72 +450,111 @@ func (ctx *linux) symbolize(rep *Report) error { return nil } -func symbolizeLine(symbFunc func(bin string, pc uint64) ([]symbolizer.Frame, error), ctx *linux, line []byte) []byte { +type linuxBacktraceLine struct { + // Fields and corresponding indices in the indices array. + Name string // 2:3 + Offset uint64 // 4:5 + Size uint64 // 6:7 + // ... 8:9 is a ModName + its enclosing parentheses. + ModName string // 10:11 + BuildID string // 12:13 + IsRipFrame bool + // These fields are to be set externally. + Inline bool + FileLine string + // These fields are not to be modified outside of the type's methods. + raw []byte + indices []int +} + +func parseLinuxBacktraceLine(line []byte) (info linuxBacktraceLine, ok bool) { match := linuxSymbolizeRe.FindSubmatchIndex(line) if match == nil { - return line + return } - fn := line[match[2]:match[3]] - off, err := strconv.ParseUint(string(line[match[4]:match[5]]), 16, 64) + info.raw = line + info.indices = match + info.Name = string(line[match[2]:match[3]]) + var err error + info.Offset, err = strconv.ParseUint(string(line[match[4]:match[5]]), 16, 64) if err != nil { - return line + return } - size, err := strconv.ParseUint(string(line[match[6]:match[7]]), 16, 64) + info.Size, err = strconv.ParseUint(string(line[match[6]:match[7]]), 16, 64) if err != nil { - return line + return } - modName := "" if match[10] != -1 && match[11] != -1 { - modName = string(line[match[10]:match[11]]) + info.ModName = string(line[match[10]:match[11]]) } - buildID := "" if match[12] != -1 && match[13] != -1 { - buildID = string(line[match[12]:match[13]]) + info.BuildID = string(line[match[12]:match[13]]) + } + info.IsRipFrame = linuxRipFrame.Match(line) + return info, true +} + +// Note that Assemble() ignores changes to Offset and Size (no reason as these are not updated anywhere). +func (line linuxBacktraceLine) Assemble() []byte { + match := line.indices + modified := append([]byte{}, line.raw...) + if line.BuildID != "" { + modified = replace(modified, match[8], match[9], []byte(" ["+line.ModName+"]")) + } + if line.FileLine != "" { + modified = replace(modified, match[7], match[7], []byte(line.FileLine)) } - symb := ctx.symbols[modName][string(fn)] + if line.Inline { + end := match[7] + len(line.FileLine) + modified = replace(modified, end, end, []byte(" [inline]")) + modified = replace(modified, match[2], match[7], []byte(line.Name)) + } else { + modified = replace(modified, match[2], match[3], []byte(line.Name)) + } + return modified +} + +func symbolizeLine(symbFunc func(bin string, pc uint64) ([]symbolizer.Frame, error), ctx *linux, + parsed linuxBacktraceLine) []linuxBacktraceLine { + symb := ctx.symbols[parsed.ModName][parsed.Name] if len(symb) == 0 { - return line + return []linuxBacktraceLine{parsed} } var funcStart uint64 for _, s := range symb { - if funcStart == 0 || int(size) == s.Size { + if funcStart == 0 || int(parsed.Size) == s.Size { funcStart = s.Addr } } - pc := funcStart + off - if !linuxRipFrame.Match(line) { + pc := funcStart + parsed.Offset + if !parsed.IsRipFrame { // Usually we have return PCs, so we need to look at the previous instruction. // But RIP lines contain the exact faulting PC. pc-- } var bin string for _, mod := range ctx.config.kernelModules { - if mod.Name == modName { + if mod.Name == parsed.ModName { bin = mod.Path break } } frames, err := symbFunc(bin, pc) if err != nil || len(frames) == 0 { - return line + return []linuxBacktraceLine{parsed} } - var symbolized []byte + var ret []linuxBacktraceLine for _, frame := range frames { path, _ := backend.CleanPath(frame.File, &ctx.kernelDirs, nil) - info := fmt.Sprintf(" %v:%v", path, frame.Line) - modified := append([]byte{}, line...) - if buildID != "" { - modified = replace(modified, match[8], match[9], []byte(" ["+modName+"]")) - } - modified = replace(modified, match[7], match[7], []byte(info)) + copy := parsed + copy.FileLine = fmt.Sprintf(" %v:%v", path, frame.Line) if frame.Inline { - end := match[7] + len(info) - modified = replace(modified, end, end, []byte(" [inline]")) - modified = replace(modified, match[2], match[7], []byte(frame.Func)) + copy.Inline = true + copy.Name = frame.Func } - symbolized = append(symbolized, modified...) + ret = append(ret, copy) } - return symbolized + return ret } type parsedOpcodes struct { diff --git a/pkg/report/linux_test.go b/pkg/report/linux_test.go index f10fe0058..7a51a1ab6 100644 --- a/pkg/report/linux_test.go +++ b/pkg/report/linux_test.go @@ -18,6 +18,7 @@ import ( "github.com/google/syzkaller/pkg/symbolizer" "github.com/google/syzkaller/pkg/vminfo" "github.com/google/syzkaller/sys/targets" + "github.com/stretchr/testify/assert" ) func TestLinuxIgnores(t *testing.T) { @@ -299,10 +300,12 @@ func TestLinuxSymbolizeLine(t *testing.T) { } for i, test := range tests { t.Run(fmt.Sprint(i), func(t *testing.T) { - result := symbolizeLine(symb, ctx, []byte(test.line)) - if test.result != string(result) { - t.Errorf("want %q\n\t get %q", test.result, string(result)) + rep := &Report{ + Report: []byte(test.line), } + err := ctx.symbolize(rep, symb) + assert.NoError(t, err) + assert.Equal(t, test.result, string(rep.Report)) }) } } -- cgit mrf-deployment