• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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