mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	 1a9bd03d7a
			
		
	
	1a9bd03d7a
	
	
	
		
			
			Once upon a time Go version strings for development builds did not reference the major/minor version they belonged to, therefore the best we could do was to always assume that a development build belonged to the most recently released version of Go. This commit changes pkg/goversion so that the new style of development version string is fully parsed.
		
			
				
	
	
		
			3032 lines
		
	
	
		
			90 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			3032 lines
		
	
	
		
			90 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package proc
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"debug/dwarf"
 | |
| 	"debug/elf"
 | |
| 	"debug/macho"
 | |
| 	"debug/pe"
 | |
| 	"encoding/binary"
 | |
| 	"encoding/hex"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/token"
 | |
| 	"hash/crc32"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"slices"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	pdwarf "github.com/go-delve/delve/pkg/dwarf"
 | |
| 	"github.com/go-delve/delve/pkg/dwarf/frame"
 | |
| 	"github.com/go-delve/delve/pkg/dwarf/godwarf"
 | |
| 	"github.com/go-delve/delve/pkg/dwarf/line"
 | |
| 	"github.com/go-delve/delve/pkg/dwarf/loclist"
 | |
| 	"github.com/go-delve/delve/pkg/dwarf/op"
 | |
| 	"github.com/go-delve/delve/pkg/dwarf/reader"
 | |
| 	"github.com/go-delve/delve/pkg/goversion"
 | |
| 	"github.com/go-delve/delve/pkg/internal/gosym"
 | |
| 	"github.com/go-delve/delve/pkg/logflags"
 | |
| 	"github.com/go-delve/delve/pkg/proc/debuginfod"
 | |
| 	"github.com/go-delve/delve/pkg/proc/evalop"
 | |
| 	"github.com/hashicorp/golang-lru/simplelru"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	dwarfGoLanguage    = 22   // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
 | |
| 	dwarfAttrAddrBase  = 0x74 // debug/dwarf.AttrAddrBase in Go 1.14, defined here for compatibility with Go < 1.14
 | |
| 	dwarfTreeCacheSize = 512  // size of the dwarfTree cache of each image
 | |
| )
 | |
| 
 | |
| // BinaryInfo holds information on the binaries being executed (this
 | |
| // includes both the executable and also any loaded libraries).
 | |
| type BinaryInfo struct {
 | |
| 	// Architecture of this binary.
 | |
| 	Arch *Arch
 | |
| 
 | |
| 	// GOOS operating system this binary is executing on.
 | |
| 	GOOS string
 | |
| 
 | |
| 	DebugInfoDirectories []string
 | |
| 
 | |
| 	// Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point
 | |
| 	Functions []Function
 | |
| 	// Sources is a list of all source files found in debug_line.
 | |
| 	Sources []string
 | |
| 	// lookupFunc maps function names to a description of the function.
 | |
| 	lookupFunc map[string][]*Function
 | |
| 	// lookupGenericFunc maps function names, with their type parameters removed, to functions.
 | |
| 	// Functions that are not generic are not added to this map.
 | |
| 	lookupGenericFunc map[string][]*Function
 | |
| 
 | |
| 	// SymNames maps addr to a description *elf.Symbol of this addr.
 | |
| 	SymNames map[uint64]*elf.Symbol
 | |
| 
 | |
| 	// Images is a list of loaded shared libraries (also known as
 | |
| 	// shared objects on linux or DLLs on windows).
 | |
| 	Images []*Image
 | |
| 
 | |
| 	ElfDynamicSection ElfDynamicSection
 | |
| 
 | |
| 	lastModified time.Time // Time the executable of this process was last modified
 | |
| 
 | |
| 	// PackageMap maps package names to package paths, needed to lookup types inside DWARF info.
 | |
| 	// On Go1.12 this mapping is determined by using the last element of a package path, for example:
 | |
| 	//   github.com/go-delve/delve
 | |
| 	// will map to 'delve' because it ends in '/delve'.
 | |
| 	// Starting with Go1.13 debug_info will contain a special attribute
 | |
| 	// (godwarf.AttrGoPackageName) containing the canonical package name for
 | |
| 	// each package.
 | |
| 	// If multiple packages have the same name the map entry will have more
 | |
| 	// than one item in the slice.
 | |
| 	PackageMap map[string][]string
 | |
| 
 | |
| 	frameEntries frame.FrameDescriptionEntries
 | |
| 
 | |
| 	types       map[string]dwarfRef
 | |
| 	packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address
 | |
| 
 | |
| 	gStructOffset      uint64
 | |
| 	gStructOffsetIsPtr bool
 | |
| 
 | |
| 	// consts[off] lists all the constants with the type defined at offset off.
 | |
| 	consts constantsMap
 | |
| 
 | |
| 	// inlinedCallLines maps a file:line pair, corresponding to the header line
 | |
| 	// of a function to a list of PC addresses where an inlined call to that
 | |
| 	// function starts.
 | |
| 	inlinedCallLines map[fileLine][]uint64
 | |
| 
 | |
| 	// dwrapUnwrapCache caches unwrapping of defer wrapper functions (dwrap)
 | |
| 	dwrapUnwrapCache map[uint64]*Function
 | |
| 
 | |
| 	moduleDataCache []ModuleData
 | |
| 
 | |
| 	// Go 1.17 register ABI is enabled.
 | |
| 	regabi bool
 | |
| 
 | |
| 	debugPinnerFn *Function
 | |
| 	logger        logflags.Logger
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	// ErrCouldNotDetermineRelocation is an error returned when Delve could not determine the base address of a
 | |
| 	// position independent executable.
 | |
| 	ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")
 | |
| 
 | |
| 	// ErrNoDebugInfoFound is returned when Delve cannot open the debug_info
 | |
| 	// section or find an external debug info file.
 | |
| 	ErrNoDebugInfoFound = errors.New("could not open debug info")
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	supportedLinuxArch = map[elf.Machine]bool{
 | |
| 		elf.EM_X86_64:  true,
 | |
| 		elf.EM_AARCH64: true,
 | |
| 		elf.EM_386:     true,
 | |
| 		elf.EM_PPC64:   true,
 | |
| 		elf.EM_RISCV:   true,
 | |
| 	}
 | |
| 
 | |
| 	supportedWindowsArch = map[_PEMachine]bool{
 | |
| 		_IMAGE_FILE_MACHINE_AMD64: true,
 | |
| 		_IMAGE_FILE_MACHINE_ARM64: true,
 | |
| 	}
 | |
| 
 | |
| 	supportedDarwinArch = map[macho.Cpu]bool{
 | |
| 		macho.CpuAmd64: true,
 | |
| 		macho.CpuArm64: true,
 | |
| 	}
 | |
| )
 | |
| 
 | |
| // ErrFunctionNotFound is returned when failing to find the
 | |
| // function named 'FuncName' within the binary.
 | |
| type ErrFunctionNotFound struct {
 | |
| 	FuncName string
 | |
| }
 | |
| 
 | |
| func (err *ErrFunctionNotFound) Error() string {
 | |
| 	return fmt.Sprintf("could not find function %s\n", err.FuncName)
 | |
| }
 | |
| 
 | |
| // FindFileLocation returns the PC for a given file:line.
 | |
| // Assumes that `file` is normalized to lower case and '/' on Windows.
 | |
