1
2
3
4
5 package buildid
6
7 import (
8 "bytes"
9 "debug/elf"
10 "debug/macho"
11 "encoding/binary"
12 "fmt"
13 "io"
14 "io/fs"
15 "os"
16 )
17
18 func readAligned4(r io.Reader, sz int32) ([]byte, error) {
19 full := (sz + 3) &^ 3
20 data := make([]byte, full)
21 _, err := io.ReadFull(r, data)
22 if err != nil {
23 return nil, err
24 }
25 data = data[:sz]
26 return data, nil
27 }
28
29 func ReadELFNote(filename, name string, typ int32) ([]byte, error) {
30 f, err := elf.Open(filename)
31 if err != nil {
32 return nil, err
33 }
34 defer f.Close()
35 for _, sect := range f.Sections {
36 if sect.Type != elf.SHT_NOTE {
37 continue
38 }
39 r := sect.Open()
40 for {
41 var namesize, descsize, noteType int32
42 err = binary.Read(r, f.ByteOrder, &namesize)
43 if err != nil {
44 if err == io.EOF {
45 break
46 }
47 return nil, fmt.Errorf("read namesize failed: %v", err)
48 }
49 err = binary.Read(r, f.ByteOrder, &descsize)
50 if err != nil {
51 return nil, fmt.Errorf("read descsize failed: %v", err)
52 }
53 err = binary.Read(r, f.ByteOrder, ¬eType)
54 if err != nil {
55 return nil, fmt.Errorf("read type failed: %v", err)
56 }
57 noteName, err := readAligned4(r, namesize)
58 if err != nil {
59 return nil, fmt.Errorf("read name failed: %v", err)
60 }
61 desc, err := readAligned4(r, descsize)
62 if err != nil {
63 return nil, fmt.Errorf("read desc failed: %v", err)
64 }
65 if name == string(noteName) && typ == noteType {
66 return desc, nil
67 }
68 }
69 }
70 return nil, nil
71 }
72
73 var elfGoNote = []byte("Go\x00\x00")
74 var elfGNUNote = []byte("GNU\x00")
75
76
77
78
79 func readELF(name string, f *os.File, data []byte) (buildid string, err error) {
80
81
82
83
84
85
86 switch elf.Class(data[elf.EI_CLASS]) {
87 case elf.ELFCLASS32:
88 data[32], data[33], data[34], data[35] = 0, 0, 0, 0
89 data[48] = 0
90 data[49] = 0
91 case elf.ELFCLASS64:
92 data[40], data[41], data[42], data[43] = 0, 0, 0, 0
93 data[44], data[45], data[46], data[47] = 0, 0, 0, 0
94 data[60] = 0
95 data[61] = 0
96 }
97
98 const elfGoBuildIDTag = 4
99 const gnuBuildIDTag = 3
100
101 ef, err := elf.NewFile(bytes.NewReader(data))
102 if err != nil {
103 return "", &fs.PathError{Path: name, Op: "parse", Err: err}
104 }
105 var gnu string
106 for _, p := range ef.Progs {
107 if p.Type != elf.PT_NOTE || p.Filesz < 16 {
108 continue
109 }
110
111 var note []byte
112 if p.Off+p.Filesz < uint64(len(data)) {
113 note = data[p.Off : p.Off+p.Filesz]
114 } else {
115
116
117
118
119
120
121 _, err = f.Seek(int64(p.Off), io.SeekStart)
122 if err != nil {
123 return "", err
124 }
125
126 note = make([]byte, p.Filesz)
127 _, err = io.ReadFull(f, note)
128 if err != nil {
129 return "", err
130 }
131 }
132
133 filesz := p.Filesz
134 off := p.Off
135 for filesz >= 16 {
136 nameSize := ef.ByteOrder.Uint32(note)
137 valSize := ef.ByteOrder.Uint32(note[4:])
138 tag := ef.ByteOrder.Uint32(note[8:])
139 nname := note[12:16]
140 if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) {
141 return string(note[16 : 16+valSize]), nil
142 }
143
144 if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) {
145 gnu = string(note[16 : 16+valSize])
146 }
147
148 nameSize = (nameSize + 3) &^ 3
149 valSize = (valSize + 3) &^ 3
150 notesz := uint64(12 + nameSize + valSize)
151 if filesz <= notesz {
152 break
153 }
154 off += notesz
155 align := p.Align
156 if align != 0 {
157 alignedOff := (off + align - 1) &^ (align - 1)
158 notesz += alignedOff - off
159 off = alignedOff
160 }
161 filesz -= notesz
162 note = note[notesz:]
163 }
164 }
165
166
167
168 if gnu != "" {
169 return gnu, nil
170 }
171
172
173 return "", nil
174 }
175
176
177
178
179
180 func readMacho(name string, f *os.File, data []byte) (buildid string, err error) {
181
182
183
184 if b, err := readRaw(name, data); b != "" && err == nil {
185 return b, err
186 }
187
188 mf, err := macho.NewFile(f)
189 if err != nil {
190 return "", &fs.PathError{Path: name, Op: "parse", Err: err}
191 }
192
193 sect := mf.Section("__text")
194 if sect == nil {
195
196 return "", &fs.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")}
197 }
198
199
200
201
202
203 n := sect.Size
204 if n > uint64(readSize) {
205 n = uint64(readSize)
206 }
207 buf := make([]byte, n)
208 if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil {
209 return "", err
210 }
211
212 return readRaw(name, buf)
213 }
214
View as plain text