| // 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 |
| } |