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 	"encoding/binary"
     11 	"fmt"
     12 	"io"
     13 	"os"
     14 )
     15 
     16 func readAligned4(r io.Reader, sz int32) ([]byte, error) {
     17 	full := (sz + 3) &^ 3
     18 	data := make([]byte, full)
     19 	_, err := io.ReadFull(r, data)
     20 	if err != nil {
     21 		return nil, err
     22 	}
     23 	data = data[:sz]
     24 	return data, nil
     25 }
     26 
     27 func readELFNote(filename, name string, typ int32) ([]byte, error) {
     28 	f, err := elf.Open(filename)
     29 	if err != nil {
     30 		return nil, err
     31 	}
     32 	for _, sect := range f.Sections {
     33 		if sect.Type != elf.SHT_NOTE {
     34 			continue
     35 		}
     36 		r := sect.Open()
     37 		for {
     38 			var namesize, descsize, noteType int32
     39 			err = binary.Read(r, f.ByteOrder, &namesize)
     40 			if err != nil {
     41 				if err == io.EOF {
     42 					break
     43 				}
     44 				return nil, fmt.Errorf("read namesize failed: %v", err)
     45 			}
     46 			err = binary.Read(r, f.ByteOrder, &descsize)
     47 			if err != nil {
     48 				return nil, fmt.Errorf("read descsize failed: %v", err)
     49 			}
     50 			err = binary.Read(r, f.ByteOrder, &noteType)
     51 			if err != nil {
     52 				return nil, fmt.Errorf("read type failed: %v", err)
     53 			}
     54 			noteName, err := readAligned4(r, namesize)
     55 			if err != nil {
     56 				return nil, fmt.Errorf("read name failed: %v", err)
     57 			}
     58 			desc, err := readAligned4(r, descsize)
     59 			if err != nil {
     60 				return nil, fmt.Errorf("read desc failed: %v", err)
     61 			}
     62 			if name == string(noteName) && typ == noteType {
     63 				return desc, nil
     64 			}
     65 		}
     66 	}
     67 	return nil, nil
     68 }
     69 
     70 var elfGoNote = []byte("Go\x00\x00")
     71 
     72 // readELFGoBuildID the Go build ID string from an ELF binary.
     73 // The Go build ID is stored in a note described by an ELF PT_NOTE prog header.
     74 // The caller has already opened filename, to get f, and read the first 4 kB out, in data.
     75 func readELFGoBuildID(filename string, f *os.File, data []byte) (buildid string, err error) {
     76 	// Assume the note content is in the first 4 kB, already read.
     77 	// Rewrite the ELF header to set shnum to 0, so that we can pass
     78 	// the data to elf.NewFile and it will decode the Prog list but not
     79 	// try to read the section headers and the string table from disk.
     80 	// That's a waste of I/O when all we care about is the Prog list
     81 	// and the one ELF note.
     82 	switch elf.Class(data[elf.EI_CLASS]) {
     83 	case elf.ELFCLASS32:
     84 		data[48] = 0
     85 		data[49] = 0
     86 	case elf.ELFCLASS64:
     87 		data[60] = 0
     88 		data[61] = 0
     89 	}
     90 
     91 	const elfGoBuildIDTag = 4
     92 
     93 	ef, err := elf.NewFile(bytes.NewReader(data))
     94 	if err != nil {
     95 		return "", &os.PathError{Path: filename, Op: "parse", Err: err}
     96 	}
     97 	for _, p := range ef.Progs {
     98 		if p.Type != elf.PT_NOTE || p.Off >= uint64(len(data)) || p.Off+p.Filesz >= uint64(len(data)) || p.Filesz < 16 {
     99 			continue
    100 		}
    101 
    102 		note := data[p.Off : p.Off+p.Filesz]
    103 		nameSize := ef.ByteOrder.Uint32(note)
    104 		valSize := ef.ByteOrder.Uint32(note[4:])
    105 		tag := ef.ByteOrder.Uint32(note[8:])
    106 		name := note[12:16]
    107 		if nameSize != 4 || 16+valSize > uint32(len(note)) || tag != elfGoBuildIDTag || !bytes.Equal(name, elfGoNote) {
    108 			continue
    109 		}
    110 
    111 		return string(note[16 : 16+valSize]), nil
    112 	}
    113 
    114 	// No note. Treat as successful but build ID empty.
    115 	return "", nil
    116 }
    117