github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/agent/pprof/elf.go (about) 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 }