aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/internal/buildid/note.go
blob: f5b6fc565f36dcc6852cba2da03a6ed1bd9e087c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// Copyright 2015 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 buildid

import (
	"bytes"
	"debug/elf"
	"debug/macho"
	"encoding/binary"
	"fmt"
	"io"
	"io/fs"
	"os"
)

func readAligned4(r io.Reader, sz int32) ([]byte, error) {
	full := (sz + 3) &^ 3
	data := make([]byte, full)
	_, err := io.ReadFull(r, data)
	if err != nil {
		return nil, err
	}
	data = data[:sz]
	return data, nil
}

func ReadELFNote(filename, name string, typ int32) ([]byte, error) {
	f, err := elf.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	for _, sect := range f.Sections {
		if sect.Type != elf.SHT_NOTE {
			continue
		}
		r := sect.Open()
		for {
			var namesize, descsize, noteType int32
			err = binary.Read(r, f.ByteOrder, &namesize)
			if err != nil {
				if err == io.EOF {
					break
				}
				return nil, fmt.Errorf("read namesize failed: %v", err)
			}
			err = binary.Read(r, f.ByteOrder, &descsize)
			if err != nil {
				return nil, fmt.Errorf("read descsize failed: %v", err)
			}
			err = binary.Read(r, f.ByteOrder, &noteType)
			if err != nil {
				return nil, fmt.Errorf("read type failed: %v", err)
			}
			noteName, err := readAligned4(r, namesize)
			if err != nil {
				return nil, fmt.Errorf("read name failed: %v", err)
			}
			desc, err := readAligned4(r, descsize)
			if err != nil {
				return nil, fmt.Errorf("read desc failed: %v", err)
			}
			if name == string(noteName) && typ == noteType {
				return desc, nil
			}
		}
	}
	return nil, nil
}

var elfGoNote = []byte("Go\x00\x00")
var elfGNUNote = []byte("GNU\x00")

// The Go build ID is stored in a note described by an ELF PT_NOTE prog
// header. The caller has already opened filename, to get f, and read
// at least 4 kB out, in data.
func readELF(name string, f *os.File, data []byte) (buildid string, err error) {
	// Assume the note content is in the data, already read.
	// Rewrite the ELF header to set shnum to 0, so that we can pass
	// the data to elf.NewFile and it will decode the Prog list but not
	// try to read the section headers and the string table from disk.
	// That's a waste of I/O when all we care about is the Prog list
	// and the one ELF note.
	switch elf.Class(data[elf.EI_CLASS]) {
	case elf.ELFCLASS32:
		data[48] = 0
		data[49] = 0
	case elf.ELFCLASS64:
		data[60] = 0
		data[61] = 0
	}

	const elfGoBuildIDTag = 4
	const gnuBuildIDTag = 3

	ef, err := elf.NewFile(bytes.NewReader(data))
	if err != nil {
		return "", &fs.PathError{Path: name, Op: "parse", Err: err}
	}
	var gnu string
	for _, p := range ef.Progs {
		if p.Type != elf.PT_NOTE || p.Filesz < 16 {
			continue
		}

		var note []byte
		if p.Off+p.Filesz < uint64(len(data)) {
			note = data[p.Off : p.Off+p.Filesz]
		} else {
			// For some linkers, such as the Solaris linker,
			// the buildid may not be found in data (which
			// likely contains the first 16kB of the file)
			// or even the first few megabytes of the file
			// due to differences in note segment placement;
			// in that case, extract the note data manually.
			_, err = f.Seek(int64(p.Off), io.SeekStart)
			if err != nil {
				return "", err
			}

			note = make([]byte, p.Filesz)
			_, err = io.ReadFull(f, note)
			if err != nil {
				return "", err
			}
		}

		filesz := p.Filesz
		off := p.Off
		for filesz >= 16 {
			nameSize := ef.ByteOrder.Uint32(note)
			valSize := ef.ByteOrder.Uint32(note[4:])
			tag := ef.ByteOrder.Uint32(note[8:])
			nname := note[12:16]
			if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) {
				return string(note[16 : 16+valSize]), nil
			}

			if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) {
				gnu = string(note[16 : 16+valSize])
			}

			nameSize = (nameSize + 3) &^ 3
			valSize = (valSize + 3) &^ 3
			notesz := uint64(12 + nameSize + valSize)
			if filesz <= notesz {
				break
			}
			off += notesz
			align := p.Align
			alignedOff := (off + align - 1) &^ (align - 1)
			notesz += alignedOff - off
			off = alignedOff
			filesz -= notesz
			note = note[notesz:]
		}
	}

	// If we didn't find a Go note, use a GNU note if available.
	// This is what gccgo uses.
	if gnu != "" {
		return gnu, nil
	}

	// No note. Treat as successful but build ID empty.
	return "", nil
}

// The Go build ID is stored at the beginning of the Mach-O __text segment.
// The caller has already opened filename, to get f, and read a few kB out, in data.
// Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount
// of other junk placed in the file ahead of the main text.
func readMacho(name string, f *os.File, data []byte) (buildid string, err error) {
	// If the data we want has already been read, don't worry about Mach-O parsing.
	// This is both an optimization and a hedge against the Mach-O parsing failing
	// in the future due to, for example, the name of the __text section changing.
	if b, err := readRaw(name, data); b != "" && err == nil {
		return b, err
	}

	mf, err := macho.NewFile(f)
	if err != nil {
		return "", &fs.PathError{Path: name, Op: "parse", Err: err}
	}

	sect := mf.Section("__text")
	if sect == nil {
		// Every binary has a __text section. Something is wrong.
		return "", &fs.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")}
	}

	// It should be in the first few bytes, but read a lot just in case,
	// especially given our past problems on OS X with the build ID moving.
	// There shouldn't be much difference between reading 4kB and 32kB:
	// the hard part is getting to the data, not transferring it.
	n := sect.Size
	if n > uint64(readSize) {
		n = uint64(readSize)
	}
	buf := make([]byte, n)
	if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil {
		return "", err
	}

	return readRaw(name, buf)
}