libgo: update to go1.9
[official-gcc.git] / libgo / go / cmd / go / internal / buildid / note.go
blob68c91e27047507c546331e151675d0935001cb0c
1 // Copyright 2015 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 package buildid
7 import (
8 "bytes"
9 "debug/elf"
10 "debug/macho"
11 "encoding/binary"
12 "fmt"
13 "io"
14 "os"
17 func readAligned4(r io.Reader, sz int32) ([]byte, error) {
18 full := (sz + 3) &^ 3
19 data := make([]byte, full)
20 _, err := io.ReadFull(r, data)
21 if err != nil {
22 return nil, err
24 data = data[:sz]
25 return data, nil
28 func ReadELFNote(filename, name string, typ int32) ([]byte, error) {
29 f, err := elf.Open(filename)
30 if err != nil {
31 return nil, err
33 for _, sect := range f.Sections {
34 if sect.Type != elf.SHT_NOTE {
35 continue
37 r := sect.Open()
38 for {
39 var namesize, descsize, noteType int32
40 err = binary.Read(r, f.ByteOrder, &namesize)
41 if err != nil {
42 if err == io.EOF {
43 break
45 return nil, fmt.Errorf("read namesize failed: %v", err)
47 err = binary.Read(r, f.ByteOrder, &descsize)
48 if err != nil {
49 return nil, fmt.Errorf("read descsize failed: %v", err)
51 err = binary.Read(r, f.ByteOrder, &noteType)
52 if err != nil {
53 return nil, fmt.Errorf("read type failed: %v", err)
55 noteName, err := readAligned4(r, namesize)
56 if err != nil {
57 return nil, fmt.Errorf("read name failed: %v", err)
59 desc, err := readAligned4(r, descsize)
60 if err != nil {
61 return nil, fmt.Errorf("read desc failed: %v", err)
63 if name == string(noteName) && typ == noteType {
64 return desc, nil
68 return nil, nil
71 var elfGoNote = []byte("Go\x00\x00")
73 // The Go build ID is stored in a note described by an ELF PT_NOTE prog
74 // header. The caller has already opened filename, to get f, and read
75 // at least 4 kB out, in data.
76 func readELFGoBuildID(filename string, f *os.File, data []byte) (buildid string, err error) {
77 // Assume the note content is in the data, already read.
78 // Rewrite the ELF header to set shnum to 0, so that we can pass
79 // the data to elf.NewFile and it will decode the Prog list but not
80 // try to read the section headers and the string table from disk.
81 // That's a waste of I/O when all we care about is the Prog list
82 // and the one ELF note.
83 switch elf.Class(data[elf.EI_CLASS]) {
84 case elf.ELFCLASS32:
85 data[48] = 0
86 data[49] = 0
87 case elf.ELFCLASS64:
88 data[60] = 0
89 data[61] = 0
92 const elfGoBuildIDTag = 4
94 ef, err := elf.NewFile(bytes.NewReader(data))
95 if err != nil {
96 return "", &os.PathError{Path: filename, Op: "parse", Err: err}
98 for _, p := range ef.Progs {
99 if p.Type != elf.PT_NOTE || p.Filesz < 16 {
100 continue
103 var note []byte
104 if p.Off+p.Filesz < uint64(len(data)) {
105 note = data[p.Off : p.Off+p.Filesz]
106 } else {
107 // For some linkers, such as the Solaris linker,
108 // the buildid may not be found in data (which
109 // likely contains the first 16kB of the file)
110 // or even the first few megabytes of the file
111 // due to differences in note segment placement;
112 // in that case, extract the note data manually.
113 _, err = f.Seek(int64(p.Off), io.SeekStart)
114 if err != nil {
115 return "", err
118 note = make([]byte, p.Filesz)
119 _, err = io.ReadFull(f, note)
120 if err != nil {
121 return "", err
125 filesz := p.Filesz
126 for filesz >= 16 {
127 nameSize := ef.ByteOrder.Uint32(note)
128 valSize := ef.ByteOrder.Uint32(note[4:])
129 tag := ef.ByteOrder.Uint32(note[8:])
130 name := note[12:16]
131 if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(name, elfGoNote) {
132 return string(note[16 : 16+valSize]), nil
135 nameSize = (nameSize + 3) &^ 3
136 valSize = (valSize + 3) &^ 3
137 notesz := uint64(12 + nameSize + valSize)
138 if filesz <= notesz {
139 break
141 filesz -= notesz
142 note = note[notesz:]
146 // No note. Treat as successful but build ID empty.
147 return "", nil
150 // The Go build ID is stored at the beginning of the Mach-O __text segment.
151 // The caller has already opened filename, to get f, and read a few kB out, in data.
152 // Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount
153 // of other junk placed in the file ahead of the main text.
154 func readMachoGoBuildID(filename string, f *os.File, data []byte) (buildid string, err error) {
155 // If the data we want has already been read, don't worry about Mach-O parsing.
156 // This is both an optimization and a hedge against the Mach-O parsing failing
157 // in the future due to, for example, the name of the __text section changing.
158 if b, err := readRawGoBuildID(filename, data); b != "" && err == nil {
159 return b, err
162 mf, err := macho.NewFile(f)
163 if err != nil {
164 return "", &os.PathError{Path: filename, Op: "parse", Err: err}
167 sect := mf.Section("__text")
168 if sect == nil {
169 // Every binary has a __text section. Something is wrong.
170 return "", &os.PathError{Path: filename, Op: "parse", Err: fmt.Errorf("cannot find __text section")}
173 // It should be in the first few bytes, but read a lot just in case,
174 // especially given our past problems on OS X with the build ID moving.
175 // There shouldn't be much difference between reading 4kB and 32kB:
176 // the hard part is getting to the data, not transferring it.
177 n := sect.Size
178 if n > uint64(BuildIDReadSize) {
179 n = uint64(BuildIDReadSize)
181 buf := make([]byte, n)
182 if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil {
183 return "", err
186 return readRawGoBuildID(filename, buf)