blob: f6c6dbe78704351aec46e0298c96440ec1e026d3 [file] [log] [blame]
// 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"
"fmt"
"io"
"os/exec"
"strconv"
"strings"
"github.com/google/syzkaller/pkg/osutil"
)
type Symbolizer struct {
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() *Symbolizer {
return &Symbolizer{}
}
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) getSubprocess(bin string) (*subprocess, error) {
if sub := s.subprocs[bin]; sub != nil {
return sub, nil
}
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
}