blob: e84a8aeea400bc0a106ff7a885a7cfe6af45226c [file] [log] [blame]
Colin Cross36f55aa2022-03-21 18:46:41 -07001// 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
Peter Collingbourne941ff1d2024-03-14 21:17:21 -070015package elf
Colin Cross36f55aa2022-03-21 18:46:41 -070016
17import (
18 "debug/elf"
19 "encoding/binary"
20 "encoding/hex"
Colin Cross6285c652022-04-05 18:06:15 -070021 "errors"
Colin Cross36f55aa2022-03-21 18:46:41 -070022 "fmt"
23 "io"
Colin Cross6285c652022-04-05 18:06:15 -070024 "os"
Colin Cross36f55aa2022-03-21 18:46:41 -070025)
26
27const gnuBuildID = "GNU\x00"
28
Peter Collingbourne941ff1d2024-03-14 21:17:21 -070029// Identifier extracts the elf build ID from an elf file. If allowMissing is true it returns
Colin Cross36f55aa2022-03-21 18:46:41 -070030// an empty identifier if the file exists but the build ID note does not.
Peter Collingbourne941ff1d2024-03-14 21:17:21 -070031func Identifier(filename string, allowMissing bool) (string, error) {
Colin Cross6285c652022-04-05 18:06:15 -070032 f, err := os.Open(filename)
Colin Cross36f55aa2022-03-21 18:46:41 -070033 if err != nil {
34 return "", fmt.Errorf("failed to open %s: %w", filename, err)
35 }
36 defer f.Close()
37
Colin Cross6285c652022-04-05 18:06:15 -070038 return elfIdentifierFromReaderAt(f, filename, allowMissing)
39}
40
Colin Cross338df532022-04-11 18:42:34 -070041// 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.
Colin Cross6285c652022-04-05 18:06:15 -070043func elfIdentifierFromReaderAt(r io.ReaderAt, filename string, allowMissing bool) (string, error) {
44 f, err := elf.NewFile(r)
45 if err != nil {
46 if allowMissing {
Colin Cross338df532022-04-11 18:42:34 -070047 if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
Colin Cross6285c652022-04-05 18:06:15 -070048 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
Colin Cross36f55aa2022-03-21 18:46:41 -070059 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, &noteHeader)
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}