1// Copyright 2022 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package elf 16 17import ( 18 "debug/elf" 19 "encoding/binary" 20 "encoding/hex" 21 "errors" 22 "fmt" 23 "io" 24 "os" 25) 26 27const gnuBuildID = "GNU\x00" 28 29// Identifier extracts the elf build ID from an elf file. If allowMissing is true it returns 30// an empty identifier if the file exists but the build ID note does not. 31func Identifier(filename string, allowMissing bool) (string, error) { 32 f, err := os.Open(filename) 33 if err != nil { 34 return "", fmt.Errorf("failed to open %s: %w", filename, err) 35 } 36 defer f.Close() 37 38 return elfIdentifierFromReaderAt(f, filename, allowMissing) 39} 40 41// elfIdentifierFromReaderAt extracts the elf build ID from a ReaderAt. If allowMissing is true it 42// returns an empty identifier if the file exists but the build ID note does not. 43func elfIdentifierFromReaderAt(r io.ReaderAt, filename string, allowMissing bool) (string, error) { 44 f, err := elf.NewFile(r) 45 if err != nil { 46 if allowMissing { 47 if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { 48 return "", nil 49 } 50 if _, ok := err.(*elf.FormatError); ok { 51 // The file was not an elf file. 52 return "", nil 53 } 54 } 55 return "", fmt.Errorf("failed to parse elf file %s: %w", filename, err) 56 } 57 defer f.Close() 58 59 buildIDNote := f.Section(".note.gnu.build-id") 60 if buildIDNote == nil { 61 if allowMissing { 62 return "", nil 63 } 64 return "", fmt.Errorf("failed to find .note.gnu.build-id in %s", filename) 65 } 66 67 buildIDs, err := readNote(buildIDNote.Open(), f.ByteOrder) 68 if err != nil { 69 return "", fmt.Errorf("failed to read .note.gnu.build-id: %w", err) 70 } 71 72 for name, desc := range buildIDs { 73 if name == gnuBuildID { 74 return hex.EncodeToString(desc), nil 75 } 76 } 77 78 return "", nil 79} 80 81// readNote reads the contents of a note section, returning it as a map from name to descriptor. 82func readNote(note io.Reader, byteOrder binary.ByteOrder) (map[string][]byte, error) { 83 var noteHeader struct { 84 Namesz uint32 85 Descsz uint32 86 Type uint32 87 } 88 89 notes := make(map[string][]byte) 90 for { 91 err := binary.Read(note, byteOrder, ¬eHeader) 92 if err != nil { 93 if err == io.EOF { 94 return notes, nil 95 } 96 return nil, fmt.Errorf("failed to read note header: %w", err) 97 } 98 99 nameBuf := make([]byte, align4(noteHeader.Namesz)) 100 err = binary.Read(note, byteOrder, &nameBuf) 101 if err != nil { 102 return nil, fmt.Errorf("failed to read note name: %w", err) 103 } 104 name := string(nameBuf[:noteHeader.Namesz]) 105 106 descBuf := make([]byte, align4(noteHeader.Descsz)) 107 err = binary.Read(note, byteOrder, &descBuf) 108 if err != nil { 109 return nil, fmt.Errorf("failed to read note desc: %w", err) 110 } 111 notes[name] = descBuf[:noteHeader.Descsz] 112 } 113} 114 115// align4 rounds the input up to the next multiple of 4. 116func align4(i uint32) uint32 { 117 return (i + 3) &^ 3 118} 119