| func FindFileLocation(p Process, filename string, lineno int) ([]uint64, error) {
 | |
| 	// A single file:line can appear in multiple concrete functions, because of
 | |
| 	// generics instantiation as well as multiple inlined calls into other
 | |
| 	// concrete functions.
 | |
| 
 | |
| 	// 1. Find all instructions assigned in debug_line to filename:lineno.
 | |
| 
 | |
| 	bi := p.BinInfo()
 | |
| 
 | |
| 	fileFound := false
 | |
| 	pcs := []line.PCStmt{}
 | |
| 	for _, image := range bi.Images {
 | |
| 		for _, cu := range image.compileUnits {
 | |
| 			if cu.lineInfo == nil || cu.lineInfo.Lookup[filename] == nil {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			fileFound = true
 | |
| 			pcs = append(pcs, cu.lineInfo.LineToPCs(filename, lineno)...)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(pcs) == 0 {
 | |
| 		// Check if the line contained a call to a function that was inlined, in
 | |
| 		// that case it's possible for the line itself to not appear in debug_line
 | |
| 		// at all, but it will still be in debug_info as the call site for an
 | |
| 		// inlined subroutine entry.
 | |
| 		for _, pc := range bi.inlinedCallLines[fileLine{filename, lineno}] {
 | |
| 			pcs = append(pcs, line.PCStmt{PC: pc, Stmt: true})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(pcs) == 0 {
 | |
| 		return nil, &ErrCouldNotFindLine{fileFound, filename, lineno}
 | |
| 	}
 | |
| 
 | |
| 	// 2. assign all occurrences of filename:lineno to their containing function
 | |
| 
 | |
| 	pcByFunc := map[*Function][]line.PCStmt{}
 | |
| 	sort.Slice(pcs, func(i, j int) bool { return pcs[i].PC < pcs[j].PC })
 | |
| 	var fn *Function
 | |
| 	for _, pcstmt := range pcs {
 | |
| 		if fn == nil || (pcstmt.PC < fn.Entry) || (pcstmt.PC >= fn.End) {
 | |
| 			fn = p.BinInfo().PCToFunc(pcstmt.PC)
 | |
| 		}
 | |
| 		if fn != nil {
 | |
| 			pcByFunc[fn] = append(pcByFunc[fn], pcstmt)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	selectedPCs := []uint64{}
 | |
| 
 | |
| 	for fn, pcs := range pcByFunc {
 | |
| 		// 3. for each concrete function split instruction between the inlined functions it contains
 | |
| 
 | |
| 		if strings.Contains(fn.Name, "·dwrap·") || fn.trampoline {
 | |
| 			// skip autogenerated functions
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		dwtree, err := fn.cu.image.getDwarfTree(fn.offset)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("loading DWARF for %s@%#x: %v", fn.Name, fn.offset, err)
 | |
| 		}
 | |
| 		inlrngs := allInlineCallRanges(dwtree)
 | |
| 
 | |
| 		// findInlRng returns the DWARF offset of the inlined call containing pc.
 | |
| 		// If multiple nested inlined calls contain pc the deepest one is returned
 | |
| 		// (since allInlineCallRanges returns inlined call by decreasing depth
 | |
| 		// this is the first matching entry of the slice).
 | |
| 		findInlRng := func(pc uint64) dwarf.Offset {
 | |
| 			for _, inlrng := range inlrngs {
 | |
| 				if inlrng.rng[0] <= pc && pc < inlrng.rng[1] {
 | |
| 					return inlrng.off
 | |
| 				}
 | |
| 			}
 | |
| 			return fn.offset
 | |
| 		}
 | |
| 
 | |
| 		pcsByOff := map[dwarf.Offset][]line.PCStmt{}
 | |
| 
 | |
| 		for _, pc := range pcs {
 | |
| 			off := findInlRng(pc.PC)
 | |
| 			pcsByOff[off] = append(pcsByOff[off], pc)
 | |
| 		}
 | |
| 
 | |
| 		// 4. pick the first instruction with stmt set for each inlined call as
 | |
| 		//    well as the main body of the concrete function. If nothing has
 | |
| 		//    is_stmt set pick the first instruction instead.
 | |
| 
 | |
| 		for off, pcs := range pcsByOff {
 | |
| 			sort.Slice(pcs, func(i, j int) bool { return pcs[i].PC < pcs[j].PC })
 | |
| 
 | |
| 			var selectedPC uint64
 | |
| 			for _, pc := range pcs {
 | |
| 				if pc.Stmt {
 | |
| 					selectedPC = pc.PC
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if selectedPC == 0 && len(pcs) > 0 {
 | |
| 				selectedPC = pcs[0].PC
 | |
| 			}
 | |
| 
 | |
| 			if selectedPC == 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			// 5. if we picked the entry point of the function, skip it
 | |
| 
 | |
| 			if off == fn.offset && fn.Entry == selectedPC {
 | |
| 				selectedPC, _ = FirstPCAfterPrologue(p, fn, true)
 | |
| 			}
 | |
| 
 | |
| 			selectedPCs = append(selectedPCs, selectedPC)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	sort.Slice(selectedPCs, func(i, j int) bool { return selectedPCs[i] < selectedPCs[j] })
 | |
| 
 | |
| 	return selectedPCs, nil
 | |
| }
 | |
| 
 | |
| // inlRange is the range of an inlined call
 | |
| type inlRange struct {
 | |
| 	off   dwarf.Offset
 | |
| 	depth uint32
 | |
| 	rng   [2]uint64
 | |
| }
 | |
| 
 | |
| // allInlineCallRanges returns all inlined calls contained inside 'tree' in
 | |
| // reverse nesting order (i.e. the most nested calls are returned first).
 | |
| // Note that a single inlined call might not have a continuous range of
 | |
| // addresses and therefore appear multiple times in the returned slice.
 | |
| func allInlineCallRanges(tree *godwarf.Tree) []inlRange {
 | |
| 	r := []inlRange{}
 | |
| 
 | |
| 	var visit func(*godwarf.Tree, uint32)
 | |
| 	visit = func(n *godwarf.Tree, depth uint32) {
 | |
| 		if n.Tag == dwarf.TagInlinedSubroutine {
 | |
| 			for _, rng := range n.Ranges {
 | |
| 				r = append(r, inlRange{off: n.Offset, depth: depth, rng: rng})
 | |
| 			}
 | |
| 		}
 | |
| 		for _, child := range n.Children {
 | |
| 			visit(child, depth+1)
 | |
| 		}
 | |
| 	}
 | |
| 	visit(tree, 0)
 | |
| 
 | |
| 	sort.SliceStable(r, func(i, j int) bool { return r[i].depth > r[j].depth })
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // FindFunction returns the functions with name funcName.
 | |
| func (bi *BinaryInfo) FindFunction(funcName string) ([]*Function, error) {
 | |
| 	if fns := bi.LookupFunc()[funcName]; fns != nil {
 | |
| 		return fns, nil
 | |
| 	}
 | |
| 	fns := bi.LookupGenericFunc()[funcName]
 | |
| 	if len(fns) == 0 {
 | |
| 		return nil, &ErrFunctionNotFound{funcName}
 | |
| 	}
 | |
| 	return fns, nil
 | |
| }
 | |
| 
 | |
| // FindFunctionLocation finds address of a function's line
 | |
| // If lineOffset is passed FindFunctionLocation will return the address of that line
 | |
| func FindFunctionLocation(p Process, funcName string, lineOffset int) ([]uint64, error) {
 | |
| 	bi := p.BinInfo()
 | |
| 	origfns, err := bi.FindFunction(funcName)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if lineOffset > 0 {
 | |
| 		fn := origfns[0]
 | |
| 		filename, lineno := bi.EntryLineForFunc(fn)
 | |
| 		return FindFileLocation(p, filename, lineno+lineOffset)
 | |
| 	}
 | |
| 
 | |
| 	r := make([]uint64, 0, len(origfns[0].InlinedCalls)+len(origfns))
 | |
| 
 | |
| 	for _, origfn := range origfns {
 | |
| 		if origfn.Entry > 0 {
 | |
| 			// add concrete implementation of the function
 | |
| 			pc, err := FirstPCAfterPrologue(p, origfn, false)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			r = append(r, pc)
 | |
| 		}
 | |
| 		// add inlined calls to the function
 | |
| 		for _, call := range origfn.InlinedCalls {
 | |
| 			r = append(r, call.LowPC)
 | |
| 		}
 | |
| 		if len(r) == 0 {
 | |
| 			return nil, &ErrFunctionNotFound{funcName}
 | |
| 		}
 | |
| 	}
 | |
| 	sort.Slice(r, func(i, j int) bool { return r[i] < r[j] })
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| // FirstPCAfterPrologue returns the address of the first
 | |
| // instruction after the prologue for function fn.
 | |
| // If sameline is set FirstPCAfterPrologue will always return an
 | |
| // address associated with the same line as fn.Entry.
 | |
| func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error) {
 | |
| 	if fn.cu.lineInfo != nil {
 | |
| 		pc, _, line, ok := fn.cu.lineInfo.PrologueEndPC(fn.Entry, fn.End)
 | |
| 		if ok {
 | |
| 			if !sameline {
 | |
| 				return pc, nil
 | |
| 			}
 | |
| 			_, entryLine := p.BinInfo().EntryLineForFunc(fn)
 | |
| 			if entryLine == line {
 | |
| 				return pc, nil
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	pc, err := firstPCAfterPrologueDisassembly(p, fn, sameline)
 | |
| 	if err != nil {
 | |
| 		return fn.Entry, err
 | |
| 	}
 | |
| 
 | |
| 	if pc == fn.Entry && fn.cu.lineInfo != nil {
 | |
| 		// Look for the first instruction with the stmt flag set, so that setting a
 | |
| 		// breakpoint with file:line and with the function name always result on
 | |
| 		// the same instruction being selected.
 | |
| 		if pc2, _, _, ok := fn.cu.lineInfo.FirstStmtForLine(fn.Entry, fn.End); ok {
 | |
| 			return pc2, nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return pc, nil
 | |
| }
 | |
| 
 | |
| func findRetPC(t *Target, name string) ([]uint64, error) {
 | |
| 	fn := t.BinInfo().lookupOneFunc(name)
 | |
| 	if fn == nil {
 | |
| 		return nil, fmt.Errorf("could not find %s", name)
 | |
| 	}
 | |
| 	text, err := Disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), fn.Entry, fn.End)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	r := []uint64{}
 | |
| 	for _, instr := range text {
 | |
| 		if instr.IsRet() {
 | |
| 			r = append(r, instr.Loc.PC)
 | |
| 		}
 | |
| 	}
 | |
| 	if len(r) == 0 {
 | |
| 		return nil, fmt.Errorf("could not find return instruction in %s", name)
 | |
| 	}
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| // cpuArch is a stringer interface representing CPU architectures.
 | |
| type cpuArch interface {
 | |
| 	String() string
 | |
| }
 | |
| 
 | |
| // ErrUnsupportedArch is returned when attempting to debug a binary compiled for an unsupported architecture.
 | |
| type ErrUnsupportedArch struct {
 | |
| 	os      string
 | |
| 	cpuArch cpuArch
 | |
| }
 | |
| 
 | |
| func (e *ErrUnsupportedArch) Error() string {
 | |
| 	var supportArchs []cpuArch
 | |
| 	switch e.os {
 | |
| 	case "linux":
 | |
| 		for linuxArch := range supportedLinuxArch {
 | |
| 			supportArchs = append(supportArchs, linuxArch)
 | |
| 		}
 | |
| 	case "windows":
 | |
| 		for windowArch := range supportedWindowsArch {
 | |
| 			supportArchs = append(supportArchs, windowArch)
 | |
| 		}
 | |
| 	case "darwin":
 | |
| 		for darwinArch := range supportedDarwinArch {
 | |
| 			supportArchs = append(supportArchs, darwinArch)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	errStr := "unsupported architecture of " + e.os + "/" + e.cpuArch.String()
 | |
| 	errStr += " - only"
 | |
| 	for _, arch := range supportArchs {
 | |
| 		errStr += " " + e.os + "/" + arch.String() + " "
 | |
| 	}
 | |
| 	if len(supportArchs) == 1 {
 | |
| 		errStr += "is supported"
 | |
| 	} else {
 | |
| 		errStr += "are supported"
 | |
| 	}
 | |
| 
 | |
| 	return errStr
 | |
| }
 | |
| 
 | |
| type compileUnit struct {
 | |
| 	name    string // univocal name for non-go compile units
 | |
| 	Version uint8  // DWARF version of this compile unit
 | |
| 	lowPC   uint64
 | |
| 	ranges  [][2]uint64
 | |
| 
 | |
| 	entry     *dwarf.Entry        // debug_info entry describing this compile unit
 | |
| 	isgo      bool                // true if this is the go compile unit
 | |
| 	lineInfo  *line.DebugLineInfo // debug_line segment associated with this compile unit
 | |
| 	optimized optimizedFlags      // this compile unit is optimized
 | |
| 	producer  string              // producer attribute
 | |
| 
 | |
| 	offset dwarf.Offset // offset of the entry describing the compile unit
 | |
| 
 | |
| 	image *Image // parent image of this compilation unit.
 | |
| }
 | |
| 
 | |
| type optimizedFlags uint8
 | |
| 
 | |
| const (
 | |
| 	optimizedInlined optimizedFlags = 1 << iota
 | |
| 	optimizedOptimized
 | |
| )
 | |
| 
 | |
| type fileLine struct {
 | |
| 	file string
 | |
| 	line int
 | |
| }
 | |
| 
 | |
| // dwarfRef is a reference to a Debug Info Entry inside a shared object.
 | |
| type dwarfRef struct {
 | |
| 	imageIndex int
 | |
| 	offset     dwarf.Offset
 | |
| }
 | |
| 
 | |
| // InlinedCall represents a concrete inlined call to a function.
 | |
| type InlinedCall struct {
 | |
| 	cu            *compileUnit
 | |
| 	LowPC, HighPC uint64 // Address range of the generated inlined instructions
 | |
| }
 | |
| 
 | |
| // Function describes a function in the target program.
 | |
| type Function struct {
 | |
| 	Name       string
 | |
| 	Entry, End uint64 // same as DW_AT_lowpc and DW_AT_highpc
 | |
| 	offset     dwarf.Offset
 | |
| 	cu         *compileUnit
 | |
| 
 | |
| 	trampoline bool // DW_AT_trampoline attribute set to true
 | |
| 
 | |
| 	// InlinedCalls lists all inlined calls to this function
 | |
| 	InlinedCalls         []InlinedCall
 | |
| 	rangeParentNameCache int // see rangeParentName
 | |
| 	// extraCache contains information about this function that is only needed for
 | |
| 	// some operations and is expensive to compute or store for every function.
 | |
| 	extraCache *functionExtra
 | |
| }
 | |
| 
 | |
| type functionExtra struct {
 | |
| 	// closureStructType is the cached struct type for closures for this function
 | |
| 	closureStructType *godwarf.StructType
 | |
| 
 | |
| 	// rangeParent is set when this function is a range-over-func body closure
 | |
| 	// and points to the function that the closure was generated from.
 | |
| 	rangeParent *Function
 | |
| 	// rangeBodies is the list of range-over-func body closures for this
 | |
| 	// function. Only one between rangeParent and rangeBodies should be set at
 | |
| 	// any given time.
 | |
| 	rangeBodies []*Function
 | |
| }
 | |
| 
 | |
| // instRange returns the indexes in fn.Name of the type parameter
 | |
| // instantiation, which is the position of the outermost '[' and ']'.
 | |
| // If fn is not an instantiated function both returned values will be len(fn.Name)
 | |
| func (fn *Function) instRange() [2]int {
 | |
| 	d := len(fn.Name)
 | |
| 	inst := [2]int{d, d}
 | |
| 	if strings.HasPrefix(fn.Name, "type..") {
 | |
| 		return inst
 | |
| 	}
 | |
| 	inst[0] = strings.Index(fn.Name, "[")
 | |
| 	if inst[0] < 0 {
 | |
| 		inst[0] = d
 | |
| 		return inst
 | |
| 	}
 | |
| 	inst[1] = strings.LastIndex(fn.Name, "]")
 | |
| 	if inst[1] < 0 {
 | |
| 		inst[0] = d
 | |
| 		inst[1] = d
 | |
| 		return inst
 | |
| 	}
 | |
| 	return inst
 | |
| }
 | |
| 
 | |
| // PackageName returns the package part of the symbol name,
 | |
| // or the empty string if there is none.
 | |
| // Borrowed from $GOROOT/debug/gosym/symtab.go
 | |
| func (fn *Function) PackageName() string {
 | |
| 	inst := fn.instRange()
 | |
| 	return packageName(fn.Name[:inst[0]])
 | |
| }
 | |
| 
 | |
| func packageName(name string) string {
 | |
| 	pathend := strings.LastIndex(name, "/")
 | |
| 	if pathend < 0 {
 | |
| 		pathend = 0
 | |
| 	}
 | |
| 
 | |
| 	if i := strings.Index(name[pathend:], "."); i != -1 {
 | |
| 		return name[:pathend+i]
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // ReceiverName returns the receiver type name of this symbol,
 | |
| // or the empty string if there is none.
 | |
| // Borrowed from $GOROOT/debug/gosym/symtab.go
 | |
| func (fn *Function) ReceiverName() string {
 | |
| 	inst := fn.instRange()
 | |
| 	pathend := strings.LastIndex(fn.Name[:inst[0]], "/")
 | |
| 	if pathend < 0 {
 | |
| 		pathend = 0
 | |
| 	}
 | |
| 	l := strings.Index(fn.Name[pathend:], ".")
 | |
| 	if l == -1 {
 | |
| 		return ""
 | |
| 	}
 | |
| 	if r := strings.LastIndex(fn.Name[inst[1]:], "."); r != -1 && pathend+l != inst[1]+r {
 | |
| 		return fn.Name[pathend+l+1 : inst[1]+r]
 | |
| 	} else if r := strings.LastIndex(fn.Name[pathend:inst[0]], "."); r != -1 && l != r {
 | |
| 		return fn.Name[pathend+l+1 : pathend+r]
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // BaseName returns the symbol name without the package or receiver name.
 | |
| // Borrowed from $GOROOT/debug/gosym/symtab.go
 | |
| func (fn *Function) BaseName() string {
 | |
| 	inst := fn.instRange()
 | |
| 	if i := strings.LastIndex(fn.Name[inst[1]:], "."); i != -1 {
 | |
| 		return fn.Name[inst[1]+i+1:]
 | |
| 	} else if i := strings.LastIndex(fn.Name[:inst[0]], "."); i != -1 {
 | |
| 		return fn.Name[i+1:]
 | |
| 	}
 | |
| 	return fn.Name
 | |
| }
 | |
| 
 | |
| // NameWithoutTypeParams returns the function name without instantiation parameters
 | |
| func (fn *Function) NameWithoutTypeParams() string {
 | |
| 	inst := fn.instRange()
 | |
| 	if inst[0] == inst[1] {
 | |
| 		return fn.Name
 | |
| 	}
 | |
| 	return fn.Name[:inst[0]] + fn.Name[inst[1]+1:]
 | |
| }
 | |
| 
 | |
| // Optimized returns true if the function was optimized by the compiler.
 | |
| func (fn *Function) Optimized() bool {
 | |
| 	return fn.cu.optimized != 0
 | |
| }
 | |
| 
 | |
| // PrologueEndPC returns the PC just after the function prologue
 | |
| func (fn *Function) PrologueEndPC() uint64 {
 | |
| 	pc, _, _, ok := fn.cu.lineInfo.PrologueEndPC(fn.Entry, fn.End)
 | |
| 	if !ok {
 | |
| 		return fn.Entry
 | |
| 	}
 | |
| 	return pc
 | |
| }
 | |
| 
 | |
| func (fn *Function) AllPCs(excludeFile string, excludeLine int) ([]uint64, error) {
 | |
| 	if !fn.cu.image.Stripped() {
 | |
| 		return fn.cu.lineInfo.AllPCsBetween(fn.Entry, fn.End-1, excludeFile, excludeLine)
 | |
| 	}
 | |
| 	var pcs []uint64
 | |
| 	fnFile, lastLine, _ := fn.cu.image.symTable.PCToLine(fn.Entry - fn.cu.image.StaticBase)
 | |
| 	for pc := fn.Entry - fn.cu.image.StaticBase; pc < fn.End-fn.cu.image.StaticBase; pc++ {
 | |
| 		f, line, pcfn := fn.cu.image.symTable.PCToLine(pc)
 | |
| 		if pcfn == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		if f == fnFile && line > lastLine {
 | |
| 			lastLine = line
 | |
| 			pcs = append(pcs, pc+fn.cu.image.StaticBase)
 | |
| 		}
 | |
| 	}
 | |
| 	return pcs, nil
 | |
| }
 | |
| 
 | |
| // From $GOROOT/src/runtime/traceback.go:597
 | |
| // exportedRuntime reports whether the function is an exported runtime function.
 | |
| // It is only for runtime functions, so ASCII A-Z is fine.
 | |
| func (fn *Function) exportedRuntime() bool {
 | |
| 	name := fn.Name
 | |
| 	const n = len("runtime.")
 | |
| 	return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z'
 | |
| }
 | |
| 
 | |
| // unexportedRuntime reports whether the function is a private runtime function.
 | |
| func (fn *Function) privateRuntime() bool {
 | |
| 	name := fn.Name
 | |
| 	const n = len("runtime.")
 | |
| 	return len(name) > n && name[:n] == "runtime." && !('A' <= name[n] && name[n] <= 'Z')
 | |
| }
 | |
| 
 | |
| func rangeParentName(fnname string) int {
 | |
| 	const rangeSuffix = "-range"
 | |
| 	ridx := strings.Index(fnname, rangeSuffix)
 | |
| 	if ridx <= 0 {
 | |
| 		return -1
 | |
| 	}
 | |
| 	ok := true
 | |
| 	for i := ridx + len(rangeSuffix); i < len(fnname); i++ {
 | |
| 		if fnname[i] < '0' || fnname[i] > '9' {
 | |
| 			ok = false
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if !ok {
 | |
| 		return -1
 | |
| 	}
 | |
| 	return ridx
 | |
| }
 | |
| 
 | |
| // rangeParentName, if this function is a range-over-func body closure
 | |
| // returns the name of the parent function, otherwise returns ""
 | |
| func (fn *Function) rangeParentName() string {
 | |
| 	if fn.rangeParentNameCache == 0 {
 | |
| 		ridx := rangeParentName(fn.Name)
 | |
| 		fn.rangeParentNameCache = ridx
 | |
| 	}
 | |
| 	if fn.rangeParentNameCache < 0 {
 | |
| 		return ""
 | |
| 	}
 | |
| 	return fn.Name[:fn.rangeParentNameCache]
 | |
| }
 | |
| 
 | |
| // extra loads information about fn that is expensive to compute and we
 | |
| // only need for a minority of the functions.
 | |
| func (fn *Function) extra(bi *BinaryInfo) *functionExtra {
 | |
| 	if fn.extraCache != nil {
 | |
| 		return fn.extraCache
 | |
| 	}
 | |
| 
 | |
| 	if fn.cu.image.Stripped() {
 | |
| 		fn.extraCache = &functionExtra{}
 | |
| 		return fn.extraCache
 | |
| 	}
 | |
| 
 | |
| 	fn.extraCache = &functionExtra{}
 | |
| 
 | |
| 	// Calculate closureStructType
 | |
| 	{
 | |
| 		dwarfTree, err := fn.cu.image.getDwarfTree(fn.offset)
 | |
| 		if err != nil {
 | |
| 			return nil
 | |
| 		}
 | |
| 		st := &godwarf.StructType{
 | |
| 			Kind: "struct",
 | |
| 		}
 | |
| 		vars := reader.Variables(dwarfTree, 0, 0, reader.VariablesNoDeclLineCheck|reader.VariablesSkipInlinedSubroutines)
 | |
| 		for _, v := range vars {
 | |
| 			off, ok := v.Val(godwarf.AttrGoClosureOffset).(int64)
 | |
| 			if ok {
 | |
| 				n, _ := v.Val(dwarf.AttrName).(string)
 | |
| 				typ, err := v.Type(fn.cu.image.dwarf, fn.cu.image.index, fn.cu.image.typeCache)
 | |
| 				if err == nil {
 | |
| 					sz := typ.Common().ByteSize
 | |
| 					st.Field = append(st.Field, &godwarf.StructField{
 | |
| 						Name:       n,
 | |
| 						Type:       typ,
 | |
| 						ByteOffset: off,
 | |
| 						ByteSize:   sz,
 | |
| 						BitOffset:  off * 8,
 | |
| 						BitSize:    sz * 8,
 | |
| 					})
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if len(st.Field) > 0 {
 | |
| 			lf := st.Field[len(st.Field)-1]
 | |
| 			st.ByteSize = lf.ByteOffset + lf.Type.Common().ByteSize
 | |
| 		}
 | |
| 		fn.extraCache.closureStructType = st
 | |
| 	}
 | |
| 
 | |
| 	// Find rangeParent for this function (if it is a range-over-func body closure)
 | |
| 	if rangeParentName := fn.rangeParentName(); rangeParentName != "" {
 | |
| 		fn.extraCache.rangeParent = bi.lookupOneFunc(rangeParentName)
 | |
| 	}
 | |
| 
 | |
| 	// Find range-over-func bodies of this function
 | |
| 	if fn.extraCache.rangeParent == nil {
 | |
| 		for i := range bi.Functions {
 | |
| 			fn2 := &bi.Functions[i]
 | |
| 			if strings.HasPrefix(fn2.Name, fn.Name) && fn2.rangeParentName() == fn.Name {
 | |
| 				fn.extraCache.rangeBodies = append(fn.extraCache.rangeBodies, fn2)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return fn.extraCache
 | |
| }
 | |
| 
 | |
| type constantsMap map[dwarfRef]*constantType
 | |
| 
 | |
| type constantType struct {
 | |
| 	initialized bool
 | |
| 	values      []constantValue
 | |
| }
 | |
| 
 | |
| type constantValue struct {
 | |
| 	name      string
 | |
| 	fullName  string
 | |
| 	value     int64
 | |
| 	singleBit bool
 | |
| }
 | |
| 
 | |
| // packageVar represents a package-level variable (or a C global variable).
 | |
| // If a global variable does not have an address (for example it's stored in
 | |
| // a register, or non-contiguously) addr will be 0.
 | |
| type packageVar struct {
 | |
| 	name   string
 | |
| 	cu     *compileUnit
 | |
| 	offset dwarf.Offset
 | |
| 	addr   uint64
 | |
| }
 | |
| 
 | |
| type buildIDHeader struct {
 | |
| 	Namesz uint32
 | |
| 	Descsz uint32
 | |
| 	Type   uint32
 | |
| }
 | |
| 
 | |
| // ElfDynamicSection describes the .dynamic section of an ELF executable.
 | |
| type ElfDynamicSection struct {
 | |
| 	Addr uint64 // relocated address of where the .dynamic section is mapped in memory
 | |
| 	Size uint64 // size of the .dynamic section of the executable
 | |
| }
 | |
| 
 | |
| // NewBinaryInfo returns an initialized but unloaded BinaryInfo struct.
 | |
| func NewBinaryInfo(goos, goarch string) *BinaryInfo {
 | |
| 	r := &BinaryInfo{GOOS: goos, logger: logflags.DebuggerLogger()}
 | |
| 
 | |
| 	// TODO: find better way to determine proc arch (perhaps use executable file info).
 | |
| 	switch goarch {
 | |
| 	case "386":
 | |
| 		r.Arch = I386Arch(goos)
 | |
| 	case "amd64":
 | |
| 		r.Arch = AMD64Arch(goos)
 | |
| 	case "arm64":
 | |
| 		r.Arch = ARM64Arch(goos)
 | |
| 	case "ppc64le":
 | |
| 		r.Arch = PPC64LEArch(goos)
 | |
| 	case "riscv64":
 | |
| 		r.Arch = RISCV64Arch(goos)
 | |
| 	}
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // LoadBinaryInfo will load and store the information from the binary at 'path'.
 | |
| func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, debugInfoDirs []string) error {
 | |
| 	fi, err := os.Stat(path)
 | |
| 	if err == nil {
 | |
| 		bi.lastModified = fi.ModTime()
 | |
| 	}
 | |
| 
 | |
| 	bi.DebugInfoDirectories = debugInfoDirs
 | |
| 
 | |
| 	return bi.AddImage(path, entryPoint)
 | |
| }
 | |
| 
 | |
| func loadBinaryInfo(bi *BinaryInfo, image *Image, path string, entryPoint uint64) error {
 | |
| 	var wg sync.WaitGroup
 | |
| 	defer wg.Wait()
 | |
| 
 | |
| 	switch bi.GOOS {
 | |
| 	case "linux", "freebsd":
 | |
| 		return loadBinaryInfoElf(bi, image, path, entryPoint, &wg)
 | |
| 	case "windows":
 | |
| 		return loadBinaryInfoPE(bi, image, path, entryPoint, &wg)
 | |
| 	case "darwin":
 | |
| 		return loadBinaryInfoMacho(bi, image, path, entryPoint, &wg)
 | |
| 	}
 | |
| 	return errors.New("unsupported operating system")
 | |
| }
 | |
| 
 | |
| // GStructOffset returns the offset of the G
 | |
| // struct in thread local storage.
 | |
| func (bi *BinaryInfo) GStructOffset(mem MemoryReadWriter) (uint64, error) {
 | |
| 	offset := bi.gStructOffset
 | |
| 	if bi.gStructOffsetIsPtr {
 | |
| 		// The G struct offset from the TLS section is a pointer
 | |
| 		// and the address must be dereferenced to find to actual G struct offset.
 | |
| 		var err error
 | |
| 		offset, err = readUintRaw(mem, offset, int64(bi.Arch.PtrSize()))
 | |
| 		if err != nil {
 | |
| 			return 0, err
 | |
| 		}
 | |
| 	}
 | |
| 	return offset, nil
 | |
| }
 | |
| 
 | |
| // LastModified returns the last modified time of the binary.
 | |
| func (bi *BinaryInfo) LastModified() time.Time {
 | |
| 	return bi.lastModified
 | |
| }
 | |
| 
 | |
| // DwarfReader returns a reader for the dwarf data
 | |
| func (so *Image) DwarfReader() *reader.Reader {
 | |
| 	if so.dwarf == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return reader.New(so.dwarf)
 | |
| }
 | |
| 
 | |
| // Types returns list of types present in the debugged program.
 | |
| func (bi *BinaryInfo) Types() ([]string, error) {
 | |
| 	types := make([]string, 0, len(bi.types))
 | |
| 	for k := range bi.types {
 | |
| 		types = append(types, k)
 | |
| 	}
 | |
| 	return types, nil
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) EntryLineForFunc(fn *Function) (string, int) {
 | |
| 	return bi.pcToLine(fn, fn.Entry)
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) pcToLine(fn *Function, pc uint64) (string, int) {
 | |
| 	if fn.cu.lineInfo == nil {
 | |
| 		f, l, _ := fn.cu.image.symTable.PCToLine(pc - fn.cu.image.StaticBase)
 | |
| 		return f, l
 | |
| 	}
 | |
| 	f, l := fn.cu.lineInfo.PCToLine(fn.Entry, pc)
 | |
| 	return f, l
 | |
| }
 | |
| 
 | |
| // PCToLine converts an instruction address to a file/line/function.
 | |
| func (bi *BinaryInfo) PCToLine(pc uint64) (string, int, *Function) {
 | |
| 	fn := bi.PCToFunc(pc)
 | |
| 	if fn == nil {
 | |
| 		return "", 0, nil
 | |
| 	}
 | |
| 	f, ln := bi.pcToLine(fn, pc)
 | |
| 	return f, ln, fn
 | |
| }
 | |
| 
 | |
| type ErrCouldNotFindLine struct {
 | |
| 	fileFound bool
 | |
| 	filename  string
 | |
| 	lineno    int
 | |
| }
 | |
| 
 | |
| func (err *ErrCouldNotFindLine) Error() string {
 | |
| 	if err.fileFound {
 | |
| 		return fmt.Sprintf("could not find statement at %s:%d, please use a line with a statement", err.filename, err.lineno)
 | |
| 	}
 | |
| 	return fmt.Sprintf("could not find file %s", err.filename)
 | |
| }
 | |
| 
 | |
| // AllPCsForFileLines returns a map providing all PC addresses for filename and each line in linenos
 | |
| func (bi *BinaryInfo) AllPCsForFileLines(filename string, linenos []int) map[int][]uint64 {
 | |
| 	r := make(map[int][]uint64)
 | |
| 	for _, line := range linenos {
 | |
| 		r[line] = make([]uint64, 0, 1)
 | |
| 	}
 | |
| 	for _, image := range bi.Images {
 | |
| 		for _, cu := range image.compileUnits {
 | |
| 			if cu.lineInfo != nil && cu.lineInfo.Lookup[filename] != nil {
 | |
| 				cu.lineInfo.AllPCsForFileLines(filename, r)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // PCToFunc returns the concrete function containing the given PC address.
 | |
| // If the PC address belongs to an inlined call it will return the containing function.
 | |
| func (bi *BinaryInfo) PCToFunc(pc uint64) *Function {
 | |
| 	i := sort.Search(len(bi.Functions), func(i int) bool {
 | |
| 		fn := bi.Functions[i]
 | |
| 		return pc <= fn.Entry || (fn.Entry <= pc && pc < fn.End)
 | |
| 	})
 | |
| 	if i != len(bi.Functions) {
 | |
| 		fn := &bi.Functions[i]
 | |
| 		if fn.Entry <= pc && pc < fn.End {
 | |
| 			return fn
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // PCToImage returns the image containing the given PC address.
 | |
| func (bi *BinaryInfo) PCToImage(pc uint64) *Image {
 | |
| 	fn := bi.PCToFunc(pc)
 | |
| 	return bi.funcToImage(fn)
 | |
| }
 | |
| 
 | |
| // Image represents a loaded library file (shared object on linux, DLL on windows).
 | |
| type Image struct {
 | |
| 	Path       string
 | |
| 	StaticBase uint64
 | |
| 	BuildID    string
 | |
| 	addr       uint64
 | |
| 
 | |
| 	index int // index of this object in BinaryInfo.SharedObjects
 | |
| 
 | |
| 	closer         io.Closer
 | |
| 	sepDebugCloser io.Closer
 | |
| 
 | |
| 	dwarf        *dwarf.Data
 | |
| 	dwarfReader  *dwarf.Reader
 | |
| 	loclist2     *loclist.Dwarf2Reader
 | |
| 	loclist5     *loclist.Dwarf5Reader
 | |
| 	debugAddr    *godwarf.DebugAddrSection
 | |
| 	debugLineStr []byte
 | |
| 
 | |
| 	symTable *gosym.Table
 | |
| 
 | |
| 	typeCache map[dwarf.Offset]godwarf.Type
 | |
| 
 | |
| 	compileUnits []*compileUnit // compileUnits is sorted by increasing DWARF offset
 | |
| 
 | |
| 	dwarfTreeCache  *simplelru.LRU
 | |
| 	workaroundCache map[dwarf.Offset]*godwarf.Tree
 | |
| 
 | |
| 	// runtimeTypeToDIE maps between the offset of a runtime._type in
 | |
| 	// runtime.moduledata.types and the offset of the DIE in debug_info. This
 | |
| 	// map is filled by using the extended attribute godwarf.AttrGoRuntimeType
 | |
| 	// which was added in go 1.11.
 | |
| 	runtimeTypeToDIE map[uint64]runtimeTypeDIE
 | |
| 
 | |
| 	loadErrMu sync.Mutex
 | |
| 	loadErr   error
 | |
| }
 | |
| 
 | |
| func (image *Image) registerRuntimeTypeToDIE(entry *dwarf.Entry, ardr *reader.Reader) {
 | |
| 	if off, ok := entry.Val(godwarf.AttrGoRuntimeType).(uint64); ok {
 | |
| 		if _, ok := image.runtimeTypeToDIE[off]; !ok {
 | |
| 			image.runtimeTypeToDIE[off] = runtimeTypeDIE{entry.Offset, -1}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (image *Image) Stripped() bool {
 | |
| 	return image.dwarf == nil
 | |
| }
 | |
| 
 | |
| // AddImage adds the specified image to bi, loading data asynchronously.
 | |
| // Addr is the relocated entry point for the executable and staticBase (i.e.
 | |
| // the relocation offset) for all other images.
 | |
| // The first image added must be the executable file.
 | |
| func (bi *BinaryInfo) AddImage(path string, addr uint64) error {
 | |
| 	// Check if the image is already present.
 | |
| 	if len(bi.Images) > 0 && !strings.HasPrefix(path, "/") {
 | |
| 		return nil
 | |
| 	}
 | |
| 	for _, image := range bi.Images {
 | |
| 		if image.Path == path && image.addr == addr {
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Actually add the image.
 | |
| 	image := &Image{Path: path, addr: addr, typeCache: make(map[dwarf.Offset]godwarf.Type)}
 | |
| 	image.dwarfTreeCache, _ = simplelru.NewLRU(dwarfTreeCacheSize, nil)
 | |
| 
 | |
| 	// add Image regardless of error so that we don't attempt to re-add it every time we stop
 | |
| 	image.index = len(bi.Images)
 | |
| 	bi.Images = append(bi.Images, image)
 | |
| 	err := loadBinaryInfo(bi, image, path, addr)
 | |
| 	if err != nil {
 | |
| 		bi.Images[len(bi.Images)-1].loadErr = err
 | |
| 	}
 | |
| 	bi.macOSDebugFrameBugWorkaround()
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // moduleDataToImage finds the image corresponding to the given module data object.
 | |
| func (bi *BinaryInfo) moduleDataToImage(md *ModuleData) *Image {
 | |
| 	fn := bi.PCToFunc(md.text)
 | |
| 	if fn != nil {
 | |
| 		return bi.funcToImage(fn)
 | |
| 	}
 | |
| 	// Try searching for the image with the closest address preceding md.text
 | |
| 	var so *Image
 | |
| 	for i := range bi.Images {
 | |
| 		if int64(bi.Images[i].StaticBase) > int64(md.text) {
 | |
| 			continue
 | |
| 		}
 | |
| 		if so == nil || int64(bi.Images[i].StaticBase) > int64(so.StaticBase) {
 | |
| 			so = bi.Images[i]
 | |
| 		}
 | |
| 	}
 | |
| 	return so
 | |
| }
 | |
| 
 | |
| // imageToModuleData finds the module data in mds corresponding to the given image.
 | |
| func (bi *BinaryInfo) imageToModuleData(image *Image, mds []ModuleData) *ModuleData {
 | |
| 	for _, md := range mds {
 | |
| 		im2 := bi.moduleDataToImage(&md)
 | |
| 		if im2 != nil && im2.index == image.index {
 | |
| 			return &md
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // typeToImage returns the image containing the give type.
 | |
| func (bi *BinaryInfo) typeToImage(typ godwarf.Type) *Image {
 | |
| 	return bi.Images[typ.Common().Index]
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) runtimeTypeTypename() string {
 | |
| 	if goversion.ProducerAfterOrEqual(bi.Producer(), 1, 21) {
 | |
| 		return "internal/abi.Type"
 | |
| 	}
 | |
| 	return "runtime._type"
 | |
| }
 | |
| 
 | |
| var errBinaryInfoClose = errors.New("multiple errors closing executable files")
 | |
| 
 | |
| // Close closes all internal readers.
 | |
| func (bi *BinaryInfo) Close() error {
 | |
| 	var errs []error
 | |
| 	for _, image := range bi.Images {
 | |
| 		if err := image.Close(); err != nil {
 | |
| 			errs = append(errs, err)
 | |
| 		}
 | |
| 	}
 | |
| 	switch len(errs) {
 | |
| 	case 0:
 | |
| 		return nil
 | |
| 	case 1:
 | |
| 		return errs[0]
 | |
| 	default:
 | |
| 		return errBinaryInfoClose
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (image *Image) Close() error {
 | |
| 	var err1, err2 error
 | |
| 	if image.sepDebugCloser != nil {
 | |
| 		err := image.sepDebugCloser.Close()
 | |
| 		if err != nil {
 | |
| 			err1 = fmt.Errorf("closing shared object %q (split dwarf): %v", image.Path, err)
 | |
| 		}
 | |
| 	}
 | |
| 	if image.closer != nil {
 | |
| 		err := image.closer.Close()
 | |
| 		if err != nil {
 | |
| 			err2 = fmt.Errorf("closing shared object %q: %v", image.Path, err)
 | |
| 		}
 | |
| 	}
 | |
| 	if err1 != nil && err2 != nil {
 | |
| 		return errBinaryInfoClose
 | |
| 	}
 | |
| 	if err1 != nil {
 | |
| 		return err1
 | |
| 	}
 | |
| 	return err2
 | |
| }
 | |
| 
 | |
| func (image *Image) setLoadError(logger logflags.Logger, fmtstr string, args ...interface{}) {
 | |
| 	image.loadErrMu.Lock()
 | |
| 	image.loadErr = fmt.Errorf(fmtstr, args...)
 | |
| 	image.loadErrMu.Unlock()
 | |
| 	if logger != nil {
 | |
| 		logger.Errorf("error loading binary %q: %v", image.Path, image.loadErr)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // LoadError returns any error incurred while loading this image.
 | |
| func (image *Image) LoadError() error {
 | |
| 	return image.loadErr
 | |
| }
 | |
| 
 | |
| func (image *Image) getDwarfTree(off dwarf.Offset) (*godwarf.Tree, error) {
 | |
| 	if image.workaroundCache[off] != nil {
 | |
| 		return image.workaroundCache[off], nil
 | |
| 	}
 | |
| 	if r, ok := image.dwarfTreeCache.Get(off); ok {
 | |
| 		return r.(*godwarf.Tree), nil
 | |
| 	}
 | |
| 	r, err := godwarf.LoadTree(off, image.dwarf, image.StaticBase)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	image.dwarfTreeCache.Add(off, r)
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| type nilCloser struct{}
 | |
| 
 | |
| func (c *nilCloser) Close() error { return nil }
 | |
| 
 | |
| // LoadImageFromData creates a new Image, using the specified data, and adds it to bi.
 | |
| // This is used for debugging BinaryInfo, you should use LoadBinary instead.
 | |
| func (bi *BinaryInfo) LoadImageFromData(dwdata *dwarf.Data, debugFrameBytes, debugLineBytes, debugLocBytes []byte) {
 | |
| 	image := &Image{}
 | |
| 	image.closer = (*nilCloser)(nil)
 | |
| 	image.sepDebugCloser = (*nilCloser)(nil)
 | |
| 	image.dwarf = dwdata
 | |
| 	image.typeCache = make(map[dwarf.Offset]godwarf.Type)
 | |
| 	image.dwarfTreeCache, _ = simplelru.NewLRU(dwarfTreeCacheSize, nil)
 | |
| 
 | |
| 	if debugFrameBytes != nil {
 | |
| 		bi.frameEntries, _ = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), 0, bi.Arch.PtrSize(), 0)
 | |
| 	}
 | |
| 
 | |
| 	image.loclist2 = loclist.NewDwarf2Reader(debugLocBytes, bi.Arch.PtrSize())
 | |
| 
 | |
| 	bi.loadDebugInfoMaps(image, nil, debugLineBytes, nil, nil)
 | |
| 
 | |
| 	bi.Images = append(bi.Images, image)
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) locationExpr(entry godwarf.Entry, attr dwarf.Attr, pc uint64) ([]byte, *locationExpr, error) {
 | |
| 	//TODO(aarzilli): handle DW_FORM_loclistx attribute form new in DWARFv5
 | |
| 	a := entry.Val(attr)
 | |
| 	if a == nil {
 | |
| 		return nil, nil, fmt.Errorf("no location attribute %s", attr)
 | |
| 	}
 | |
| 	if instr, ok := a.([]byte); ok {
 | |
| 		return instr, &locationExpr{isBlock: true, instr: instr, regnumToName: bi.Arch.RegnumToString}, nil
 | |
| 	}
 | |
| 	off, ok := a.(int64)
 | |
| 	if !ok {
 | |
| 		return nil, nil, fmt.Errorf("could not interpret location attribute %s", attr)
 | |
| 	}
 | |
| 	instr := bi.loclistEntry(off, pc)
 | |
| 	if instr == nil {
 | |
| 		return nil, nil, fmt.Errorf("could not find loclist entry at %#x for address %#x", off, pc)
 | |
| 	}
 | |
| 	return instr, &locationExpr{pc: pc, off: off, instr: instr, regnumToName: bi.Arch.RegnumToString}, nil
 | |
| }
 | |
| 
 | |
| type locationExpr struct {
 | |
| 	isBlock   bool
 | |
| 	isEscaped bool
 | |
| 	off       int64
 | |
| 	pc        uint64
 | |
| 	instr     []byte
 | |
| 
 | |
| 	regnumToName func(uint64) string
 | |
| }
 | |
| 
 | |
| func (le *locationExpr) String() string {
 | |
| 	if le == nil {
 | |
| 		return ""
 | |
| 	}
 | |
| 	var descr bytes.Buffer
 | |
| 
 | |
| 	if le.isBlock {
 | |
| 		fmt.Fprintf(&descr, "[block] ")
 | |
| 		op.PrettyPrint(&descr, le.instr, le.regnumToName)
 | |
| 	} else {
 | |
| 		fmt.Fprintf(&descr, "[%#x:%#x] ", le.off, le.pc)
 | |
| 		op.PrettyPrint(&descr, le.instr, le.regnumToName)
 | |
| 	}
 | |
| 
 | |
| 	if le.isEscaped {
 | |
| 		fmt.Fprintf(&descr, " (escaped)")
 | |
| 	}
 | |
| 	return descr.String()
 | |
| }
 | |
| 
 | |
| // LocationCovers returns the list of PC addresses that is covered by the
 | |
| // location attribute 'attr' of entry 'entry'.
 | |
| func (bi *BinaryInfo) LocationCovers(entry *dwarf.Entry, attr dwarf.Attr) ([][2]uint64, error) {
 | |
| 	a := entry.Val(attr)
 | |
| 	if a == nil {
 | |
| 		return nil, fmt.Errorf("attribute %s not found", attr)
 | |
| 	}
 | |
| 	if _, isblock := a.([]byte); isblock {
 | |
| 		return [][2]uint64{{0, ^uint64(0)}}, nil
 | |
| 	}
 | |
| 
 | |
| 	off, ok := a.(int64)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("attribute %s of unsupported type %T", attr, a)
 | |
| 	}
 | |
| 	cu := bi.Images[0].findCompileUnitForOffset(entry.Offset)
 | |
| 	if cu == nil {
 | |
| 		return nil, errors.New("could not find compile unit")
 | |
| 	}
 | |
| 	if cu.Version >= 5 && cu.image.loclist5 != nil {
 | |
| 		return nil, errors.New("LocationCovers does not support DWARFv5")
 | |
| 	}
 | |
| 
 | |
| 	image := cu.image
 | |
| 	base := cu.lowPC
 | |
| 	if image == nil || image.loclist2.Empty() {
 | |
| 		return nil, errors.New("malformed executable")
 | |
| 	}
 | |
| 
 | |
| 	r := [][2]uint64{}
 | |
| 	var e loclist.Entry
 | |
| 	image.loclist2.Seek(int(off))
 | |
| 	for image.loclist2.Next(&e) {
 | |
| 		if e.BaseAddressSelection() {
 | |
| 			base = e.HighPC
 | |
| 			continue
 | |
| 		}
 | |
| 		r = append(r, [2]uint64{e.LowPC + base, e.HighPC + base})
 | |
| 	}
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| // Location returns the location described by attribute attr of entry.
 | |
| // This will either be an int64 address or a slice of Pieces for locations
 | |
| // that don't correspond to a single memory address (registers, composite
 | |
| // locations).
 | |
| func (bi *BinaryInfo) Location(entry godwarf.Entry, attr dwarf.Attr, pc uint64, regs op.DwarfRegisters, mem MemoryReadWriter) (int64, []op.Piece, *locationExpr, error) {
 | |
| 	instr, descr, err := bi.locationExpr(entry, attr, pc)
 | |
| 	if err != nil {
 | |
| 		return 0, nil, nil, err
 | |
| 	}
 | |
| 	readMemory := op.ReadMemoryFunc(nil)
 | |
| 	if mem != nil {
 | |
| 		readMemory = mem.ReadMemory
 | |
| 	}
 | |
| 	addr, pieces, err := op.ExecuteStackProgram(regs, instr, bi.Arch.PtrSize(), readMemory)
 | |
| 	return addr, pieces, descr, err
 | |
| }
 | |
| 
 | |
| // loclistEntry returns the loclist entry in the loclist starting at off,
 | |
| // for address pc.
 | |
| func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte {
 | |
| 	var base uint64
 | |
| 	image := bi.Images[0]
 | |
| 	cu := bi.findCompileUnit(pc)
 | |
| 	if cu != nil {
 | |
| 		base = cu.lowPC
 | |
| 		image = cu.image
 | |
| 	}
 | |
| 	if image == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	var loclist loclist.Reader = image.loclist2
 | |
| 	var debugAddr *godwarf.DebugAddr
 | |
| 	if cu != nil && cu.Version >= 5 && image.loclist5 != nil {
 | |
| 		loclist = image.loclist5
 | |
| 		if addrBase, ok := cu.entry.Val(dwarfAttrAddrBase).(int64); ok {
 | |
| 			debugAddr = image.debugAddr.GetSubsection(uint64(addrBase))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if loclist.Empty() {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	e, err := loclist.Find(int(off), image.StaticBase, base, pc, debugAddr)
 | |
| 	if err != nil {
 | |
| 		bi.logger.Errorf("error reading loclist section: %v", err)
 | |
| 		return nil
 | |
| 	}
 | |
| 	if e != nil {
 | |
| 		return e.Instr
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // findCompileUnit returns the compile unit containing address pc.
 | |
| func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit {
 | |
| 	for _, image := range bi.Images {
 | |
| 		for _, cu := range image.compileUnits {
 | |
| 			for _, rng := range cu.ranges {
 | |
| 				if pc >= rng[0] && pc < rng[1] {
 | |
| 					return cu
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (bi *Image) findCompileUnitForOffset(off dwarf.Offset) *compileUnit {
 | |
| 	i := sort.Search(len(bi.compileUnits), func(i int) bool {
 | |
| 		return bi.compileUnits[i].offset >= off
 | |
| 	})
 | |
| 	if i > 0 {
 | |
| 		i--
 | |
| 	}
 | |
| 	return bi.compileUnits[i]
 | |
| }
 | |
| 
 | |
| // Producer returns the value of DW_AT_producer.
 | |
| func (bi *BinaryInfo) Producer() string {
 | |
| 	for _, cu := range bi.Images[0].compileUnits {
 | |
| 		if cu.isgo && cu.producer != "" {
 | |
| 			return cu.producer
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // Type returns the Dwarf type entry at `offset`.
 | |
| func (image *Image) Type(offset dwarf.Offset) (godwarf.Type, error) {
 | |
| 	return godwarf.ReadType(image.dwarf, image.index, offset, image.typeCache)
 | |
| }
 | |
| 
 | |
| // funcToImage returns the Image containing function fn, or the
 | |
| // executable file as a fallback.
 | |
| func (bi *BinaryInfo) funcToImage(fn *Function) *Image {
 | |
| 	if fn == nil {
 | |
| 		return bi.Images[0]
 | |
| 	}
 | |
| 	return fn.cu.image
 | |
| }
 | |
| 
 | |
| // parseDebugFrameGeneral parses a debug_frame and a eh_frame section.
 | |
| // At least one of the two must be present and parsed correctly, if
 | |
| // debug_frame is present it must be parsable correctly.
 | |
| func (bi *BinaryInfo) parseDebugFrameGeneral(image *Image, debugFrameBytes []byte, debugFrameName string, debugFrameErr error, ehFrameBytes []byte, ehFrameAddr uint64, ehFrameName string, byteOrder binary.ByteOrder) {
 | |
| 	if debugFrameBytes == nil && ehFrameBytes == nil {
 | |
| 		image.setLoadError(bi.logger, "could not get %s section: %v", debugFrameName, debugFrameErr)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if debugFrameBytes != nil {
 | |
| 		fe, err := frame.Parse(debugFrameBytes, byteOrder, image.StaticBase, bi.Arch.PtrSize(), 0)
 | |
| 		if err != nil {
 | |
| 			image.setLoadError(bi.logger, "could not parse %s section: %v", debugFrameName, err)
 | |
| 			return
 | |
| 		}
 | |
| 		bi.frameEntries = bi.frameEntries.Append(fe)
 | |
| 	}
 | |
| 
 | |
| 	if ehFrameBytes != nil && ehFrameAddr > 0 {
 | |
| 		fe, err := frame.Parse(ehFrameBytes, byteOrder, image.StaticBase, bi.Arch.PtrSize(), ehFrameAddr)
 | |
| 		if err != nil {
 | |
| 			if debugFrameBytes == nil {
 | |
| 				image.setLoadError(bi.logger, "could not parse %s section: %v", ehFrameName, err)
 | |
| 				return
 | |
| 			}
 | |
| 			bi.logger.Warnf("could not parse %s section: %v", ehFrameName, err)
 | |
| 			return
 | |
| 		}
 | |
| 		bi.frameEntries = bi.frameEntries.Append(fe)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ELF ///////////////////////////////////////////////////////////////
 | |
| 
 | |
| // openSeparateDebugInfo searches for a file containing the separate
 | |
| // debug info for the binary using the "build ID" method as described
 | |
| // in GDB's documentation [1], and if found returns two handles, one
 | |
| // for the bare file, and another for its corresponding elf.File.
 | |
| // [1] https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
 | |
| //
 | |
| // Alternatively, if the debug file cannot be found be the build-id, Delve
 | |
| // will look in directories specified by the debug-info-directories config value.
 | |
| func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugInfoDirectories []string) (*os.File, *elf.File, error) {
 | |
| 	exePath := image.Path
 | |
| 	exeName := filepath.Base(image.Path)
 | |
| 	if strings.HasPrefix(image.Path, "/proc") {
 | |
| 		var err error
 | |
| 		exePath, err = filepath.EvalSymlinks(image.Path)
 | |
| 		if err == nil {
 | |
| 			exeName = filepath.Base(exePath)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var debugFilePath string
 | |
| 
 | |
| 	check := func(potentialDebugFilePath string) bool {
 | |
| 		_, err := os.Stat(potentialDebugFilePath)
 | |
| 		if err == nil {
 | |
| 			debugFilePath = potentialDebugFilePath
 | |
| 			return true
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	find := func(f func(string) bool, suffix string) {
 | |
| 		for _, dir := range debugInfoDirectories {
 | |
| 			if f != nil && !f(dir) {
 | |
| 				continue
 | |
| 			}
 | |
| 			if check(fmt.Sprintf("%s/%s", dir, suffix)) {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if debugFilePath == "" && len(image.BuildID) > 2 {
 | |
| 		// Build ID method: look for a file named .build-id/nn/nnnnnnnn.debug in
 | |
| 		// every debug info directory.
 | |
| 		find(nil, fmt.Sprintf(".build-id/%s/%s.debug", image.BuildID[:2], image.BuildID[2:]))
 | |
| 	}
 | |
| 
 | |
| 	if debugFilePath == "" {
 | |
| 		// Debug link: method if the executable contains a .gnu_debuglink section
 | |
| 		// it will look for the file named in the same directory of the
 | |
| 		// executable, then in a subdirectory named .debug and finally in each
 | |
| 		// debug info directory in a subdirectory with the same path as the
 | |
| 		// directory of the executable
 | |
| 		debugLink, crc := bi.getDebugLink(exe)
 | |
| 
 | |
| 		if debugLink != "" {
 | |
| 			check(filepath.Join(filepath.Dir(exePath), debugLink))
 | |
| 			if debugFilePath == "" {
 | |
| 				check(filepath.Join(filepath.Dir(exePath), ".debug", debugLink))
 | |
| 			}
 | |
| 			if debugFilePath == "" {
 | |
| 				suffix := filepath.Join(filepath.Dir(exePath)[1:], debugLink)
 | |
| 				find(nil, suffix)
 | |
| 			}
 | |
| 			if debugFilePath == "" {
 | |
| 				bi.logger.Warnf("gnu_debuglink link %q not found in any debug info directory", debugLink)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if debugFilePath != "" {
 | |
| 			// CRC check
 | |
| 			buf, err := os.ReadFile(debugFilePath)
 | |
| 			if err == nil {
 | |
| 				computedCRC := crc32.ChecksumIEEE(buf)
 | |
| 				if crc != computedCRC {
 | |
| 					bi.logger.Errorf("gnu_debuglink CRC check failed for %s (want %x got %x)", debugFilePath, crc, computedCRC)
 | |
| 					debugFilePath = ""
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if debugFilePath == "" && len(image.BuildID) > 2 {
 | |
| 		// Previous versions of delve looked for the build id in every debug info
 | |
| 		// directory that contained the build-id substring. This behavior deviates
 | |
| 		// from the ones specified by GDB but we keep it for backwards compatibility.
 | |
| 		find(func(dir string) bool { return strings.Contains(dir, "build-id") }, fmt.Sprintf("%s/%s.debug", image.BuildID[:2], image.BuildID[2:]))
 | |
| 	}
 | |
| 
 | |
| 	if debugFilePath == "" {
 | |
| 		// Previous versions of delve looked for the executable filename (with
 | |
| 		// .debug extension) in every debug info directory.  This behavior also
 | |
| 		// deviates from the ones specified by GDB, but we keep it for backwards
 | |
| 		// compatibility.
 | |
| 		find(func(dir string) bool { return !strings.Contains(dir, "build-id") }, fmt.Sprintf("%s.debug", exeName))
 | |
| 	}
 | |
| 
 | |
| 	// We cannot find the debug information locally on the system. Try and see if we're on a system that
 | |
| 	// has debuginfod so that we can use that in order to find any relevant debug information.
 | |
| 	if debugFilePath == "" {
 | |
| 		var err error
 | |
| 		debugFilePath, err = debuginfod.GetDebuginfo(image.BuildID)
 | |
| 		if err != nil {
 | |
| 			return nil, nil, ErrNoDebugInfoFound
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	sepFile, err := os.OpenFile(debugFilePath, 0, os.ModePerm)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, errors.New("can't open separate debug file: " + err.Error())
 | |
| 	}
 | |
| 
 | |
| 	elfFile, err := elf.NewFile(sepFile)
 | |
| 	if err != nil {
 | |
| 		sepFile.Close()
 | |
| 		return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, err.Error())
 | |
| 	}
 | |
| 
 | |
| 	if !supportedLinuxArch[elfFile.Machine] {
 | |
| 		sepFile.Close()
 | |
| 		return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, &ErrUnsupportedArch{os: "linux", cpuArch: elfFile.Machine})
 | |
| 	}
 | |
| 
 | |
| 	return sepFile, elfFile, nil
 | |
| }
 | |
| 
 | |
| // loadBinaryInfoElf specifically loads information from an ELF binary.
 | |
| func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, wg *sync.WaitGroup) error {
 | |
| 	exe, err := os.OpenFile(path, 0, os.ModePerm)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	image.closer = exe
 | |
| 	elfFile, err := elf.NewFile(exe)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if !supportedLinuxArch[elfFile.Machine] {
 | |
| 		return &ErrUnsupportedArch{os: "linux", cpuArch: elfFile.Machine}
 | |
| 	}
 | |
| 
 | |
| 	if image.index == 0 {
 | |
| 		// adding executable file:
 | |
| 		// - addr is entryPoint therefore staticBase needs to be calculated by
 | |
| 		//   subtracting the entry point specified in the executable file from addr.
 | |
| 		// - memory address of the .dynamic section needs to be recorded in
 | |
| 		//   BinaryInfo so that we can find loaded libraries.
 | |
| 		if addr != 0 {
 | |
| 			image.StaticBase = addr - elfFile.Entry
 | |
| 		} else if elfFile.Type == elf.ET_DYN {
 | |
| 			return ErrCouldNotDetermineRelocation
 | |
| 		}
 | |
| 		if dynsec := elfFile.Section(".dynamic"); dynsec != nil {
 | |
| 			bi.ElfDynamicSection.Addr = dynsec.Addr + image.StaticBase
 | |
| 			bi.ElfDynamicSection.Size = dynsec.Size
 | |
| 		}
 | |
| 	} else {
 | |
| 		image.StaticBase = addr
 | |
| 	}
 | |
| 
 | |
| 	dwarfFile := elfFile
 | |
| 
 | |
| 	bi.loadBuildID(image, elfFile)
 | |
| 	var debugInfoBytes []byte
 | |
| 	var dwerr error
 | |
| 	image.dwarf, dwerr = elfFile.DWARF()
 | |
| 	if dwerr != nil {
 | |
| 		var sepFile *os.File
 | |
| 		var serr error
 | |
| 		sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(image, elfFile, bi.DebugInfoDirectories)
 | |
| 		if serr != nil {
 | |
| 			if len(bi.Images) <= 1 {
 | |
| 				fmt.Fprintln(os.Stderr, "Warning: no debug info found, some functionality will be missing such as stack traces and variable evaluation.")
 | |
| 			}
 | |
| 			err := loadBinaryInfoGoRuntimeElf(bi, image, path, elfFile)
 | |
| 			if err != nil {
 | |
| 				return fmt.Errorf("could not read debug info (%v) and could not read go symbol table (%v)", dwerr, err)
 | |
| 			}
 | |
| 			return nil
 | |
| 		}
 | |
| 		image.sepDebugCloser = sepFile
 | |
| 		image.dwarf, err = dwarfFile.DWARF()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	debugInfoBytes, err = godwarf.GetDebugSectionElf(dwarfFile, "info")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	image.dwarfReader = image.dwarf.Reader()
 | |
| 
 | |
| 	debugLineBytes, err := godwarf.GetDebugSectionElf(dwarfFile, "line")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	debugLocBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "loc")
 | |
| 	image.loclist2 = loclist.NewDwarf2Reader(debugLocBytes, bi.Arch.PtrSize())
 | |
| 	debugLoclistBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "loclists")
 | |
| 	image.loclist5 = loclist.NewDwarf5Reader(debugLoclistBytes)
 | |
| 	debugAddrBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "addr")
 | |
| 	image.debugAddr = godwarf.ParseAddr(debugAddrBytes)
 | |
| 	debugLineStrBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "line_str")
 | |
| 	image.debugLineStr = debugLineStrBytes
 | |
| 
 | |
| 	wg.Add(3)
 | |
| 	go bi.parseDebugFrameElf(image, dwarfFile, elfFile, debugInfoBytes, wg)
 | |
| 	go bi.loadDebugInfoMaps(image, debugInfoBytes, debugLineBytes, wg, nil)
 | |
| 	go bi.loadSymbolName(image, elfFile, wg)
 | |
| 	if image.index == 0 {
 | |
| 		// determine g struct offset only when loading the executable file
 | |
| 		wg.Add(1)
 | |
| 		go bi.setGStructOffsetElf(image, dwarfFile, wg)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func findGoFuncVal(moduleData []byte, roDataAddr uint64, ptrsize int) (uint64, error) {
 | |
| 	buf := new(bytes.Buffer)
 | |
| 	err := binary.Write(buf, binary.LittleEndian, &roDataAddr)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	// Here we search for the value of `go.func.*` by searching through the raw bytes of the
 | |
| 	// runtime.moduledata structure. Since we don't know the value that we are looking for,
 | |
| 	// we use a known value, in this case the address of the .rodata section.
 | |
| 	// This is because in the layout of the struct, the rodata member is right next to
 | |
| 	// the value we need, making the math trivial once we find that member.
 | |
| 	// We use `bytes.LastIndex` specifically because the `types` struct member can also
 | |
| 	// contain the address of the .rodata section, so this pointer can appear multiple times
 | |
| 	// in the raw bytes.
 | |
| 	// Yes, this is very ill-advised low-level hackery but it works fine until
 | |
| 	// https://github.com/golang/go/issues/58474#issuecomment-1785681472 happens.
 | |
| 	// This code path also only runs in stripped binaries, so the whole implementation is
 | |
| 	// best effort anyways.
 | |
| 	rodata := bytes.LastIndex(moduleData, buf.Bytes()[:ptrsize])
 | |
| 	if rodata == -1 {
 | |
| 		return 0, errors.New("could not find rodata struct member")
 | |
| 	}
 | |
| 	// Layout of struct members is:
 | |
| 	// type moduledata struct {
 | |
| 	// 	...
 | |
| 	// 	rodata uintptr
 | |
| 	// 	gofunc uintptr
 | |
| 	// 	...
 | |
| 	// }
 | |
| 	// So do some pointer arithmetic to get the value we need.
 | |
| 	gofuncval := binary.LittleEndian.Uint64(moduleData[rodata+(1*ptrsize) : rodata+(2*ptrsize)])
 | |
| 	return gofuncval, nil
 | |
| }
 | |
| 
 | |
| func parseModuleData(dataSection []byte, tableAddr uint64) ([]byte, error) {
 | |
| 	buf := new(bytes.Buffer)
 | |
| 	err := binary.Write(buf, binary.LittleEndian, &tableAddr)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	off := bytes.Index(dataSection, buf.Bytes()[:4])
 | |
| 	if off == -1 {
 | |
| 		return nil, errors.New("could not find moduledata")
 | |
| 	}
 | |
| 	return dataSection[off : off+0x300], nil
 | |
| }
 | |
| 
 | |
| // _STT_FUNC is a code object, see /usr/include/elf.h for a full definition.
 | |
| const _STT_FUNC = 2
 | |
| 
 | |
| func (bi *BinaryInfo) loadSymbolName(image *Image, file *elf.File, wg *sync.WaitGroup) {
 | |
| 	defer wg.Done()
 | |
| 	if bi.SymNames == nil {
 | |
| 		bi.SymNames = make(map[uint64]*elf.Symbol)
 | |
| 	}
 | |
| 	symSecs, _ := file.Symbols()
 | |
| 	for _, symSec := range symSecs {
 | |
| 		if symSec.Info == _STT_FUNC { // TODO(chainhelen), need to parse others types.
 | |
| 			s := symSec
 | |
| 			bi.SymNames[symSec.Value+image.StaticBase] = &s
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) loadBuildID(image *Image, file *elf.File) {
 | |
| 	buildid := file.Section(".note.gnu.build-id")
 | |
| 	if buildid == nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	br := buildid.Open()
 | |
| 	bh := new(buildIDHeader)
 | |
| 	if err := binary.Read(br, binary.LittleEndian, bh); err != nil {
 | |
| 		bi.logger.Warnf("can't read build-id header: %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	name := make([]byte, bh.Namesz)
 | |
| 	if err := binary.Read(br, binary.LittleEndian, name); err != nil {
 | |
| 		bi.logger.Warnf("can't read build-id name: %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if strings.TrimSpace(string(name)) != "GNU\x00" {
 | |
| 		bi.logger.Warn("invalid build-id signature")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	descBinary := make([]byte, bh.Descsz)
 | |
| 	if err := binary.Read(br, binary.LittleEndian, descBinary); err != nil {
 | |
| 		bi.logger.Warnf("can't read build-id desc: %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 	image.BuildID = hex.EncodeToString(descBinary)
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) getDebugLink(exe *elf.File) (debugLink string, crc uint32) {
 | |
| 	gnuDebugLink := exe.Section(".gnu_debuglink")
 | |
| 	if gnuDebugLink == nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	br := gnuDebugLink.Open()
 | |
| 	buf, err := io.ReadAll(br)
 | |
| 	if err != nil {
 | |
| 		bi.logger.Warnf("can't read .gnu_debuglink: %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 	zero := bytes.Index(buf, []byte{0})
 | |
| 	if zero <= 0 || len(buf[zero+1:]) < 4 {
 | |
| 		bi.logger.Warnf("wrong .gnu_debuglink format: %q", buf)
 | |
| 		return
 | |
| 	}
 | |
| 	debugLink = string(buf[:zero])
 | |
| 	crc = binary.LittleEndian.Uint32(buf[len(buf)-4:])
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) parseDebugFrameElf(image *Image, dwarfFile, exeFile *elf.File, debugInfoBytes []byte, wg *sync.WaitGroup) {
 | |
| 	defer wg.Done()
 | |
| 
 | |
| 	debugFrameData, debugFrameErr := godwarf.GetDebugSectionElf(dwarfFile, "frame")
 | |
| 	ehFrameSection := exeFile.Section(".eh_frame")
 | |
| 	var ehFrameData []byte
 | |
| 	var ehFrameAddr uint64
 | |
| 	if ehFrameSection != nil {
 | |
| 		ehFrameAddr = ehFrameSection.Addr
 | |
| 		// Workaround for go.dev/cl/429601
 | |
| 		if ehFrameSection.Type == elf.SHT_NOBITS {
 | |
| 			ehFrameData = make([]byte, ehFrameSection.Size)
 | |
| 		} else {
 | |
| 			ehFrameData, _ = ehFrameSection.Data()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	bi.parseDebugFrameGeneral(image, debugFrameData, ".debug_frame", debugFrameErr, ehFrameData, ehFrameAddr, ".eh_frame", frame.DwarfEndian(debugInfoBytes))
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.WaitGroup) {
 | |
| 	defer wg.Done()
 | |
| 
 | |
| 	// This is a bit arcane. Essentially:
 | |
| 	// - If the program is pure Go, it can do whatever it wants, and puts the G
 | |
| 	//   pointer at %fs-8 on 64 bit.
 | |
| 	// - %Gs is the index of private storage in GDT on 32 bit, and puts the G
 | |
| 	//   pointer at -4(tls).
 | |
| 	// - Otherwise, Go asks the external linker to place the G pointer by
 | |
| 	//   emitting runtime.tlsg, a TLS symbol, which is relocated to the chosen
 | |
| 	//   offset in libc's TLS block.
 | |
| 	// - On ARM64 (but really, any architecture other than i386 and 86x64) the
 | |
| 	//   offset is calculated using runtime.tls_g and the formula is different.
 | |
| 
 | |
| 	var tls *elf.Prog
 | |
| 	for _, prog := range exe.Progs {
 | |
| 		if prog.Type == elf.PT_TLS {
 | |
| 			tls = prog
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	switch exe.Machine {
 | |
| 	case elf.EM_X86_64, elf.EM_386:
 | |
| 		tlsg := getSymbol(image, bi.logger, exe, "runtime.tlsg")
 | |
| 		if tlsg == nil || tls == nil {
 | |
| 			bi.gStructOffset = ^uint64(bi.Arch.PtrSize()) + 1 //-ptrSize
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		// According to https://reviews.llvm.org/D61824, linkers must pad the actual
 | |
| 		// size of the TLS segment to ensure that (tlsoffset%align) == (vaddr%align).
 | |
| 		// This formula, copied from the lld code, matches that.
 | |
| 		// https://github.com/llvm-mirror/lld/blob/9aef969544981d76bea8e4d1961d3a6980980ef9/ELF/InputSection.cpp#L643
 | |
| 		memsz := tls.Memsz + (-tls.Vaddr-tls.Memsz)&(tls.Align-1)
 | |
| 
 | |
| 		// The TLS register points to the end of the TLS block, which is
 | |
| 		// tls.Memsz long. runtime.tlsg is an offset from the beginning of that block.
 | |
| 		bi.gStructOffset = ^(memsz) + 1 + tlsg.Value // -tls.Memsz + tlsg.Value
 | |
| 
 | |
| 	case elf.EM_AARCH64:
 | |
| 		tlsg := getSymbol(image, bi.logger, exe, "runtime.tls_g")
 | |
| 		if tlsg == nil || tls == nil {
 | |
| 			bi.gStructOffset = 2 * uint64(bi.Arch.PtrSize())
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		bi.gStructOffset = tlsg.Value + uint64(bi.Arch.PtrSize()*2) + ((tls.Vaddr - uint64(bi.Arch.PtrSize()*2)) & (tls.Align - 1))
 | |
| 
 | |
| 	case elf.EM_PPC64, elf.EM_RISCV:
 | |
| 		_ = getSymbol(image, bi.logger, exe, "runtime.tls_g")
 | |
| 
 | |
| 	default:
 | |
| 		// we should never get here
 | |
| 		panic("architecture not supported")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func getSymbol(image *Image, logger logflags.Logger, exe *elf.File, name string) *elf.Symbol {
 | |
| 	symbols, err := exe.Symbols()
 | |
| 	if err != nil {
 | |
| 		image.setLoadError(logger, "could not parse ELF symbols: %v", err)
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	for _, symbol := range symbols {
 | |
| 		if symbol.Name == name {
 | |
| 			s := symbol
 | |
| 			return &s
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // PE ////////////////////////////////////////////////////////////////
 | |
| 
 | |
| const _IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040
 | |
| 
 | |
| // loadBinaryInfoPE specifically loads information from a PE binary.
 | |
| func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint64, wg *sync.WaitGroup) error {
 | |
| 	peFile, closer, err := openExecutablePathPE(path)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	image.closer = closer
 | |
| 	cpuArch := _PEMachine(peFile.Machine)
 | |
| 	if !supportedWindowsArch[cpuArch] {
 | |
| 		return &ErrUnsupportedArch{os: "windows", cpuArch: cpuArch}
 | |
| 	}
 | |
| 	image.dwarf, err = peFile.DWARF()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	debugInfoBytes, err := godwarf.GetDebugSectionPE(peFile, "info")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	opth := peFile.OptionalHeader.(*pe.OptionalHeader64)
 | |
| 	if entryPoint != 0 {
 | |
| 		image.StaticBase = entryPoint - opth.ImageBase
 | |
| 	} else {
 | |
| 		if opth.DllCharacteristics&_IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE != 0 {
 | |
| 			return ErrCouldNotDetermineRelocation
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	image.dwarfReader = image.dwarf.Reader()
 | |
| 
 | |
| 	debugLineBytes, err := godwarf.GetDebugSectionPE(peFile, "line")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	debugLocBytes, _ := godwarf.GetDebugSectionPE(peFile, "loc")
 | |
| 	image.loclist2 = loclist.NewDwarf2Reader(debugLocBytes, bi.Arch.PtrSize())
 | |
| 	debugLoclistBytes, _ := godwarf.GetDebugSectionPE(peFile, "loclists")
 | |
| 	image.loclist5 = loclist.NewDwarf5Reader(debugLoclistBytes)
 | |
| 	debugAddrBytes, _ := godwarf.GetDebugSectionPE(peFile, "addr")
 | |
| 	image.debugAddr = godwarf.ParseAddr(debugAddrBytes)
 | |
| 	debugLineStrBytes, _ := godwarf.GetDebugSectionPE(peFile, "line_str")
 | |
| 	image.debugLineStr = debugLineStrBytes
 | |
| 
 | |
| 	wg.Add(2)
 | |
| 	go bi.parseDebugFramePE(image, peFile, debugInfoBytes, wg)
 | |
| 	go bi.loadDebugInfoMaps(image, debugInfoBytes, debugLineBytes, wg, func() {
 | |
| 		// setGStructOffsetPE requires the image compile units to be loaded,
 | |
| 		// so it can't be called concurrently with loadDebugInfoMaps.
 | |
| 		if image.index == 0 {
 | |
| 			// determine g struct offset only when loading the executable file.
 | |
| 			bi.setGStructOffsetPE(entryPoint, peFile)
 | |
| 		}
 | |
| 	})
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) setGStructOffsetPE(entryPoint uint64, peFile *pe.File) {
 | |
| 	readtls_g := func() uint64 {
 | |
| 		for _, s := range peFile.Symbols {
 | |
| 			if s.Name == "runtime.tls_g" {
 | |
| 				i := int(s.SectionNumber) - 1
 | |
| 				if 0 <= i && i < len(peFile.Sections) {
 | |
| 					sect := peFile.Sections[i]
 | |
| 					if s.Value < sect.VirtualSize {
 | |
| 						return entryPoint + uint64(sect.VirtualAddress) + uint64(s.Value)
 | |
| 					}
 | |
| 				}
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		return 0
 | |
| 	}
 | |
| 	switch _PEMachine(peFile.Machine) {
 | |
| 	case _IMAGE_FILE_MACHINE_AMD64:
 | |
| 		producer := bi.Producer()
 | |
| 		if producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 20) {
 | |
| 			// Use runtime.tls_g as pointer to offset from GS to G struct:
 | |
| 			// https://go.dev/src/runtime/sys_windows_amd64.s
 | |
| 			bi.gStructOffset = readtls_g()
 | |
| 			bi.gStructOffsetIsPtr = true
 | |
| 		} else {
 | |
| 			// Use ArbitraryUserPointer (0x28) as pointer to pointer
 | |
| 			// to G struct per:
 | |
| 			// https://go.dev/src/runtime/cgo/gcc_windows_amd64.c
 | |
| 			bi.gStructOffset = 0x28
 | |
| 		}
 | |
| 	case _IMAGE_FILE_MACHINE_ARM64:
 | |
| 		// Use runtime.tls_g as pointer to offset from R18 to G struct:
 | |
| 		// https://go.dev/src/runtime/sys_windows_arm64.s
 | |
| 		bi.gStructOffset = readtls_g()
 | |
| 		bi.gStructOffsetIsPtr = true
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
 | |
| 	f, err := os.OpenFile(path, 0, os.ModePerm)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	peFile, err := pe.NewFile(f)
 | |
| 	if err != nil {
 | |
| 		f.Close()
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	return peFile, f, nil
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) parseDebugFramePE(image *Image, exe *pe.File, debugInfoBytes []byte, wg *sync.WaitGroup) {
 | |
| 	defer wg.Done()
 | |
| 
 | |
| 	debugFrameBytes, err := godwarf.GetDebugSectionPE(exe, "frame")
 | |
| 	bi.parseDebugFrameGeneral(image, debugFrameBytes, ".debug_frame", err, nil, 0, "", frame.DwarfEndian(debugInfoBytes))
 | |
| }
 | |
| 
 | |
| // MACH-O ////////////////////////////////////////////////////////////
 | |
| 
 | |
| // loadBinaryInfoMacho specifically loads information from a Mach-O binary.
 | |
| func loadBinaryInfoMacho(bi *BinaryInfo, image *Image, path string, entryPoint uint64, wg *sync.WaitGroup) error {
 | |
| 	exe, err := macho.Open(path)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if entryPoint != 0 {
 | |
| 		machoOff := uint64(0x100000000)
 | |
| 		for _, ld := range exe.Loads {
 | |
| 			if seg, _ := ld.(*macho.Segment); seg != nil {
 | |
| 				if seg.Name == "__TEXT" {
 | |
| 					machoOff = seg.Addr
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		logflags.DebuggerLogger().Debugf("entryPoint %#x machoOff %#x", entryPoint, machoOff)
 | |
| 		image.StaticBase = entryPoint - machoOff
 | |
| 	}
 | |
| 
 | |
| 	image.closer = exe
 | |
| 	if !supportedDarwinArch[exe.Cpu] {
 | |
| 		return &ErrUnsupportedArch{os: "darwin", cpuArch: exe.Cpu}
 | |
| 	}
 | |
| 	var dwerr error
 | |
| 	macOSShortSectionNamesWorkaround(exe)
 | |
| 	image.dwarf, dwerr = exe.DWARF()
 | |
| 	if dwerr != nil {
 | |
| 		if len(bi.Images) <= 1 {
 | |
| 			fmt.Fprintln(os.Stderr, "Warning: no debug info found, some functionality will be missing such as stack traces and variable evaluation.")
 | |
| 		}
 | |
| 		err := loadBinaryInfoGoRuntimeMacho(bi, image, path, exe)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("could not read debug info (%v) and could not read go symbol table (%v)", dwerr, err)
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 	debugInfoBytes, err := godwarf.GetDebugSectionMacho(exe, "info")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	image.dwarfReader = image.dwarf.Reader()
 | |
| 
 | |
| 	debugLineBytes, err := godwarf.GetDebugSectionMacho(exe, "line")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	debugLocBytes, _ := godwarf.GetDebugSectionMacho(exe, "loc")
 | |
| 	image.loclist2 = loclist.NewDwarf2Reader(debugLocBytes, bi.Arch.PtrSize())
 | |
| 	debugLoclistBytes, _ := godwarf.GetDebugSectionMacho(exe, "loclists")
 | |
| 	image.loclist5 = loclist.NewDwarf5Reader(debugLoclistBytes)
 | |
| 	debugAddrBytes, _ := godwarf.GetDebugSectionMacho(exe, "addr")
 | |
| 	image.debugAddr = godwarf.ParseAddr(debugAddrBytes)
 | |
| 	debugLineStrBytes, _ := godwarf.GetDebugSectionMacho(exe, "line_str")
 | |
| 	image.debugLineStr = debugLineStrBytes
 | |
| 
 | |
| 	wg.Add(2)
 | |
| 	go bi.parseDebugFrameMacho(image, exe, debugInfoBytes, wg)
 | |
| 	go bi.loadDebugInfoMaps(image, debugInfoBytes, debugLineBytes, wg, bi.setGStructOffsetMacho)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) setGStructOffsetMacho() {
 | |
| 	// In go1.11 it's 0x30, before 0x8a0, see:
 | |
| 	// https://github.com/golang/go/issues/23617
 | |
| 	// and go commit b3a854c733257c5249c3435ffcee194f8439676a
 | |
| 	producer := bi.Producer()
 | |
| 	if producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 11) {
 | |
| 		bi.gStructOffset = 0x30
 | |
| 		return
 | |
| 	}
 | |
| 	bi.gStructOffset = 0x8a0
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) parseDebugFrameMacho(image *Image, exe *macho.File, debugInfoBytes []byte, wg *sync.WaitGroup) {
 | |
| 	defer wg.Done()
 | |
| 
 | |
| 	debugFrameBytes, debugFrameErr := godwarf.GetDebugSectionMacho(exe, "frame")
 | |
| 	ehFrameSection := exe.Section("__eh_frame")
 | |
| 	var ehFrameBytes []byte
 | |
| 	var ehFrameAddr uint64
 | |
| 	if ehFrameSection != nil {
 | |
| 		ehFrameAddr = ehFrameSection.Addr
 | |
| 		ehFrameBytes, _ = ehFrameSection.Data()
 | |
| 	}
 | |
| 
 | |
| 	bi.parseDebugFrameGeneral(image, debugFrameBytes, "__debug_frame", debugFrameErr, ehFrameBytes, ehFrameAddr, "__eh_frame", frame.DwarfEndian(debugInfoBytes))
 | |
| }
 | |
| 
 | |
| // macOSDebugFrameBugWorkaround applies a workaround for [golang/go#25841]
 | |
| //
 | |
| // It finds the Go function with the lowest entry point and the first
 | |
| // debug_frame FDE, calculates the difference between the start of the
 | |
| // function and the start of the FDE and sums it to all debug_frame FDEs.
 | |
| // A number of additional checks are performed to make sure we don't ruin
 | |
| // executables unaffected by this bug.
 | |
| //
 | |
| // [golang/go#25841]: https://github.com/golang/go/issues/25841
 | |
| func (bi *BinaryInfo) macOSDebugFrameBugWorkaround() {
 | |
| 	if bi.GOOS != "darwin" {
 | |
| 		return
 | |
| 	}
 | |
| 	if len(bi.Images) > 1 {
 | |
| 		// Only do this for the first executable, but it might work for plugins as
 | |
| 		// well if we had a way to distinguish where entries in bi.frameEntries
 | |
| 		// come from
 | |
| 		return
 | |
| 	}
 | |
| 	exe, ok := bi.Images[0].closer.(*macho.File)
 | |
| 	if !ok {
 | |
| 		return
 | |
| 	}
 | |
| 	if bi.Arch.Name == "arm64" {
 | |
| 		if exe.Flags&macho.FlagPIE == 0 {
 | |
| 			bi.logger.Infof("debug_frame workaround not needed: not a PIE (%#x)", exe.Flags)
 | |
| 			return
 | |
| 		}
 | |
| 	} else {
 | |
| 		prod := goversion.ParseProducer(bi.Producer())
 | |
| 		if !prod.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 19, Rev: 3}) && !prod.IsOldDevel() {
 | |
| 			bi.logger.Infof("debug_frame workaround not needed (version %q on %s)", bi.Producer(), bi.Arch.Name)
 | |
| 			return
 | |
| 		}
 | |
| 		found := false
 | |
| 		for i := range bi.frameEntries {
 | |
| 			if bi.frameEntries[i].CIE.CIE_id == ^uint32(0) && bi.frameEntries[i].Begin() < 0x4000000 {
 | |
| 				found = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			bi.logger.Infof("debug_frame workaround not needed (all FDEs above 0x4000000)")
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Find first Go function (first = lowest entry point)
 | |
| 	var fn *Function
 | |
| 	for i := range bi.Functions {
 | |
| 		if bi.Functions[i].cu.isgo && bi.Functions[i].Entry > 0 {
 | |
| 			fn = &bi.Functions[i]
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if fn == nil {
 | |
| 		bi.logger.Warn("debug_frame workaround not applied: could not find a Go function")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if fde, _ := bi.frameEntries.FDEForPC(fn.Entry); fde != nil {
 | |
| 		// Function is covered, no need to apply workaround
 | |
| 		bi.logger.Warnf("debug_frame workaround not applied: function %s (at %#x) covered by %#x-%#x", fn.Name, fn.Entry, fde.Begin(), fde.End())
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Find lowest FDE in debug_frame
 | |
| 	var fde *frame.FrameDescriptionEntry
 | |
| 	for i := range bi.frameEntries {
 | |
| 		if bi.frameEntries[i].CIE.CIE_id == ^uint32(0) {
 | |
| 			fde = bi.frameEntries[i]
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if fde == nil {
 | |
| 		bi.logger.Warnf("debug_frame workaround not applied because there are no debug_frame entries (%d)", len(bi.frameEntries))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	fnsize := fn.End - fn.Entry
 | |
| 
 | |
| 	if fde.End()-fde.Begin() != fnsize || fde.Begin() > fn.Entry {
 | |
| 		bi.logger.Warnf("debug_frame workaround not applied: function %s (at %#x-%#x) has a different size than the first FDE (%#x-%#x) (or the FDE starts after the function)", fn.Name, fn.Entry, fn.End, fde.Begin(), fde.End())
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	delta := fn.Entry - fde.Begin()
 | |
| 
 | |
| 	bi.logger.Infof("applying debug_frame workaround +%#x: function %s (at %#x-%#x) and FDE %#x-%#x", delta, fn.Name, fn.Entry, fn.End, fde.Begin(), fde.End())
 | |
| 
 | |
| 	for i := range bi.frameEntries {
 | |
| 		if bi.frameEntries[i].CIE.CIE_id == ^uint32(0) {
 | |
| 			bi.frameEntries[i].Translate(delta)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // macOSShortSectionNamesWorkaround works around a bug in Go 1.23 (and
 | |
| // earlier).
 | |
| // Section names in Macho-O executables are limited to 16 characters, which
 | |
| // means that some DWARF sections with long names will be truncated. Go 1.23
 | |
| // and prior do not take into account this making the DWARF info sometimes
 | |
| // unreadable.
 | |
| // This bug only manifests on macOS 15 because the C toolchain of prior
 | |
| // versions of the operating system did not emit problematic DWARF sections.
 | |
| // See also https://github.com/go-delve/delve/issues/3797
 | |
| func macOSShortSectionNamesWorkaround(exe *macho.File) {
 | |
| 	for _, sec := range exe.Sections {
 | |
| 		if sec == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		for _, longname := range []string{
 | |
| 			"__debug_str_offsets",
 | |
| 			"__zdebug_line_str",
 | |
| 			"__zdebug_loclists",
 | |
| 			"__zdebug_pubnames",
 | |
| 			"__zdebug_pubtypes",
 | |
| 			"__zdebug_rnglists",
 | |
| 			"__zdebug_str_offsets",
 | |
| 		} {
 | |
| 			if sec.Name == longname[:16] {
 | |
| 				logflags.DebuggerLogger().Debugf("expanding section name %q to %q", sec.Name, longname)
 | |
| 				sec.Name = longname
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // GO RUNTIME INFO ////////////////////////////////////////////////////////////
 | |
| 
 | |
| // loadBinaryInfoGoRuntimeElf loads information from the Go runtime sections
 | |
| // of an ELF binary, it is only called when debug info has been stripped.
 | |
| func loadBinaryInfoGoRuntimeElf(bi *BinaryInfo, image *Image, path string, elfFile *elf.File) (err error) {
 | |
| 	// This is a best-effort procedure, it can go wrong in unexpected ways, so
 | |
| 	// recover all panics.
 | |
| 	defer func() {
 | |
| 		ierr := recover()
 | |
| 		if ierr != nil {
 | |
| 			err = fmt.Errorf("error loading binary info from Go runtime: %v", ierr)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	cu := &compileUnit{}
 | |
| 	cu.image = image
 | |
| 	symTable, symTabAddr, err := readPcLnTableElf(elfFile, path)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	image.symTable = symTable
 | |
| 	noPtrSectionData, err := elfFile.Section(".noptrdata").Data()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	md, err := parseModuleData(noPtrSectionData, symTabAddr)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	roDataAddr := elfFile.Section(".rodata").Addr
 | |
| 	goFuncVal, err := findGoFuncVal(md, roDataAddr, bi.Arch.ptrSize)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	prog := gosym.ProgContaining(elfFile, goFuncVal)
 | |
| 	var progAddr uint64
 | |
| 	var progReaderAt io.ReaderAt
 | |
| 	if prog != nil {
 | |
| 		progAddr = prog.Vaddr
 | |
| 		progReaderAt = prog.ReaderAt
 | |
| 	}
 | |
| 	return loadBinaryInfoGoRuntimeCommon(bi, image, cu, goFuncVal, progAddr, progReaderAt)
 | |
| }
 | |
| 
 | |
| // loadBinaryInfoGoRuntimeMacho loads information from the Go runtime sections
 | |
| // of an Macho-o binary, it is only called when debug info has been stripped.
 | |
| func loadBinaryInfoGoRuntimeMacho(bi *BinaryInfo, image *Image, path string, exe *macho.File) (err error) {
 | |
| 	// This is a best-effort procedure, it can go wrong in unexpected ways, so
 | |
| 	// recover all panics.
 | |
| 	defer func() {
 | |
| 		ierr := recover()
 | |
| 		if ierr != nil {
 | |
| 			err = fmt.Errorf("error loading binary info from Go runtime: %v", ierr)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	cu := &compileUnit{}
 | |
| 	cu.image = image
 | |
| 	symTable, symTabAddr, err := readPcLnTableMacho(exe, path)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	image.symTable = symTable
 | |
| 	noPtrSectionData, err := exe.Section("__noptrdata").Data()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	md, err := parseModuleData(noPtrSectionData, symTabAddr)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	roDataAddr := exe.Section("__rodata").Addr
 | |
| 	goFuncVal, err := findGoFuncVal(md, roDataAddr, bi.Arch.ptrSize)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	seg := gosym.SegmentContaining(exe, goFuncVal)
 | |
| 	var segAddr uint64
 | |
| 	var segReaderAt io.ReaderAt
 | |
| 	if seg != nil {
 | |
| 		segAddr = seg.Addr
 | |
| 		segReaderAt = seg.ReaderAt
 | |
| 	}
 | |
| 	return loadBinaryInfoGoRuntimeCommon(bi, image, cu, goFuncVal, segAddr, segReaderAt)
 | |
| }
 | |
| 
 | |
| func loadBinaryInfoGoRuntimeCommon(bi *BinaryInfo, image *Image, cu *compileUnit, goFuncVal uint64, goFuncSegAddr uint64, goFuncReader io.ReaderAt) error {
 | |
| 	inlFuncs := make(map[string]*Function)
 | |
| 	for _, f := range image.symTable.Funcs {
 | |
| 		fnEntry := f.Entry + image.StaticBase
 | |
| 		if goFuncReader != nil {
 | |
| 			inlCalls, err := image.symTable.GetInlineTree(&f, goFuncVal, goFuncSegAddr, goFuncReader)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			for _, inlfn := range inlCalls {
 | |
| 				newInlinedCall := InlinedCall{cu: cu, LowPC: fnEntry + uint64(inlfn.ParentPC)}
 | |
| 				if fn, ok := inlFuncs[inlfn.Name]; ok {
 | |
| 					fn.InlinedCalls = append(fn.InlinedCalls, newInlinedCall)
 | |
| 					continue
 | |
| 				}
 | |
| 				inlFuncs[inlfn.Name] = &Function{
 | |
| 					Name:  inlfn.Name,
 | |
| 					Entry: 0, End: 0,
 | |
| 					cu: cu,
 | |
| 					InlinedCalls: []InlinedCall{
 | |
| 						newInlinedCall,
 | |
| 					},
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		fn := Function{Name: f.Name, Entry: fnEntry, End: f.End + image.StaticBase, cu: cu}
 | |
| 		bi.Functions = append(bi.Functions, fn)
 | |
| 	}
 | |
| 	for i := range inlFuncs {
 | |
| 		bi.Functions = append(bi.Functions, *inlFuncs[i])
 | |
| 	}
 | |
| 	sort.Sort(functionsDebugInfoByEntry(bi.Functions))
 | |
| 	for f := range image.symTable.Files {
 | |
| 		bi.Sources = append(bi.Sources, f)
 | |
| 	}
 | |
| 	sort.Strings(bi.Sources)
 | |
| 	bi.Sources = slices.Compact(bi.Sources)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Do not call this function directly it isn't able to deal correctly with package paths
 | |
| func (bi *BinaryInfo) findType(name string) (godwarf.Type, error) {
 | |
| 	name = strings.ReplaceAll(name, "interface{", "interface {")
 | |
| 	name = strings.ReplaceAll(name, "struct{", "struct {")
 | |
| 	ref, found := bi.types[name]
 | |
| 	if !found {
 | |
| 		return nil, reader.ErrTypeNotFound
 | |
| 	}
 | |
| 	image := bi.Images[ref.imageIndex]
 | |
| 	return godwarf.ReadType(image.dwarf, ref.imageIndex, ref.offset, image.typeCache)
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) findTypeExpr(expr ast.Expr) (godwarf.Type, error) {
 | |
| 	if lit, islit := expr.(*ast.BasicLit); islit && lit.Kind == token.STRING {
 | |
| 		// Allow users to specify type names verbatim as quoted
 | |
| 		// string. Useful as a catch-all workaround for cases where we don't
 | |
| 		// parse/serialize types correctly or can not resolve package paths.
 | |
| 		typn, _ := strconv.Unquote(lit.Value)
 | |
| 
 | |
| 		// Check if the type in question is an array type, in which case we try to
 | |
| 		// fake it.
 | |
| 		if len(typn) > 0 && typn[0] == '[' {
 | |
| 			closedBrace := strings.Index(typn, "]")
 | |
| 			if closedBrace > 1 {
 | |
| 				n, err := strconv.Atoi(typn[1:closedBrace])
 | |
| 				if err == nil {
 | |
| 					return bi.findArrayType(n, typn[closedBrace+1:])
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return bi.findType(typn)
 | |
| 	}
 | |
| 	bi.expandPackagesInType(expr)
 | |
| 	if snode, ok := expr.(*ast.StarExpr); ok {
 | |
| 		// Pointer types only appear in the dwarf information when
 | |
| 		// a pointer to the type is used in the target program, here
 | |
| 		// we create a pointer type on the fly so that the user can
 | |
| 		// specify a pointer to any variable used in the target program
 | |
| 		ptyp, err := bi.findTypeExpr(snode.X)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return pointerTo(ptyp, bi.Arch), nil
 | |
| 	}
 | |
| 	if anode, ok := expr.(*ast.ArrayType); ok {
 | |
| 		// Array types (for example [N]byte) are only present in DWARF if they are
 | |
| 		// used by the program, but it's convenient to make all of them available
 | |
| 		// to the user for two reasons:
 | |
| 		// 1. to allow reading arbitrary memory byte-by-byte (by casting an
 | |
| 		//    address to an array of bytes).
 | |
| 		// 2. to read the contents of a channel's buffer (we create fake array
 | |
| 		//    types for them)
 | |
| 
 | |
| 		alen, litlen := anode.Len.(*ast.BasicLit)
 | |
| 		if litlen && alen.Kind == token.INT {
 | |
| 			n, _ := strconv.Atoi(alen.Value)
 | |
| 			return bi.findArrayType(n, exprToString(anode.Elt))
 | |
| 		}
 | |
| 	}
 | |
| 	return bi.findType(exprToString(expr))
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) findArrayType(n int, etyp string) (godwarf.Type, error) {
 | |
| 	switch etyp {
 | |
| 	case "byte", "uint8":
 | |
| 		etyp = "uint8"
 | |
| 		fallthrough
 | |
| 	default:
 | |
| 		btyp, err := bi.findType(etyp)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return fakeArrayType(uint64(n), btyp), nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func complexType(typename string) bool {
 | |
| 	for _, ch := range typename {
 | |
| 		switch ch {
 | |
| 		case '*', '[', '<', '{', '(', ' ':
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) registerTypeToPackageMap(entry *dwarf.Entry) {
 | |
| 	if entry.Tag != dwarf.TagTypedef && entry.Tag != dwarf.TagBaseType && entry.Tag != dwarf.TagClassType && entry.Tag != dwarf.TagStructType {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	typename, ok := entry.Val(dwarf.AttrName).(string)
 | |
| 	if !ok || complexType(typename) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	dot := strings.LastIndex(typename, ".")
 | |
| 	if dot < 0 {
 | |
| 		return
 | |
| 	}
 | |
| 	path := typename[:dot]
 | |
| 	slash := strings.LastIndex(path, "/")
 | |
| 	if slash < 0 || slash+1 >= len(path) {
 | |
| 		return
 | |
| 	}
 | |
| 	name := path[slash+1:]
 | |
| 	bi.PackageMap[name] = []string{path}
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineBytes []byte, wg *sync.WaitGroup, cont func()) {
 | |
| 	if wg != nil {
 | |
| 		defer wg.Done()
 | |
| 	}
 | |
| 
 | |
| 	if bi.types == nil {
 | |
| 		bi.types = make(map[string]dwarfRef)
 | |
| 	}
 | |
| 	if bi.consts == nil {
 | |
| 		bi.consts = make(map[dwarfRef]*constantType)
 | |
| 	}
 | |
| 	if bi.PackageMap == nil {
 | |
| 		bi.PackageMap = make(map[string][]string)
 | |
| 	}
 | |
| 	if bi.inlinedCallLines == nil {
 | |
| 		bi.inlinedCallLines = make(map[fileLine][]uint64)
 | |
| 	}
 | |
| 	if bi.dwrapUnwrapCache == nil {
 | |
| 		bi.dwrapUnwrapCache = make(map[uint64]*Function)
 | |
| 	}
 | |
| 
 | |
| 	image.runtimeTypeToDIE = make(map[uint64]runtimeTypeDIE)
 | |
| 
 | |
| 	ctxt := newLoadDebugInfoMapsContext(bi, image, pdwarf.ReadUnitVersions(debugInfoBytes))
 | |
| 
 | |
| 	reader := image.DwarfReader()
 | |
| 
 | |
| 	for {
 | |
| 		entry, err := reader.Next()
 | |
| 		if err != nil {
 | |
| 			image.setLoadError(bi.logger, "error reading debug_info: %v", err)
 | |
| 			break
 | |
| 		}
 | |
| 		if entry == nil {
 | |
| 			break
 | |
| 		}
 | |
| 		switch entry.Tag {
 | |
| 		case dwarf.TagCompileUnit:
 | |
| 			cu := &compileUnit{}
 | |
| 			cu.image = image
 | |
| 			cu.entry = entry
 | |
| 			cu.offset = entry.Offset
 | |
| 			cu.Version = ctxt.offsetToVersion[cu.offset]
 | |
| 			if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage {
 | |
| 				cu.isgo = true
 | |
| 			}
 | |
| 			cu.name, _ = entry.Val(dwarf.AttrName).(string)
 | |
| 			compdir, _ := entry.Val(dwarf.AttrCompDir).(string)
 | |
| 			if compdir != "" {
 | |
| 				cu.name = filepath.Join(compdir, cu.name)
 | |
| 			}
 | |
| 			cu.ranges, _ = image.dwarf.Ranges(entry)
 | |
| 			for i := range cu.ranges {
 | |
| 				cu.ranges[i][0] += image.StaticBase
 | |
| 				cu.ranges[i][1] += image.StaticBase
 | |
| 			}
 | |
| 			if len(cu.ranges) >= 1 {
 | |
| 				cu.lowPC = cu.ranges[0][0]
 | |
| 			}
 | |
| 			lineInfoOffset, hasLineInfo := entry.Val(dwarf.AttrStmtList).(int64)
 | |
| 			if hasLineInfo && lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) {
 | |
| 				var logfn func(string, ...interface{})
 | |
| 				if logflags.DebugLineErrors() {
 | |
| 					logfn = logflags.DebugLineLogger().Debugf
 | |
| 				}
 | |
| 				cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), image.debugLineStr, logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize())
 | |
| 			}
 | |
| 			cu.producer, _ = entry.Val(dwarf.AttrProducer).(string)
 | |
| 			if cu.isgo && cu.producer != "" {
 | |
| 				semicolon := strings.Index(cu.producer, ";")
 | |
| 				if semicolon < 0 {
 | |
| 					cu.optimized = 0
 | |
| 					if goversion.ProducerAfterOrEqual(cu.producer, 1, 10) {
 | |
| 						cu.optimized = optimizedInlined | optimizedOptimized
 | |
| 					}
 | |
| 				} else {
 | |
| 					cu.optimized = optimizedInlined | optimizedOptimized
 | |
| 					if strings.Contains(cu.producer[semicolon:], "-N") {
 | |
| 						cu.optimized &^= optimizedOptimized
 | |
| 					}
 | |
| 					if strings.Contains(cu.producer[semicolon:], "-l") {
 | |
| 						cu.optimized &^= optimizedInlined
 | |
| 					}
 | |
| 					const regabi = " regabi"
 | |
| 					if i := strings.Index(cu.producer[semicolon:], regabi); i > 0 {
 | |
| 						i += semicolon
 | |
| 						if i+len(regabi) >= len(cu.producer) || cu.producer[i+len(regabi)] == ' ' {
 | |
| 							bi.regabi = true
 | |
| 						}
 | |
| 					}
 | |
| 					cu.producer = cu.producer[:semicolon]
 | |
| 				}
 | |
| 			}
 | |
| 			gopkg, _ := entry.Val(godwarf.AttrGoPackageName).(string)
 | |
| 			if cu.isgo && gopkg != "" {
 | |
| 				bi.PackageMap[gopkg] = append(bi.PackageMap[gopkg], escapePackagePath(strings.ReplaceAll(cu.name, "\\", "/")))
 | |
| 			}
 | |
| 			image.compileUnits = append(image.compileUnits, cu)
 | |
| 			if entry.Children {
 | |
| 				bi.loadDebugInfoMapsCompileUnit(ctxt, image, reader, cu)
 | |
| 			}
 | |
| 
 | |
| 		case dwarf.TagPartialUnit:
 | |
| 			reader.SkipChildren()
 | |
| 
 | |
| 		default:
 | |
| 			// ignore unknown tags
 | |
| 			reader.SkipChildren()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	sort.Sort(compileUnitsByOffset(image.compileUnits))
 | |
| 	sort.Sort(functionsDebugInfoByEntry(bi.Functions))
 | |
| 	sort.Sort(packageVarsByAddr(bi.packageVars))
 | |
| 
 | |
| 	bi.lookupFunc = nil
 | |
| 	bi.lookupGenericFunc = nil
 | |
| 
 | |
| 	for _, cu := range image.compileUnits {
 | |
| 		if cu.lineInfo != nil {
 | |
| 			for _, fileEntry := range cu.lineInfo.FileNames {
 | |
| 				bi.Sources = append(bi.Sources, fileEntry.Path)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	sort.Strings(bi.Sources)
 | |
| 	bi.Sources = slices.Compact(bi.Sources)
 | |
| 
 | |
| 	if cont != nil {
 | |
| 		cont()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // LookupGenericFunc returns a map that allows searching for instantiations of generic function by specifying a function name without type parameters.
 | |
| // For example the key "pkg.(*Receiver).Amethod" will find all instantiations of Amethod:
 | |
| //   - pkg.(*Receiver[.shape.int]).Amethod
 | |
| //   - pkg.(*Receiver[.shape.*uint8]).Amethod
 | |
| //   - etc.
 | |
| func (bi *BinaryInfo) LookupGenericFunc() map[string][]*Function {
 | |
| 	if bi.lookupGenericFunc == nil {
 | |
| 		bi.lookupGenericFunc = make(map[string][]*Function)
 | |
| 		for i := range bi.Functions {
 | |
| 			dn := bi.Functions[i].NameWithoutTypeParams()
 | |
| 			if dn != bi.Functions[i].Name {
 | |
| 				bi.lookupGenericFunc[dn] = append(bi.lookupGenericFunc[dn], &bi.Functions[i])
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return bi.lookupGenericFunc
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) LookupFunc() map[string][]*Function {
 | |
| 	if bi.lookupFunc == nil {
 | |
| 		bi.lookupFunc = make(map[string][]*Function)
 | |
| 		for i := range bi.Functions {
 | |
| 			name := bi.Functions[i].Name
 | |
| 			bi.lookupFunc[name] = append(bi.lookupFunc[name], &bi.Functions[i])
 | |
| 		}
 | |
| 	}
 | |
| 	return bi.lookupFunc
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) lookupOneFunc(name string) *Function {
 | |
| 	if name == evalop.DebugPinnerFunctionName && bi.debugPinnerFn != nil {
 | |
| 		return bi.debugPinnerFn
 | |
| 	}
 | |
| 	fns := bi.LookupFunc()[name]
 | |
| 	if fns == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if name == evalop.DebugPinnerFunctionName {
 | |
| 		bi.debugPinnerFn = fns[0]
 | |
| 	}
 | |
| 	return fns[0]
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) hasDebugPinner() bool {
 | |
| 	return bi.lookupOneFunc(evalop.DebugPinnerFunctionName) != nil
 | |
| }
 | |
| 
 | |
| // loadDebugInfoMapsCompileUnit loads entry from a single compile unit.
 | |
| func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) {
 | |
| 	hasAttrGoPkgName := goversion.ProducerAfterOrEqual(cu.producer, 1, 13)
 | |
| 
 | |
| 	depth := 0
 | |
| 
 | |
| 	for {
 | |
| 		entry, err := reader.Next()
 | |
| 		if err != nil {
 | |
| 			image.setLoadError(bi.logger, "error reading debug_info: %v", err)
 | |
| 			return
 | |
| 		}
 | |
| 		if entry == nil {
 | |
| 			break
 | |
| 		}
 | |
| 		switch entry.Tag {
 | |
| 		case 0:
 | |
| 			if depth == 0 {
 | |
| 				return
 | |
| 			} else {
 | |
| 				depth--
 | |
| 			}
 | |
| 		case dwarf.TagImportedUnit:
 | |
| 			bi.loadDebugInfoMapsImportedUnit(entry, ctxt, image, cu)
 | |
| 			reader.SkipChildren()
 | |
| 
 | |
| 		case dwarf.TagArrayType, dwarf.TagBaseType, dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType, dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType, dwarf.TagEnumerationType, dwarf.TagPointerType, dwarf.TagSubroutineType, dwarf.TagTypedef, dwarf.TagUnspecifiedType:
 | |
| 			if name, ok := entry.Val(dwarf.AttrName).(string); ok {
 | |
| 				if !cu.isgo {
 | |
| 					name = "C." + name
 | |
| 				}
 | |
| 				if _, exists := bi.types[name]; !exists {
 | |
| 					bi.types[name] = dwarfRef{image.index, entry.Offset}
 | |
| 				}
 | |
| 			}
 | |
| 			if cu != nil && cu.isgo && !hasAttrGoPkgName {
 | |
| 				bi.registerTypeToPackageMap(entry)
 | |
| 			}
 | |
| 			image.registerRuntimeTypeToDIE(entry, ctxt.ardr)
 | |
| 			reader.SkipChildren()
 | |
| 
 | |
| 		case dwarf.TagVariable:
 | |
| 			if n, ok := entry.Val(dwarf.AttrName).(string); ok {
 | |
| 				var addr uint64
 | |
| 				if loc, ok := entry.Val(dwarf.AttrLocation).([]byte); ok {
 | |
| 					if len(loc) == bi.Arch.PtrSize()+1 && op.Opcode(loc[0]) == op.DW_OP_addr {
 | |
| 						addr, _ = pdwarf.ReadUintRaw(bytes.NewReader(loc[1:]), binary.LittleEndian, bi.Arch.PtrSize())
 | |
| 					}
 | |
| 				}
 | |
| 				if !cu.isgo {
 | |
| 					n = "C." + n
 | |
| 				}
 | |
| 				if _, known := ctxt.knownPackageVars[n]; !known {
 | |
| 					bi.packageVars = append(bi.packageVars, packageVar{n, cu, entry.Offset, addr + image.StaticBase})
 | |
| 				}
 | |
| 			}
 | |
| 			reader.SkipChildren()
 | |
| 
 | |
| 		case dwarf.TagConstant:
 | |
| 			name, okName := entry.Val(dwarf.AttrName).(string)
 | |
| 			typ, okType := entry.Val(dwarf.AttrType).(dwarf.Offset)
 | |
| 			val, okVal := entry.Val(dwarf.AttrConstValue).(int64)
 | |
| 			if okName && okType && okVal {
 | |
| 				if !cu.isgo {
 | |
| 					name = "C." + name
 | |
| 				}
 | |
| 				ct := bi.consts[dwarfRef{image.index, typ}]
 | |
| 				if ct == nil {
 | |
| 					ct = &constantType{}
 | |
| 					bi.consts[dwarfRef{image.index, typ}] = ct
 | |
| 				}
 | |
| 				ct.values = append(ct.values, constantValue{name: name, fullName: name, value: val})
 | |
| 			}
 | |
| 			reader.SkipChildren()
 | |
| 
 | |
| 		case dwarf.TagSubprogram:
 | |
| 			inlined := false
 | |
| 			if inval, ok := entry.Val(dwarf.AttrInline).(int64); ok {
 | |
| 				inlined = inval >= 1
 | |
| 			}
 | |
| 
 | |
| 			if inlined {
 | |
| 				bi.addAbstractSubprogram(entry, ctxt, reader, image, cu)
 | |
| 			} else {
 | |
| 				originOffset, hasAbstractOrigin := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
 | |
| 				if hasAbstractOrigin {
 | |
| 					bi.addConcreteInlinedSubprogram(entry, originOffset, ctxt, reader, cu)
 | |
| 				} else {
 | |
| 					bi.addConcreteSubprogram(entry, ctxt, reader, cu)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 		default:
 | |
| 			if entry.Children {
 | |
| 				depth++
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // loadDebugInfoMapsImportedUnit loads entries into cu from the partial unit
 | |
| // referenced in a DW_TAG_imported_unit entry.
 | |
| func (bi *BinaryInfo) loadDebugInfoMapsImportedUnit(entry *dwarf.Entry, ctxt *loadDebugInfoMapsContext, image *Image, cu *compileUnit) {
 | |
| 	off, ok := entry.Val(dwarf.AttrImport).(dwarf.Offset)
 | |
| 	if !ok {
 | |
| 		return
 | |
| 	}
 | |
| 	reader := image.DwarfReader()
 | |
| 	reader.Seek(off)
 | |
| 	imentry, err := reader.Next()
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	if imentry.Tag != dwarf.TagPartialUnit {
 | |
| 		return
 | |
| 	}
 | |
| 	bi.loadDebugInfoMapsCompileUnit(ctxt, image, reader, cu)
 | |
| }
 | |
| 
 | |
| // addAbstractSubprogram adds the abstract entry for an inlined function.
 | |
| func (bi *BinaryInfo) addAbstractSubprogram(entry *dwarf.Entry, ctxt *loadDebugInfoMapsContext, reader *reader.Reader, image *Image, cu *compileUnit) {
 | |
| 	name, ok := subprogramEntryName(entry, cu)
 | |
| 	if !ok {
 | |
| 		bi.logger.Warnf("reading debug_info: abstract subprogram without name at %#x", entry.Offset)
 | |
| 		// In some cases clang produces abstract subprograms that do not have a
 | |
| 		// name, but we should process them anyway.
 | |
| 	}
 | |
| 
 | |
| 	if entry.Children {
 | |
| 		bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu)
 | |
| 	}
 | |
| 
 | |
| 	originIdx := ctxt.lookupAbstractOrigin(bi, entry.Offset)
 | |
| 	fn := &bi.Functions[originIdx]
 | |
| 	fn.Name = name
 | |
| 	fn.offset = entry.Offset
 | |
| 	fn.cu = cu
 | |
| }
 | |
| 
 | |
| // addConcreteInlinedSubprogram adds the concrete entry of a subprogram that was also inlined.
 | |
| func (bi *BinaryInfo) addConcreteInlinedSubprogram(entry *dwarf.Entry, originOffset dwarf.Offset, ctxt *loadDebugInfoMapsContext, reader *reader.Reader, cu *compileUnit) {
 | |
| 	lowpc, highpc, ok := subprogramEntryRange(entry, cu.image)
 | |
| 	if !ok {
 | |
| 		bi.logger.Warnf("reading debug_info: concrete inlined subprogram without address range at %#x", entry.Offset)
 | |
| 		if entry.Children {
 | |
| 			reader.SkipChildren()
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	originIdx := ctxt.lookupAbstractOrigin(bi, originOffset)
 | |
| 	fn := &bi.Functions[originIdx]
 | |
| 	fn.offset = entry.Offset
 | |
| 	fn.Entry = lowpc
 | |
| 	fn.End = highpc
 | |
| 	fn.cu = cu
 | |
| 
 | |
| 	if entry.Children {
 | |
| 		bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // addConcreteSubprogram adds a concrete subprogram (a normal subprogram
 | |
| // that doesn't have abstract or inlined entries)
 | |
| func (bi *BinaryInfo) addConcreteSubprogram(entry *dwarf.Entry, ctxt *loadDebugInfoMapsContext, reader *reader.Reader, cu *compileUnit) {
 | |
| 	lowpc, highpc, ok := subprogramEntryRange(entry, cu.image)
 | |
| 	if !ok {
 | |
| 		bi.logger.Warnf("reading debug_info: concrete subprogram without address range at %#x", entry.Offset)
 | |
| 		// When clang inlines a function, in some cases, it produces a concrete
 | |
| 		// subprogram without address range and then inlined calls that reference
 | |
| 		// it, instead of producing an abstract subprogram.
 | |
| 		// It is unclear if this behavior is standard.
 | |
| 	}
 | |
| 
 | |
| 	name, ok := subprogramEntryName(entry, cu)
 | |
| 	if !ok {
 | |
| 		bi.logger.Warnf("reading debug_info: concrete subprogram without name at %#x", entry.Offset)
 | |
| 	}
 | |
| 
 | |
| 	trampoline, _ := entry.Val(dwarf.AttrTrampoline).(bool)
 | |
| 
 | |
| 	originIdx := ctxt.lookupAbstractOrigin(bi, entry.Offset)
 | |
| 	fn := &bi.Functions[originIdx]
 | |
| 
 | |
| 	fn.Name = name
 | |
| 	fn.Entry = lowpc
 | |
| 	fn.End = highpc
 | |
| 	fn.offset = entry.Offset
 | |
| 	fn.cu = cu
 | |
| 	fn.trampoline = trampoline
 | |
| 
 | |
| 	if entry.Children {
 | |
| 		bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func subprogramEntryName(entry *dwarf.Entry, cu *compileUnit) (string, bool) {
 | |
| 	name, ok := entry.Val(dwarf.AttrName).(string)
 | |
| 	if !ok {
 | |
| 		return "", false
 | |
| 	}
 | |
| 	if !cu.isgo {
 | |
| 		name = "C." + name
 | |
| 	}
 | |
| 	return name, true
 | |
| }
 | |
| 
 | |
| func subprogramEntryRange(entry *dwarf.Entry, image *Image) (lowpc, highpc uint64, ok bool) {
 | |
| 	ok = false
 | |
| 	if ranges, _ := image.dwarf.Ranges(entry); len(ranges) >= 1 {
 | |
| 		ok = true
 | |
| 		lowpc = ranges[0][0] + image.StaticBase
 | |
| 		highpc = ranges[0][1] + image.StaticBase
 | |
| 	}
 | |
| 	return lowpc, highpc, ok
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) loadDebugInfoMapsInlinedCalls(ctxt *loadDebugInfoMapsContext, reader *reader.Reader, cu *compileUnit) {
 | |
| 	for {
 | |
| 		entry, err := reader.Next()
 | |
| 		if err != nil {
 | |
| 			cu.image.setLoadError(bi.logger, "error reading debug_info: %v", err)
 | |
| 			return
 | |
| 		}
 | |
| 		switch entry.Tag {
 | |
| 		case 0:
 | |
| 			return
 | |
| 		case dwarf.TagInlinedSubroutine:
 | |
| 			originOffset, ok := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
 | |
| 			if !ok {
 | |
| 				bi.logger.Warnf("reading debug_info: inlined call without origin offset at %#x", entry.Offset)
 | |
| 				reader.SkipChildren()
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			lowpc, highpc, ok := subprogramEntryRange(entry, cu.image)
 | |
| 			if !ok {
 | |
| 				bi.logger.Warnf("reading debug_info: inlined call without address range at %#x", entry.Offset)
 | |
| 				reader.SkipChildren()
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			callfileidx, ok1 := entry.Val(dwarf.AttrCallFile).(int64)
 | |
| 			callline, ok2 := entry.Val(dwarf.AttrCallLine).(int64)
 | |
| 			if !ok1 || !ok2 {
 | |
| 				bi.logger.Warnf("reading debug_info: inlined call without CallFile/CallLine at %#x", entry.Offset)
 | |
| 				reader.SkipChildren()
 | |
| 				continue
 | |
| 			}
 | |
| 			callfile, cferr := cu.filePath(int(callfileidx), entry)
 | |
| 			if cferr != nil {
 | |
| 				bi.logger.Warnf("%v", cferr)
 | |
| 				reader.SkipChildren()
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			originIdx := ctxt.lookupAbstractOrigin(bi, originOffset)
 | |
| 			fn := &bi.Functions[originIdx]
 | |
| 
 | |
| 			fn.InlinedCalls = append(fn.InlinedCalls, InlinedCall{
 | |
| 				cu:     cu,
 | |
| 				LowPC:  lowpc,
 | |
| 				HighPC: highpc,
 | |
| 			})
 | |
| 
 | |
| 			if fn.cu == nil {
 | |
| 				fn.cu = cu
 | |
| 			}
 | |
| 
 | |
| 			fl := fileLine{callfile, int(callline)}
 | |
| 			bi.inlinedCallLines[fl] = append(bi.inlinedCallLines[fl], lowpc)
 | |
| 
 | |
| 			if entry.Children {
 | |
| 				bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu)
 | |
| 			}
 | |
| 		}
 | |
| 		reader.SkipChildren()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (bi *BinaryInfo) expandPackagesInType(expr ast.Expr) {
 | |
| 	switch e := expr.(type) {
 | |
| 	case *ast.ArrayType:
 | |
| 		bi.expandPackagesInType(e.Elt)
 | |
| 	case *ast.ChanType:
 | |
| 		bi.expandPackagesInType(e.Value)
 | |
| 	case *ast.FuncType:
 | |
| 		for i := range e.Params.List {
 | |
| 			bi.expandPackagesInType(e.Params.List[i].Type)
 | |
| 		}
 | |
| 		if e.Results != nil {
 | |
| 			for i := range e.Results.List {
 | |
| 				bi.expandPackagesInType(e.Results.List[i].Type)
 | |
| 			}
 | |
| 		}
 | |
| 	case *ast.MapType:
 | |
| 		bi.expandPackagesInType(e.Key)
 | |
| 		bi.expandPackagesInType(e.Value)
 | |
| 	case *ast.ParenExpr:
 | |
| 		bi.expandPackagesInType(e.X)
 | |
| 	case *ast.SelectorExpr:
 | |
| 		switch x := e.X.(type) {
 | |
| 		case *ast.Ident:
 | |
| 			if len(bi.PackageMap[x.Name]) > 0 {
 | |
| 				// There's no particular reason to expect the first entry to be the
 | |
| 				// correct one if the package name is ambiguous, but trying all possible
 | |
| 				// expansions of all types mentioned in the expression is complicated
 | |
| 				// and, besides type assertions, users can always specify the type they
 | |
| 				// want exactly, using a string.
 | |
| 				x.Name = bi.PackageMap[x.Name][0]
 | |
| 			}
 | |
| 		default:
 | |
| 			bi.expandPackagesInType(e.X)
 | |
| 		}
 | |
| 	case *ast.StarExpr:
 | |
| 		bi.expandPackagesInType(e.X)
 | |
| 	default:
 | |
| 		// nothing to do
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // escapePackagePath returns pkg with '.' replaced with '%2e' (in all
 | |
| // elements of the path except the first one) like Go does in variable and
 | |
| // type names.
 | |
| func escapePackagePath(pkg string) string {
 | |
| 	slash := strings.Index(pkg, "/")
 | |
| 	if slash < 0 {
 | |
| 		slash = 0
 | |
| 	}
 | |
| 	return pkg[:slash] + strings.ReplaceAll(pkg[slash:], ".", "%2e")
 | |
| }
 | |
| 
 | |
| // Looks up symbol (either functions or global variables) at address addr.
 | |
| // Used by disassembly formatter.
 | |
| func (bi *BinaryInfo) symLookup(addr uint64) (string, uint64) {
 | |
| 	fn := bi.PCToFunc(addr)
 | |
| 	if fn != nil {
 | |
| 		if fn.Entry == addr {
 | |
| 			// only report the function name if it's the exact address because it's
 | |
| 			// easier to read the absolute address than function_name+offset.
 | |
| 			return fn.Name, fn.Entry
 | |
| 		}
 | |
| 		return "", 0
 | |
| 	}
 | |
| 	if sym, ok := bi.SymNames[addr]; ok {
 | |
| 		return sym.Name, addr
 | |
| 	}
 | |
| 	i := sort.Search(len(bi.packageVars), func(i int) bool {
 | |
| 		return bi.packageVars[i].addr >= addr
 | |
| 	})
 | |
| 	if i >= len(bi.packageVars) {
 | |
| 		return "", 0
 | |
| 	}
 | |
| 	if bi.packageVars[i].addr > addr {
 | |
| 		// report previous variable + offset if i-th variable starts after addr
 | |
| 		i--
 | |
| 	}
 | |
| 	if i >= 0 && bi.packageVars[i].addr != 0 {
 | |
| 		return bi.packageVars[i].name, bi.packageVars[i].addr
 | |
| 	}
 | |
| 	return "", 0
 | |
| }
 | |
| 
 | |
| type PackageBuildInfo struct {
 | |
| 	ImportPath    string
 | |
| 	DirectoryPath string
 | |
| 	Files         map[string]struct{}
 | |
| }
 | |
| 
 | |
| // ListPackagesBuildInfo returns the list of packages used by the program along with
 | |
| // the directory where each package was compiled and optionally the list of
 | |
| // files constituting the package.
 | |
| func (bi *BinaryInfo) ListPackagesBuildInfo(includeFiles bool) []*PackageBuildInfo {
 | |
| 	m := make(map[string]*PackageBuildInfo)
 | |
| 	for _, cu := range bi.Images[0].compileUnits {
 | |
| 		if cu.image != bi.Images[0] || !cu.isgo || cu.lineInfo == nil {
 | |
| 			//TODO(aarzilli): what's the correct thing to do for plugins?
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		ip := strings.ReplaceAll(cu.name, "\\", "/")
 | |
| 		if _, ok := m[ip]; !ok {
 | |
| 			path := cu.lineInfo.FirstFile()
 | |
| 			if ext := filepath.Ext(path); ext != ".go" && ext != ".s" {
 | |
| 				continue
 | |
| 			}
 | |
| 			dp := filepath.Dir(path)
 | |
| 			m[ip] = &PackageBuildInfo{
 | |
| 				ImportPath:    ip,
 | |
| 				DirectoryPath: dp,
 | |
| 				Files:         make(map[string]struct{}),
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if includeFiles {
 | |
| 			pbi := m[ip]
 | |
| 
 | |
| 			for _, file := range cu.lineInfo.FileNames {
 | |
| 				pbi.Files[file.Path] = struct{}{}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	r := make([]*PackageBuildInfo, 0, len(m))
 | |
| 	for _, pbi := range m {
 | |
| 		r = append(r, pbi)
 | |
| 	}
 | |
| 
 | |
| 	sort.Slice(r, func(i, j int) bool { return r[i].ImportPath < r[j].ImportPath })
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // cuFilePath takes a compilation unit "cu" and a file index reference
 | |
| // "fileidx" and returns the corresponding file name entry from the
 | |
| // DWARF line table associated with the unit; "entry" is the offset of
 | |
| // the attribute where the file reference originated, for logging
 | |
| // purposes. Return value is the file string and an error value; error
 | |
| // will be non-nil if the file could not be recovered, perhaps due to
 | |
| // malformed DWARF.
 | |
| func (cu *compileUnit) filePath(fileidx int, entry *dwarf.Entry) (string, error) {
 | |
| 	if cu.lineInfo == nil {
 | |
| 		return "", fmt.Errorf("reading debug_info: file reference within a compilation unit without debug_line section at %#x", entry.Offset)
 | |
| 	}
 | |
| 	// File numbering is slightly different before and after DWARF 5;
 | |
| 	// account for this here. See section 6.2.4 of the DWARF 5 spec.
 | |
| 	if cu.Version < 5 {
 | |
| 		fileidx--
 | |
| 	}
 | |
| 	if fileidx < 0 || fileidx >= len(cu.lineInfo.FileNames) {
 | |
| 		return "", fmt.Errorf("reading debug_info: file index (%d) out of range in compile unit file table at %#x", fileidx, entry.Offset)
 | |
| 	}
 | |
| 	return cu.lineInfo.FileNames[fileidx].Path, nil
 | |
| }
 |