// 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" "bytes" "fmt" "io" "os" "os/exec" "strconv" "strings" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/sys/targets" ) type Symbolizer struct { target *targets.Target 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(target *targets.Target) *Symbolizer { return &Symbolizer{target: target} } 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) checkBinSupport(addr2line string) error { cmd := exec.Command(addr2line, "--help") cmd.Env = append(os.Environ(), "LC_ALL=C") out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("addr2line execution failed: %s", err) } if !bytes.Contains(out, []byte("supported targets:")) { return fmt.Errorf("addr2line output didn't contain supported targets") } if s.target.OS == targets.Darwin && s.target.Arch == targets.AMD64 && !bytes.Contains(out, []byte("mach-o-x86-64")) { return fmt.Errorf("addr2line was built without mach-o-x86-64 support") } return nil } func (s *Symbolizer) getSubprocess(bin string) (*subprocess, error) { if sub := s.subprocs[bin]; sub != nil { return sub, nil } addr2line := "addr2line" if s.target.Triple != "" { addr2line = s.target.Triple + "-" + addr2line } err := s.checkBinSupport(addr2line) if err != nil { return nil, err } 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 }