...

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

View as plain text