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  }