1// Copyright 2016 syzkaller project authors. All rights reserved. 2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4// TODO: strip " (discriminator N)", "constprop", "isra" from function names 5 6package symbolizer 7 8import ( 9 "bufio" 10 "fmt" 11 "io" 12 "os/exec" 13 "strconv" 14 "strings" 15 16 "github.com/google/syzkaller/pkg/osutil" 17) 18 19type Symbolizer struct { 20 subprocs map[string]*subprocess 21} 22 23type Frame struct { 24 PC uint64 25 Func string 26 File string 27 Line int 28 Inline bool 29} 30 31type subprocess struct { 32 cmd *exec.Cmd 33 stdin io.Closer 34 stdout io.Closer 35 input *bufio.Writer 36 scanner *bufio.Scanner 37} 38 39func NewSymbolizer() *Symbolizer { 40 return &Symbolizer{} 41} 42 43func (s *Symbolizer) Symbolize(bin string, pc uint64) ([]Frame, error) { 44 return s.SymbolizeArray(bin, []uint64{pc}) 45} 46 47func (s *Symbolizer) SymbolizeArray(bin string, pcs []uint64) ([]Frame, error) { 48 sub, err := s.getSubprocess(bin) 49 if err != nil { 50 return nil, err 51 } 52 return symbolize(sub.input, sub.scanner, pcs) 53} 54 55func (s *Symbolizer) Close() { 56 for _, sub := range s.subprocs { 57 sub.stdin.Close() 58 sub.stdout.Close() 59 sub.cmd.Process.Kill() 60 sub.cmd.Wait() 61 } 62} 63 64func (s *Symbolizer) getSubprocess(bin string) (*subprocess, error) { 65 if sub := s.subprocs[bin]; sub != nil { 66 return sub, nil 67 } 68 cmd := osutil.Command("addr2line", "-afi", "-e", bin) 69 stdin, err := cmd.StdinPipe() 70 if err != nil { 71 return nil, err 72 } 73 stdout, err := cmd.StdoutPipe() 74 if err != nil { 75 stdin.Close() 76 return nil, err 77 } 78 if err := cmd.Start(); err != nil { 79 stdin.Close() 80 stdout.Close() 81 return nil, err 82 } 83 sub := &subprocess{ 84 cmd: cmd, 85 stdin: stdin, 86 stdout: stdout, 87 input: bufio.NewWriter(stdin), 88 scanner: bufio.NewScanner(stdout), 89 } 90 if s.subprocs == nil { 91 s.subprocs = make(map[string]*subprocess) 92 } 93 s.subprocs[bin] = sub 94 return sub, nil 95} 96 97func symbolize(input *bufio.Writer, scanner *bufio.Scanner, pcs []uint64) ([]Frame, error) { 98 var frames []Frame 99 done := make(chan error, 1) 100 go func() { 101 var err error 102 defer func() { 103 done <- err 104 }() 105 if !scanner.Scan() { 106 if err = scanner.Err(); err == nil { 107 err = io.EOF 108 } 109 return 110 } 111 for range pcs { 112 var frames1 []Frame 113 frames1, err = parse(scanner) 114 if err != nil { 115 return 116 } 117 frames = append(frames, frames1...) 118 } 119 for i := 0; i < 2; i++ { 120 scanner.Scan() 121 } 122 }() 123 124 for _, pc := range pcs { 125 if _, err := fmt.Fprintf(input, "0x%x\n", pc); err != nil { 126 return nil, err 127 } 128 } 129 // Write an invalid PC so that parse doesn't block reading input. 130 // We read out result for this PC at the end of the function. 131 if _, err := fmt.Fprintf(input, "0xffffffffffffffff\n"); err != nil { 132 return nil, err 133 } 134 input.Flush() 135 136 if err := <-done; err != nil { 137 return nil, err 138 } 139 return frames, nil 140} 141 142func parse(s *bufio.Scanner) ([]Frame, error) { 143 pc, err := strconv.ParseUint(s.Text(), 0, 64) 144 if err != nil { 145 return nil, fmt.Errorf("failed to parse pc '%v' in addr2line output: %v", s.Text(), err) 146 } 147 var frames []Frame 148 for { 149 if !s.Scan() { 150 break 151 } 152 ln := s.Text() 153 if len(ln) > 3 && ln[0] == '0' && ln[1] == 'x' { 154 break 155 } 156 fn := ln 157 if !s.Scan() { 158 err := s.Err() 159 if err == nil { 160 err = io.EOF 161 } 162 return nil, fmt.Errorf("failed to read file:line from addr2line: %v", err) 163 } 164 ln = s.Text() 165 colon := strings.LastIndexByte(ln, ':') 166 if colon == -1 { 167 return nil, fmt.Errorf("failed to parse file:line in addr2line output: %v", ln) 168 } 169 lineEnd := colon + 1 170 for lineEnd < len(ln) && ln[lineEnd] >= '0' && ln[lineEnd] <= '9' { 171 lineEnd++ 172 } 173 file := ln[:colon] 174 line, err := strconv.Atoi(ln[colon+1 : lineEnd]) 175 if err != nil || fn == "" || fn == "??" || file == "" || file == "??" || line <= 0 { 176 continue 177 } 178 frames = append(frames, Frame{ 179 PC: pc, 180 Func: fn, 181 File: file, 182 Line: line, 183 Inline: true, 184 }) 185 } 186 if err := s.Err(); err != nil { 187 return nil, err 188 } 189 if len(frames) != 0 { 190 frames[len(frames)-1].Inline = false 191 } 192 return frames, nil 193} 194