Source file src/cmd/internal/buildid/note.go

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package buildid
     6  
     7  import (
     8  	"bytes"
     9  	"debug/elf"
    10  	"debug/macho"
    11  	"encoding/binary"
    12  	"fmt"
    13  	"io"
    14  	"io/fs"
    15  	"os"
    16  )
    17  
    18  func readAligned4(r io.Reader, sz int32) ([]byte, error) {
    19  	full := (sz + 3) &^ 3
    20  	data := make([]byte, full)
    21  	_, err := io.ReadFull(r, data)
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  	data = data[:sz]
    26  	return data, nil
    27  }
    28  
    29  func ReadELFNote(filename, name string, typ int32) ([]byte, error) {
    30  	f, err := elf.Open(filename)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  	defer f.Close()
    35  	for _, sect := range f.Sections {
    36  		if sect.Type != elf.SHT_NOTE {
    37  			continue
    38  		}
    39  		r := sect.Open()
    40  		for {
    41  			var namesize, descsize, noteType int32
    42  			err = binary.Read(r, f.ByteOrder, &namesize)
    43  			if err != nil {
    44  				if err == io.EOF {
    45  					break
    46  				}
    47  				return nil, fmt.Errorf("read namesize failed: %v", err)
    48  			}
    49  			err = binary.Read(r, f.ByteOrder, &descsize)
    50  			if err != nil {
    51  				return nil, fmt.Errorf("read descsize failed: %v", err)
    52  			}
    53  			err = binary.Read(r, f.ByteOrder, &noteType)
    54  			if err != nil {
    55  				return nil, fmt.Errorf("read type failed: %v", err)
    56  			}
    57  			noteName, err := readAligned4(r, namesize)
    58  			if err != nil {
    59  				return nil, fmt.Errorf("read name failed: %v", err)
    60  			}
    61  			desc, err := readAligned4(r, descsize)
    62  			if err != nil {
    63  				return nil, fmt.Errorf("read desc failed: %v", err)
    64  			}
    65  			if name == string(noteName) && typ == noteType {
    66  				return desc, nil
    67  			}
    68  		}
    69  	}
    70  	return nil, nil
    71  }
    72  
    73  var elfGoNote = []byte("Go\x00\x00")
    74  var elfGNUNote = []byte("GNU\x00")
    75  
    76  // The Go build ID is stored in a note described by an ELF PT_NOTE prog
    77  // header. The caller has already opened filename, to get f, and read
    78  // at least 4 kB out, in data.
    79  func readELF(name string, f *os.File, data []byte) (buildid string, err error) {
    80  	// Assume the note content is in the data, already read.
    81  	// Rewrite the ELF header to set shoff and shnum to 0, so that we can pass
    82  	// the data to elf.NewFile and it will decode the Prog list but not
    83  	// try to read the section headers and the string table from disk.
    84  	// That's a waste of I/O when all we care about is the Prog list
    85  	// and the one ELF note.
    86  	switch elf.Class(data[elf.EI_CLASS]) {
    87  	case elf.ELFCLASS32:
    88  		data[32], data[33], data[34], data[35] = 0, 0, 0, 0
    89  		data[48] = 0
    90  		data[49] = 0
    91  	case elf.ELFCLASS64:
    92  		data[40], data[41], data[42], data[43] = 0, 0, 0, 0
    93  		data[44], data[45], data[46], data[47] = 0, 0, 0, 0
    94  		data[60] = 0
    95  		data[61] = 0
    96  	}
    97  
    98  	const elfGoBuildIDTag = 4
    99  	const gnuBuildIDTag = 3
   100  
   101  	ef, err := elf.NewFile(bytes.NewReader(data))
   102  	if err != nil {
   103  		return "", &fs.PathError{Path: name, Op: "parse", Err: err}
   104  	}
   105  	var gnu string
   106  	for _, p := range ef.Progs {
   107  		if p.Type != elf.PT_NOTE || p.Filesz < 16 {
   108  			continue
   109  		}
   110  
   111  		var note []byte
   112  		if p.Off+p.Filesz < uint64(len(data)) {
   113  			note = data[p.Off : p.Off+p.Filesz]
   114  		} else {
   115  			// For some linkers, such as the Solaris linker,
   116  			// the buildid may not be found in data (which
   117  			// likely contains the first 16kB of the file)
   118  			// or even the first few megabytes of the file
   119  			// due to differences in note segment placement;
   120  			// in that case, extract the note data manually.
   121  			_, err = f.Seek(int64(p.Off), io.SeekStart)
   122  			if err != nil {
   123  				return "", err
   124  			}
   125  
   126  			note = make([]byte, p.Filesz)
   127  			_, err = io.ReadFull(f, note)
   128  			if err != nil {
   129  				return "", err
   130  			}
   131  		}
   132  
   133  		filesz := p.Filesz
   134  		off := p.Off
   135  		for filesz >= 16 {
   136  			nameSize := ef.ByteOrder.Uint32(note)
   137  			valSize := ef.ByteOrder.Uint32(note[4:])
   138  			tag := ef.ByteOrder.Uint32(note[8:])
   139  			nname := note[12:16]
   140  			if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) {
   141  				return string(note[16 : 16+valSize]), nil
   142  			}
   143  
   144  			if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) {
   145  				gnu = string(note[16 : 16+valSize])
   146  			}
   147  
   148  			nameSize = (nameSize + 3) &^ 3
   149  			valSize = (valSize + 3) &^ 3
   150  			notesz := uint64(12 + nameSize + valSize)
   151  			if filesz <= notesz {
   152  				break
   153  			}
   154  			off += notesz
   155  			align := p.Align
   156  			if align != 0 {
   157  				alignedOff := (off + align - 1) &^ (align - 1)
   158  				notesz += alignedOff - off
   159  				off = alignedOff
   160  			}
   161  			filesz -= notesz
   162  			note = note[notesz:]
   163  		}
   164  	}
   165  
   166  	// If we didn't find a Go note, use a GNU note if available.
   167  	// This is what gccgo uses.
   168  	if gnu != "" {
   169  		return gnu, nil
   170  	}
   171  
   172  	// No note. Treat as successful but build ID empty.
   173  	return "", nil
   174  }
   175  
   176  // The Go build ID is stored at the beginning of the Mach-O __text segment.
   177  // The caller has already opened filename, to get f, and read a few kB out, in data.
   178  // Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount
   179  // of other junk placed in the file ahead of the main text.
   180  func readMacho(name string, f *os.File, data []byte) (buildid string, err error) {
   181  	// If the data we want has already been read, don't worry about Mach-O parsing.
   182  	// This is both an optimization and a hedge against the Mach-O parsing failing
   183  	// in the future due to, for example, the name of the __text section changing.
   184  	if b, err := readRaw(name, data); b != "" && err == nil {
   185  		return b, err
   186  	}
   187  
   188  	mf, err := macho.NewFile(f)
   189  	if err != nil {
   190  		return "", &fs.PathError{Path: name, Op: "parse", Err: err}
   191  	}
   192  
   193  	sect := mf.Section("__text")
   194  	if sect == nil {
   195  		// Every binary has a __text section. Something is wrong.
   196  		return "", &fs.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")}
   197  	}
   198  
   199  	// It should be in the first few bytes, but read a lot just in case,
   200  	// especially given our past problems on OS X with the build ID moving.
   201  	// There shouldn't be much difference between reading 4kB and 32kB:
   202  	// the hard part is getting to the data, not transferring it.
   203  	n := sect.Size
   204  	if n > uint64(readSize) {
   205  		n = uint64(readSize)
   206  	}
   207  	buf := make([]byte, n)
   208  	if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil {
   209  		return "", err
   210  	}
   211  
   212  	return readRaw(name, buf)
   213  }
   214  

View as plain text