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