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[48] = 0
89 data[49] = 0
90 case elf.ELFCLASS64:
91 data[60] = 0
92 data[61] = 0
93 }
94
95 const elfGoBuildIDTag = 4
96 const gnuBuildIDTag = 3
97
98 ef, err := elf.NewFile(bytes.NewReader(data))
99 if err != nil {
100 return "", &fs.PathError{Path: name, Op: "parse", Err: err}
101 }
102 var gnu string
103 for _, p := range ef.Progs {
104 if p.Type != elf.PT_NOTE || p.Filesz < 16 {
105 continue
106 }
107
108 var note []byte
109 if p.Off+p.Filesz < uint64(len(data)) {
110 note = data[p.Off : p.Off+p.Filesz]
111 } else {
112
113
114
115
116
117
118 _, err = f.Seek(int64(p.Off), io.SeekStart)
119 if err != nil {
120 return "", err
121 }
122
123 note = make([]byte, p.Filesz)
124 _, err = io.ReadFull(f, note)
125 if err != nil {
126 return "", err
127 }
128 }
129
130 filesz := p.Filesz
131 off := p.Off
132 for filesz >= 16 {
133 nameSize := ef.ByteOrder.Uint32(note)
134 valSize := ef.ByteOrder.Uint32(note[4:])
135 tag := ef.ByteOrder.Uint32(note[8:])
136 nname := note[12:16]
137 if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) {
138 return string(note[16 : 16+valSize]), nil
139 }
140
141 if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) {
142 gnu = string(note[16 : 16+valSize])
143 }
144
145 nameSize = (nameSize + 3) &^ 3
146 valSize = (valSize + 3) &^ 3
147 notesz := uint64(12 + nameSize + valSize)
148 if filesz <= notesz {
149 break
150 }
151 off += notesz
152 align := p.Align
153 alignedOff := (off + align - 1) &^ (align - 1)
154 notesz += alignedOff - off
155 off = alignedOff
156 filesz -= notesz
157 note = note[notesz:]
158 }
159 }
160
161
162
163 if gnu != "" {
164 return gnu, nil
165 }
166
167
168 return "", nil
169 }
170
171
172
173
174
175 func readMacho(name string, f *os.File, data []byte) (buildid string, err error) {
176
177
178
179 if b, err := readRaw(name, data); b != "" && err == nil {
180 return b, err
181 }
182
183 mf, err := macho.NewFile(f)
184 if err != nil {
185 return "", &fs.PathError{Path: name, Op: "parse", Err: err}
186 }
187
188 sect := mf.Section("__text")
189 if sect == nil {
190
191 return "", &fs.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")}
192 }
193
194
195
196
197
198 n := sect.Size
199 if n > uint64(readSize) {
200 n = uint64(readSize)
201 }
202 buf := make([]byte, n)
203 if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil {
204 return "", err
205 }
206
207 return readRaw(name, buf)
208 }
209
View as plain text