github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/builder/buildid.go (about) 1 package builder 2 3 import ( 4 "bytes" 5 "debug/elf" 6 "debug/macho" 7 "encoding/binary" 8 "fmt" 9 "io" 10 "os" 11 "runtime" 12 ) 13 14 // ReadBuildID reads the build ID from the currently running executable. 15 func ReadBuildID() ([]byte, error) { 16 executable, err := os.Executable() 17 if err != nil { 18 return nil, err 19 } 20 f, err := os.Open(executable) 21 if err != nil { 22 return nil, err 23 } 24 defer f.Close() 25 26 switch runtime.GOOS { 27 case "linux", "freebsd", "android": 28 // Read the GNU build id section. (Not sure about FreeBSD though...) 29 file, err := elf.NewFile(f) 30 if err != nil { 31 return nil, err 32 } 33 var gnuID, goID []byte 34 for _, section := range file.Sections { 35 if section.Type != elf.SHT_NOTE || 36 (section.Name != ".note.gnu.build-id" && section.Name != ".note.go.buildid") { 37 continue 38 } 39 buf := make([]byte, section.Size) 40 n, err := section.ReadAt(buf, 0) 41 if uint64(n) != section.Size || err != nil { 42 return nil, fmt.Errorf("could not read build id: %w", err) 43 } 44 if section.Name == ".note.gnu.build-id" { 45 gnuID = buf 46 } else { 47 goID = buf 48 } 49 } 50 if gnuID != nil { 51 return gnuID, nil 52 } else if goID != nil { 53 return goID, nil 54 } 55 case "darwin": 56 // Read the LC_UUID load command, which contains the equivalent of a 57 // build ID. 58 file, err := macho.NewFile(f) 59 if err != nil { 60 return nil, err 61 } 62 for _, load := range file.Loads { 63 // Unfortunately, the debug/macho package doesn't support the 64 // LC_UUID command directly. So we have to read it from 65 // macho.LoadBytes. 66 load, ok := load.(macho.LoadBytes) 67 if !ok { 68 continue 69 } 70 raw := load.Raw() 71 command := binary.LittleEndian.Uint32(raw) 72 if command != 0x1b { 73 // Looking for the LC_UUID load command. 74 // LC_UUID is defined here as 0x1b: 75 // https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html 76 continue 77 } 78 return raw[4:], nil 79 } 80 81 // Normally we would have found a build ID by now. But not on Nix, 82 // unfortunately, because Nix adds -no_uuid for some reason: 83 // https://github.com/NixOS/nixpkgs/issues/178366 84 // Fall back to the same implementation that we use for Windows. 85 id, err := readRawGoBuildID(f, 32*1024) 86 if len(id) != 0 || err != nil { 87 return id, err 88 } 89 default: 90 // On other platforms (such as Windows) there isn't such a convenient 91 // build ID. Luckily, Go does have an equivalent of the build ID, which 92 // is stored as a special symbol named go.buildid. You can read it 93 // using `go tool buildid`, but the code below extracts it directly 94 // from the binary. 95 // Unfortunately, because of stripping with the -w flag, no symbol 96 // table might be available. Therefore, we have to scan the binary 97 // directly. Luckily the build ID is always at the start of the file. 98 // For details, see: 99 // https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go 100 id, err := readRawGoBuildID(f, 4096) 101 if len(id) != 0 || err != nil { 102 return id, err 103 } 104 } 105 return nil, fmt.Errorf("could not find build ID in %v", executable) 106 } 107 108 // The Go toolchain stores a build ID in the binary that we can use, as a 109 // fallback if binary file specific build IDs can't be obtained. 110 // This function reads that build ID from the binary. 111 func readRawGoBuildID(f *os.File, prefixSize int) ([]byte, error) { 112 fileStart := make([]byte, prefixSize) 113 _, err := io.ReadFull(f, fileStart) 114 if err != nil { 115 return nil, fmt.Errorf("could not read build id from %s: %v", f.Name(), err) 116 } 117 index := bytes.Index(fileStart, []byte("\xff Go build ID: \"")) 118 if index < 0 || index > len(fileStart)-103 { 119 return nil, fmt.Errorf("could not find build id in %s", f.Name()) 120 } 121 buf := fileStart[index : index+103] 122 if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) { 123 return buf[len("\xff Go build ID: \"") : len(buf)-1], nil 124 } 125 126 return nil, nil 127 }