Skip to content

Commit 208da36

Browse files
committed
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.
1 parent e77fae1 commit 208da36

File tree

2 files changed

+99
-43
lines changed

2 files changed

+99
-43
lines changed

pkg/report/linux.go

Lines changed: 93 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -380,11 +380,17 @@ func (ctx *linux) extractContext(line []byte) string {
380380
}
381381

382382
func (ctx *linux) Symbolize(rep *Report) error {
383+
var symbFunc symbFuncCb
383384
if ctx.vmlinux != "" {
384-
if err := ctx.symbolize(rep); err != nil {
385-
return err
385+
symb := symbolizer.Make(ctx.config.target)
386+
defer symb.Close()
387+
symbFunc = func(bin string, pc uint64) ([]symbolizer.Frame, error) {
388+
return ctx.symbolizerCache.Symbolize(symb.Symbolize, bin, pc)
386389
}
387390
}
391+
if err := ctx.symbolize(rep, symbFunc); err != nil {
392+
return err
393+
}
388394
rep.Report = ctx.decompileOpcodes(rep.Report, rep)
389395

390396
// Skip getting maintainers for Android fuzzing since the kernel source
@@ -406,17 +412,25 @@ func (ctx *linux) Symbolize(rep *Report) error {
406412
return nil
407413
}
408414

409-
func (ctx *linux) symbolize(rep *Report) error {
410-
symb := symbolizer.Make(ctx.config.target)
411-
defer symb.Close()
412-
symbFunc := func(bin string, pc uint64) ([]symbolizer.Frame, error) {
413-
return ctx.symbolizerCache.Symbolize(symb.Symbolize, bin, pc)
414-
}
415+
type symbFuncCb = func(string, uint64) ([]symbolizer.Frame, error)
416+
417+
func (ctx *linux) symbolize(rep *Report, symbFunc symbFuncCb) error {
415418
var symbolized []byte
416419
prefix := rep.reportPrefixLen
417420
for _, line := range bytes.SplitAfter(rep.Report, []byte("\n")) {
418-
line := bytes.Clone(line)
419-
newLine := symbolizeLine(symbFunc, ctx, line)
421+
var newLine []byte
422+
parsed, ok := parseLinuxBacktraceLine(line)
423+
if ok {
424+
lines := []linuxBacktraceLine{parsed}
425+
if symbFunc != nil {
426+
lines = symbolizeLine(symbFunc, ctx, parsed)
427+
}
428+
for _, line := range lines {
429+
newLine = append(newLine, line.Assemble()...)
430+
}
431+
} else {
432+
newLine = line
433+
}
420434
if prefix > len(symbolized) {
421435
prefix += len(newLine) - len(line)
422436
}
@@ -436,72 +450,111 @@ func (ctx *linux) symbolize(rep *Report) error {
436450
return nil
437451
}
438452

439-
func symbolizeLine(symbFunc func(bin string, pc uint64) ([]symbolizer.Frame, error), ctx *linux, line []byte) []byte {
453+
type linuxBacktraceLine struct {
454+
// Fields and corresponding indices in the indices array.
455+
Name string // 2:3
456+
Offset uint64 // 4:5
457+
Size uint64 // 6:7
458+
// ... 8:9 is a ModName + its enclosing parentheses.
459+
ModName string // 10:11
460+
BuildID string // 12:13
461+
IsRipFrame bool
462+
// These fields are to be set externally.
463+
Inline bool
464+
FileLine string
465+
// These fields are not to be modified outside of the type's methods.
466+
raw []byte
467+
indices []int
468+
}
469+
470+
func parseLinuxBacktraceLine(line []byte) (info linuxBacktraceLine, ok bool) {
440471
match := linuxSymbolizeRe.FindSubmatchIndex(line)
441472
if match == nil {
442-
return line
473+
return
443474
}
444-
fn := line[match[2]:match[3]]
445-
off, err := strconv.ParseUint(string(line[match[4]:match[5]]), 16, 64)
475+
info.raw = line
476+
info.indices = match
477+
info.Name = string(line[match[2]:match[3]])
478+
var err error
479+
info.Offset, err = strconv.ParseUint(string(line[match[4]:match[5]]), 16, 64)
446480
if err != nil {
447-
return line
481+
return
448482
}
449-
size, err := strconv.ParseUint(string(line[match[6]:match[7]]), 16, 64)
483+
info.Size, err = strconv.ParseUint(string(line[match[6]:match[7]]), 16, 64)
450484
if err != nil {
451-
return line
485+
return
452486
}
453-
modName := ""
454487
if match[10] != -1 && match[11] != -1 {
455-
modName = string(line[match[10]:match[11]])
488+
info.ModName = string(line[match[10]:match[11]])
456489
}
457-
buildID := ""
458490
if match[12] != -1 && match[13] != -1 {
459-
buildID = string(line[match[12]:match[13]])
491+
info.BuildID = string(line[match[12]:match[13]])
492+
}
493+
info.IsRipFrame = linuxRipFrame.Match(line)
494+
return info, true
495+
}
496+
497+
// Note that Assemble() ignores changes to Offset and Size (no reason as these are not updated anywhere).
498+
func (line linuxBacktraceLine) Assemble() []byte {
499+
match := line.indices
500+
modified := append([]byte{}, line.raw...)
501+
if line.BuildID != "" {
502+
modified = replace(modified, match[8], match[9], []byte(" ["+line.ModName+"]"))
503+
}
504+
if line.FileLine != "" {
505+
modified = replace(modified, match[7], match[7], []byte(line.FileLine))
460506
}
461-
symb := ctx.symbols[modName][string(fn)]
507+
if line.Inline {
508+
end := match[7] + len(line.FileLine)
509+
modified = replace(modified, end, end, []byte(" [inline]"))
510+
modified = replace(modified, match[2], match[7], []byte(line.Name))
511+
} else {
512+
modified = replace(modified, match[2], match[3], []byte(line.Name))
513+
}
514+
return modified
515+
}
516+
517+
func symbolizeLine(symbFunc func(bin string, pc uint64) ([]symbolizer.Frame, error), ctx *linux,
518+
parsed linuxBacktraceLine) []linuxBacktraceLine {
519+
symb := ctx.symbols[parsed.ModName][parsed.Name]
462520
if len(symb) == 0 {
463-
return line
521+
return []linuxBacktraceLine{parsed}
464522
}
465523
var funcStart uint64
466524
for _, s := range symb {
467-
if funcStart == 0 || int(size) == s.Size {
525+
if funcStart == 0 || int(parsed.Size) == s.Size {
468526
funcStart = s.Addr
469527
}
470528
}
471-
pc := funcStart + off
472-
if !linuxRipFrame.Match(line) {
529+
pc := funcStart + parsed.Offset
530+
if !parsed.IsRipFrame {
473531
// Usually we have return PCs, so we need to look at the previous instruction.
474532
// But RIP lines contain the exact faulting PC.
475533
pc--
476534
}
477535
var bin string
478536
for _, mod := range ctx.config.kernelModules {
479-
if mod.Name == modName {
537+
if mod.Name == parsed.ModName {
480538
bin = mod.Path
481539
break
482540
}
483541
}
484542
frames, err := symbFunc(bin, pc)
485543
if err != nil || len(frames) == 0 {
486-
return line
544+
return []linuxBacktraceLine{parsed}
487545
}
488-
var symbolized []byte
546+
var ret []linuxBacktraceLine
489547
for _, frame := range frames {
490548
path, _ := backend.CleanPath(frame.File, &ctx.kernelDirs, nil)
491-
info := fmt.Sprintf(" %v:%v", path, frame.Line)
492-
modified := append([]byte{}, line...)
493-
if buildID != "" {
494-
modified = replace(modified, match[8], match[9], []byte(" ["+modName+"]"))
495-
}
496-
modified = replace(modified, match[7], match[7], []byte(info))
549+
copy := parsed
550+
copy.FileLine = fmt.Sprintf(" %v:%v", path, frame.Line)
497551
if frame.Inline {
498-
end := match[7] + len(info)
499-
modified = replace(modified, end, end, []byte(" [inline]"))
500-
modified = replace(modified, match[2], match[7], []byte(frame.Func))
552+
copy.Inline = true
553+
copy.Name = frame.Func
501554
}
502-
symbolized = append(symbolized, modified...)
555+
ret = append(ret, copy)
503556
}
504-
return symbolized
557+
return ret
505558
}
506559

507560
type parsedOpcodes struct {

pkg/report/linux_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/google/syzkaller/pkg/symbolizer"
1919
"github.com/google/syzkaller/pkg/vminfo"
2020
"github.com/google/syzkaller/sys/targets"
21+
"github.com/stretchr/testify/assert"
2122
)
2223

2324
func TestLinuxIgnores(t *testing.T) {
@@ -299,10 +300,12 @@ func TestLinuxSymbolizeLine(t *testing.T) {
299300
}
300301
for i, test := range tests {
301302
t.Run(fmt.Sprint(i), func(t *testing.T) {
302-
result := symbolizeLine(symb, ctx, []byte(test.line))
303-
if test.result != string(result) {
304-
t.Errorf("want %q\n\t get %q", test.result, string(result))
303+
rep := &Report{
304+
Report: []byte(test.line),
305305
}
306+
err := ctx.symbolize(rep, symb)
307+
assert.NoError(t, err)
308+
assert.Equal(t, test.result, string(rep.Report))
306309
})
307310
}
308311
}

0 commit comments

Comments
 (0)