github.com/gocuntian/go@v0.0.0-20160610041250-fee02d270bf8/src/cmd/go/note.go (about)

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