Home | History | Annotate | Download | only in 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 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 }
    188