Home | History | Annotate | Download | only in pprof
      1 // Copyright 2017 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 pprof
      6 
      7 import (
      8 	"encoding/binary"
      9 	"errors"
     10 	"fmt"
     11 	"os"
     12 )
     13 
     14 var (
     15 	errBadELF    = errors.New("malformed ELF binary")
     16 	errNoBuildID = errors.New("no NT_GNU_BUILD_ID found in ELF binary")
     17 )
     18 
     19 // elfBuildID returns the GNU build ID of the named ELF binary,
     20 // without introducing a dependency on debug/elf and its dependencies.
     21 func elfBuildID(file string) (string, error) {
     22 	buf := make([]byte, 256)
     23 	f, err := os.Open(file)
     24 	if err != nil {
     25 		return "", err
     26 	}
     27 	defer f.Close()
     28 
     29 	if _, err := f.ReadAt(buf[:64], 0); err != nil {
     30 		return "", err
     31 	}
     32 
     33 	// ELF file begins with \x7F E L F.
     34 	if buf[0] != 0x7F || buf[1] != 'E' || buf[2] != 'L' || buf[3] != 'F' {
     35 		return "", errBadELF
     36 	}
     37 
     38 	var byteOrder binary.ByteOrder
     39 	switch buf[5] {
     40 	default:
     41 		return "", errBadELF
     42 	case 1: // little-endian
     43 		byteOrder = binary.LittleEndian
     44 	case 2: // big-endian
     45 		byteOrder = binary.BigEndian
     46 	}
     47 
     48 	var shnum int
     49 	var shoff, shentsize int64
     50 	switch buf[4] {
     51 	default:
     52 		return "", errBadELF
     53 	case 1: // 32-bit file header
     54 		shoff = int64(byteOrder.Uint32(buf[32:]))
     55 		shentsize = int64(byteOrder.Uint16(buf[46:]))
     56 		if shentsize != 40 {
     57 			return "", errBadELF
     58 		}
     59 		shnum = int(byteOrder.Uint16(buf[48:]))
     60 	case 2: // 64-bit file header
     61 		shoff = int64(byteOrder.Uint64(buf[40:]))
     62 		shentsize = int64(byteOrder.Uint16(buf[58:]))
     63 		if shentsize != 64 {
     64 			return "", errBadELF
     65 		}
     66 		shnum = int(byteOrder.Uint16(buf[60:]))
     67 	}
     68 
     69 	for i := 0; i < shnum; i++ {
     70 		if _, err := f.ReadAt(buf[:shentsize], shoff+int64(i)*shentsize); err != nil {
     71 			return "", err
     72 		}
     73 		if typ := byteOrder.Uint32(buf[4:]); typ != 7 { // SHT_NOTE
     74 			continue
     75 		}
     76 		var off, size int64
     77 		if shentsize == 40 {
     78 			// 32-bit section header
     79 			off = int64(byteOrder.Uint32(buf[16:]))
     80 			size = int64(byteOrder.Uint32(buf[20:]))
     81 		} else {
     82 			// 64-bit section header
     83 			off = int64(byteOrder.Uint64(buf[24:]))
     84 			size = int64(byteOrder.Uint64(buf[32:]))
     85 		}
     86 		size += off
     87 		for off < size {
     88 			if _, err := f.ReadAt(buf[:16], off); err != nil { // room for header + name GNU\x00
     89 				return "", err
     90 			}
     91 			nameSize := int(byteOrder.Uint32(buf[0:]))
     92 			descSize := int(byteOrder.Uint32(buf[4:]))
     93 			noteType := int(byteOrder.Uint32(buf[8:]))
     94 			descOff := off + int64(12+(nameSize+3)&^3)
     95 			off = descOff + int64((descSize+3)&^3)
     96 			if nameSize != 4 || noteType != 3 || buf[12] != 'G' || buf[13] != 'N' || buf[14] != 'U' || buf[15] != '\x00' { // want name GNU\x00 type 3 (NT_GNU_BUILD_ID)
     97 				continue
     98 			}
     99 			if descSize > len(buf) {
    100 				return "", errBadELF
    101 			}
    102 			if _, err := f.ReadAt(buf[:descSize], descOff); err != nil {
    103 				return "", err
    104 			}
    105 			return fmt.Sprintf("%x", buf[:descSize]), nil
    106 		}
    107 	}
    108 	return "", errNoBuildID
    109 }
    110