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