github.com/FenixAra/go@v0.0.0-20170127160404-96ea0918e670/src/cmd/go/note.go (about) 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. 4 5 package main 6 7 import ( 8 "bytes" 9 "debug/elf" 10 "debug/macho" 11 "encoding/binary" 12 "fmt" 13 "io" 14 "os" 15 ) 16 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 23 } 24 data = data[:sz] 25 return data, nil 26 } 27 28 func readELFNote(filename, name string, typ int32) ([]byte, error) { 29 f, err := elf.Open(filename) 30 if err != nil { 31 return nil, err 32 } 33 for _, sect := range f.Sections { 34 if sect.Type != elf.SHT_NOTE { 35 continue 36 } 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 44 } 45 return nil, fmt.Errorf("read namesize failed: %v", err) 46 } 47 err = binary.Read(r, f.ByteOrder, &descsize) 48 if err != nil { 49 return nil, fmt.Errorf("read descsize failed: %v", err) 50 } 51 err = binary.Read(r, f.ByteOrder, ¬eType) 52 if err != nil { 53 return nil, fmt.Errorf("read type failed: %v", err) 54 } 55 noteName, err := readAligned4(r, namesize) 56 if err != nil { 57 return nil, fmt.Errorf("read name failed: %v", err) 58 } 59 desc, err := readAligned4(r, descsize) 60 if err != nil { 61 return nil, fmt.Errorf("read desc failed: %v", err) 62 } 63 if name == string(noteName) && typ == noteType { 64 return desc, nil 65 } 66 } 67 } 68 return nil, nil 69 } 70 71 var elfGoNote = []byte("Go\x00\x00") 72 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 90 } 91 92 const elfGoBuildIDTag = 4 93 94 ef, err := elf.NewFile(bytes.NewReader(data)) 95 if err != nil { 96 return "", &os.PathError{Path: filename, Op: "parse", Err: err} 97 } 98 for _, p := range ef.Progs { 99 if p.Type != elf.PT_NOTE || p.Filesz < 16 { 100 continue 101 } 102 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 116 } 117 118 note = make([]byte, p.Filesz) 119 _, err = io.ReadFull(f, note) 120 if err != nil { 121 return "", err 122 } 123 } 124 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 133 } 134 135 nameSize = (nameSize + 3) &^ 3 136 valSize = (valSize + 3) &^ 3 137 notesz := uint64(12 + nameSize + valSize) 138 if filesz <= notesz { 139 break 140 } 141 filesz -= notesz 142 note = note[notesz:] 143 } 144 } 145 146 // No note. Treat as successful but build ID empty. 147 return "", nil 148 } 149 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 160 } 161 162 mf, err := macho.NewFile(f) 163 if err != nil { 164 return "", &os.PathError{Path: filename, Op: "parse", Err: err} 165 } 166 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")} 171 } 172 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) 180 } 181 buf := make([]byte, n) 182 if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil { 183 return "", err 184 } 185 186 return readRawGoBuildID(filename, buf) 187 }