| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package pprof |
| |
| import ( |
| "encoding/binary" |
| "errors" |
| "fmt" |
| "os" |
| ) |
| |
| var ( |
| errBadELF = errors.New("malformed ELF binary") |
| errNoBuildID = errors.New("no NT_GNU_BUILD_ID found in ELF binary") |
| ) |
| |
| // elfBuildID returns the GNU build ID of the named ELF binary, |
| // without introducing a dependency on debug/elf and its dependencies. |
| func elfBuildID(file string) (string, error) { |
| buf := make([]byte, 256) |
| f, err := os.Open(file) |
| if err != nil { |
| return "", err |
| } |
| defer f.Close() |
| |
| if _, err := f.ReadAt(buf[:64], 0); err != nil { |
| return "", err |
| } |
| |
| // ELF file begins with \x7F E L F. |
| if buf[0] != 0x7F || buf[1] != 'E' || buf[2] != 'L' || buf[3] != 'F' { |
| return "", errBadELF |
| } |
| |
| var byteOrder binary.ByteOrder |
| switch buf[5] { |
| default: |
| return "", errBadELF |
| case 1: // little-endian |
| byteOrder = binary.LittleEndian |
| case 2: // big-endian |
| byteOrder = binary.BigEndian |
| } |
| |
| var shnum int |
| var shoff, shentsize int64 |
| switch buf[4] { |
| default: |
| return "", errBadELF |
| case 1: // 32-bit file header |
| shoff = int64(byteOrder.Uint32(buf[32:])) |
| shentsize = int64(byteOrder.Uint16(buf[46:])) |
| if shentsize != 40 { |
| return "", errBadELF |
| } |
| shnum = int(byteOrder.Uint16(buf[48:])) |
| case 2: // 64-bit file header |
| shoff = int64(byteOrder.Uint64(buf[40:])) |
| shentsize = int64(byteOrder.Uint16(buf[58:])) |
| if shentsize != 64 { |
| return "", errBadELF |
| } |
| shnum = int(byteOrder.Uint16(buf[60:])) |
| } |
| |
| for i := 0; i < shnum; i++ { |
| if _, err := f.ReadAt(buf[:shentsize], shoff+int64(i)*shentsize); err != nil { |
| return "", err |
| } |
| if typ := byteOrder.Uint32(buf[4:]); typ != 7 { // SHT_NOTE |
| continue |
| } |
| var off, size int64 |
| if shentsize == 40 { |
| // 32-bit section header |
| off = int64(byteOrder.Uint32(buf[16:])) |
| size = int64(byteOrder.Uint32(buf[20:])) |
| } else { |
| // 64-bit section header |
| off = int64(byteOrder.Uint64(buf[24:])) |
| size = int64(byteOrder.Uint64(buf[32:])) |
| } |
| size += off |
| for off < size { |
| if _, err := f.ReadAt(buf[:16], off); err != nil { // room for header + name GNU\x00 |
| return "", err |
| } |
| nameSize := int(byteOrder.Uint32(buf[0:])) |
| descSize := int(byteOrder.Uint32(buf[4:])) |
| noteType := int(byteOrder.Uint32(buf[8:])) |
| descOff := off + int64(12+(nameSize+3)&^3) |
| off = descOff + int64((descSize+3)&^3) |
| 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) |
| continue |
| } |
| if descSize > len(buf) { |
| return "", errBadELF |
| } |
| if _, err := f.ReadAt(buf[:descSize], descOff); err != nil { |
| return "", err |
| } |
| return fmt.Sprintf("%x", buf[:descSize]), nil |
| } |
| } |
| return "", errNoBuildID |
| } |