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  }