// Copyright 2016 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. // TODO: strip " (discriminator N)", "constprop", "isra" from function names package symbolizer import ( "bufio" "fmt" "io" "os/exec" "strconv" "strings" "github.com/google/syzkaller/pkg/osutil" ) type Symbolizer struct { subprocs map[string]*subprocess } type Frame struct { PC uint64 Func string File string Line int Inline bool } type subprocess struct { cmd *exec.Cmd stdin io.Closer stdout io.Closer input *bufio.Writer scanner *bufio.Scanner } func NewSymbolizer() *Symbolizer { return &Symbolizer{} } func (s *Symbolizer) Symbolize(bin string, pc uint64) ([]Frame, error) { return s.SymbolizeArray(bin, []uint64{pc}) } func (s *Symbolizer) SymbolizeArray(bin string, pcs []uint64) ([]Frame, error) { sub, err := s.getSubprocess(bin) if err != nil { return nil, err } return symbolize(sub.input, sub.scanner, pcs) } func (s *Symbolizer) Close() { for _, sub := range s.subprocs { sub.stdin.Close() sub.stdout.Close() sub.cmd.Process.Kill() sub.cmd.Wait() } } func (s *Symbolizer) getSubprocess(bin string) (*subprocess, error) { if sub := s.subprocs[bin]; sub != nil { return sub, nil } cmd := osutil.Command("addr2line", "-afi", "-e", bin) stdin, err := cmd.StdinPipe() if err != nil { return nil, err } stdout, err := cmd.StdoutPipe() if err != nil { stdin.Close() return nil, err } if err := cmd.Start(); err != nil { stdin.Close() stdout.Close() return nil, err } sub := &subprocess{ cmd: cmd, stdin: stdin, stdout: stdout, input: bufio.NewWriter(stdin), scanner: bufio.NewScanner(stdout), } if s.subprocs == nil { s.subprocs = make(map[string]*subprocess) } s.subprocs[bin] = sub return sub, nil } func symbolize(input *bufio.Writer, scanner *bufio.Scanner, pcs []uint64) ([]Frame, error) { var frames []Frame done := make(chan error, 1) go func() { var err error defer func() { done <- err }() if !scanner.Scan() { if err = scanner.Err(); err == nil { err = io.EOF } return } for range pcs { var frames1 []Frame frames1, err = parse(scanner) if err != nil { return } frames = append(frames, frames1...) } for i := 0; i < 2; i++ { scanner.Scan() } }() for _, pc := range pcs { if _, err := fmt.Fprintf(input, "0x%x\n", pc); err != nil { return nil, err } } // Write an invalid PC so that parse doesn't block reading input. // We read out result for this PC at the end of the function. if _, err := fmt.Fprintf(input, "0xffffffffffffffff\n"); err != nil { return nil, err } input.Flush() if err := <-done; err != nil { return nil, err } return frames, nil } func parse(s *bufio.Scanner) ([]Frame, error) { pc, err := strconv.ParseUint(s.Text(), 0, 64) if err != nil { return nil, fmt.Errorf("failed to parse pc '%v' in addr2line output: %v", s.Text(), err) } var frames []Frame for { if !s.Scan() { break } ln := s.Text() if len(ln) > 3 && ln[0] == '0' && ln[1] == 'x' { break } fn := ln if !s.Scan() { err := s.Err() if err == nil { err = io.EOF } return nil, fmt.Errorf("failed to read file:line from addr2line: %v", err) } ln = s.Text() colon := strings.LastIndexByte(ln, ':') if colon == -1 { return nil, fmt.Errorf("failed to parse file:line in addr2line output: %v", ln) } lineEnd := colon + 1 for lineEnd < len(ln) && ln[lineEnd] >= '0' && ln[lineEnd] <= '9' { lineEnd++ } file := ln[:colon] line, err := strconv.Atoi(ln[colon+1 : lineEnd]) if err != nil || fn == "" || fn == "??" || file == "" || file == "??" || line <= 0 { continue } frames = append(frames, Frame{ PC: pc, Func: fn, File: file, Line: line, Inline: true, }) } if err := s.Err(); err != nil { return nil, err } if len(frames) != 0 { frames[len(frames)-1].Inline = false } return frames, nil }