github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/tools/syz-check/dwarf.go (about)

     1  // Copyright 2019 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  package main
     5  
     6  import (
     7  	"debug/dwarf"
     8  	"debug/elf"
     9  	"fmt"
    10  	"runtime"
    11  	"strings"
    12  )
    13  
    14  func parseKernelObject(obj string) (map[string]*dwarf.StructType, error) {
    15  	file, err := elf.Open(obj)
    16  	if err != nil {
    17  		return nil, err
    18  	}
    19  	var sections []*elf.Section
    20  	for _, sec := range file.Sections {
    21  		// We don't need these for our purposes and dropping them speeds up parsing a lot.
    22  		// nolint:misspell
    23  		if sec.Name == ".debug_line" || strings.HasPrefix(sec.Name, ".rela.") {
    24  			continue
    25  		}
    26  		sections = append(sections, sec)
    27  	}
    28  	file.Sections = sections
    29  	debugInfo, err := file.DWARF()
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	// DWARF parsing in Go is slow, so we parallelize it as much as possible.
    34  	// First stage extracts top-level compilation units and sends them over unitc.
    35  	// Next parallel stage consumes units, extracts struct offsets and sends them over offsetc.
    36  	// Next parallel stage consumes offsets, extracts struct types and sends them over structc.
    37  	// Last stage consumes structs, deduplicates them and builds the resulting map.
    38  	numProcs := runtime.GOMAXPROCS(0)
    39  	numTypes := numProcs / 8
    40  	if numTypes == 0 {
    41  		numTypes = 1
    42  	}
    43  	buffer := 100 * numProcs
    44  	unitc := make(chan Unit, buffer)
    45  	offsetc := make(chan []dwarf.Offset, buffer)
    46  	structc := make(chan map[string]*dwarf.StructType, buffer)
    47  	errc := make(chan error)
    48  
    49  	go extractCompilationUnits(debugInfo, unitc, errc)
    50  
    51  	uniterrc := make(chan error, numProcs)
    52  	for p := 0; p < numProcs; p++ {
    53  		go extractOffsets(debugInfo, unitc, offsetc, uniterrc)
    54  	}
    55  	go func() {
    56  		var err error
    57  		for p := 0; p < numProcs; p++ {
    58  			if err1 := <-uniterrc; err1 != nil {
    59  				err = err1
    60  			}
    61  		}
    62  		close(offsetc)
    63  		errc <- err
    64  	}()
    65  
    66  	structerrc := make(chan error, numTypes)
    67  	for p := 0; p < numTypes; p++ {
    68  		// Only parallel extraction of types races with each other,
    69  		// so we can reuse debugInfo for one of the goroutines.
    70  		debugInfo1 := debugInfo
    71  		if p != 0 {
    72  			debugInfo1 = nil
    73  		}
    74  		go extractStructs(file, debugInfo1, offsetc, structc, structerrc)
    75  	}
    76  	go func() {
    77  		var err error
    78  		for p := 0; p < numTypes; p++ {
    79  			if err1 := <-structerrc; err1 != nil {
    80  				err = err1
    81  			}
    82  		}
    83  		close(structc)
    84  		errc <- err
    85  	}()
    86  
    87  	result := make(map[string]*dwarf.StructType)
    88  	go func() {
    89  		for structs := range structc {
    90  			for name, str := range structs {
    91  				result[name] = str
    92  			}
    93  		}
    94  		errc <- nil
    95  	}()
    96  
    97  	for i := 0; i < 4; i++ {
    98  		if err := <-errc; err != nil {
    99  			return nil, err
   100  		}
   101  	}
   102  	return result, nil
   103  }
   104  
   105  type Unit struct {
   106  	start dwarf.Offset
   107  	end   dwarf.Offset
   108  }
   109  
   110  func extractCompilationUnits(debugInfo *dwarf.Data, unitc chan Unit, errc chan error) {
   111  	defer close(unitc)
   112  	const sentinel = ^dwarf.Offset(0)
   113  	prev := sentinel
   114  	for r := debugInfo.Reader(); ; {
   115  		ent, err := r.Next()
   116  		if err != nil {
   117  			errc <- err
   118  			return
   119  		}
   120  		if ent == nil {
   121  			if prev != sentinel {
   122  				unitc <- Unit{prev, sentinel}
   123  			}
   124  			errc <- nil
   125  			break
   126  		}
   127  		if ent.Tag != dwarf.TagCompileUnit {
   128  			errc <- fmt.Errorf("found unexpected tag %v on top level", ent.Tag)
   129  			return
   130  		}
   131  		if prev != sentinel {
   132  			unitc <- Unit{prev, ent.Offset}
   133  		}
   134  		prev = ent.Offset
   135  		r.SkipChildren()
   136  	}
   137  }
   138  
   139  func extractOffsets(debugInfo *dwarf.Data, unitc chan Unit, offsetc chan []dwarf.Offset, errc chan error) {
   140  	r := debugInfo.Reader()
   141  	var offsets []dwarf.Offset
   142  	for unit := range unitc {
   143  		r.Seek(unit.start)
   144  		for {
   145  			ent, err := r.Next()
   146  			if err != nil {
   147  				errc <- err
   148  				return
   149  			}
   150  			if ent == nil || ent.Offset >= unit.end {
   151  				break
   152  			}
   153  			if ent.Tag == dwarf.TagStructType || ent.Tag == dwarf.TagTypedef {
   154  				offsets = append(offsets, ent.Offset)
   155  			}
   156  			if ent.Tag != dwarf.TagCompileUnit {
   157  				r.SkipChildren()
   158  			}
   159  		}
   160  		offsetc <- offsets
   161  		offsets = make([]dwarf.Offset, 0, len(offsets))
   162  	}
   163  	errc <- nil
   164  }
   165  
   166  func extractStructs(file *elf.File, debugInfo *dwarf.Data, offsetc chan []dwarf.Offset,
   167  	structc chan map[string]*dwarf.StructType, errc chan error) {
   168  	if debugInfo == nil {
   169  		var err error
   170  		debugInfo, err = file.DWARF()
   171  		if err != nil {
   172  			errc <- err
   173  			return
   174  		}
   175  	}
   176  	var structs map[string]*dwarf.StructType
   177  	appendStruct := func(str *dwarf.StructType, name string) {
   178  		if name == "" || str.ByteSize <= 0 {
   179  			return
   180  		}
   181  		if structs == nil {
   182  			structs = make(map[string]*dwarf.StructType)
   183  		}
   184  		structs[name] = str
   185  	}
   186  	for offsets := range offsetc {
   187  		for _, off := range offsets {
   188  			typ1, err := debugInfo.Type(off)
   189  			if err != nil {
   190  				errc <- err
   191  				return
   192  			}
   193  			switch typ := typ1.(type) {
   194  			case *dwarf.StructType:
   195  				appendStruct(typ, typ.StructName)
   196  			case *dwarf.TypedefType:
   197  				if str, ok := typ.Type.(*dwarf.StructType); ok {
   198  					appendStruct(str, typ.Name)
   199  				}
   200  			default:
   201  				errc <- fmt.Errorf("got not struct/typedef")
   202  				return
   203  			}
   204  		}
   205  		structc <- structs
   206  		structs = nil
   207  	}
   208  	errc <- nil
   209  }