| // Copyright 2024 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 ld |
| |
| // This file provides helper functions for updating/rewriting the UUID |
| // load command within a Go go binary generated on Darwin using |
| // external linking. Why is it necessary to update the UUID load |
| // command? See issue #64947 for more detail, but the short answer is |
| // that newer versions of the Macos toolchain (the newer linker in |
| // particular) appear to compute the UUID based not just on the |
| // content of the object files being linked but also on things like |
| // the timestamps/paths of the objects; this makes it |
| // difficult/impossible to support reproducible builds. Since we try |
| // hard to maintain build reproducibility for Go, the APIs here |
| // compute a new UUID (based on the Go build ID) and write it to the |
| // final executable generated by the external linker. |
| |
| import ( |
| "cmd/internal/hash" |
| imacho "cmd/internal/macho" |
| |
| "debug/macho" |
| "io" |
| "os" |
| ) |
| |
| // uuidFromGoBuildId hashes the Go build ID and returns a slice of 16 |
| // bytes suitable for use as the payload in a Macho LC_UUID load |
| // command. |
| func uuidFromGoBuildId(buildID string) []byte { |
| if buildID == "" { |
| return make([]byte, 16) |
| } |
| hashedBuildID := hash.Sum32([]byte(buildID)) |
| rv := hashedBuildID[:16] |
| |
| // RFC 4122 conformance (see RFC 4122 Sections 4.2.2, 4.1.3). We |
| // want the "version" of this UUID to appear as 'hashed' as opposed |
| // to random or time-based. This is something of a fiction since |
| // we're not actually hashing using MD5 or SHA1, but it seems better |
| // to use this UUID flavor than any of the others. This is similar |
| // to how other linkers handle this (for example this code in lld: |
| // https://github.com/llvm/llvm-project/blob/2a3a79ce4c2149d7787d56f9841b66cacc9061d0/lld/MachO/Writer.cpp#L524). |
| rv[6] &= 0x0f |
| rv[6] |= 0x30 |
| rv[8] &= 0x3f |
| rv[8] |= 0xc0 |
| |
| return rv |
| } |
| |
| // machoRewriteUuid copies over the contents of the Macho executable |
| // exef into the output file outexe, and in the process updates the |
| // LC_UUID command to a new value recomputed from the Go build id. |
| func machoRewriteUuid(ctxt *Link, exef *os.File, exem *macho.File, outexe string) error { |
| outf, err := os.OpenFile(outexe, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) |
| if err != nil { |
| return err |
| } |
| defer outf.Close() |
| |
| // Copy over the file. |
| if _, err := io.Copy(outf, exef); err != nil { |
| return err |
| } |
| |
| // Locate the portion of the binary containing the load commands. |
| cmdOffset := imacho.FileHeaderSize(exem) |
| if _, err := outf.Seek(cmdOffset, 0); err != nil { |
| return err |
| } |
| |
| // Read the load commands, looking for the LC_UUID cmd. If/when we |
| // locate it, overwrite it with a new value produced by |
| // uuidFromGoBuildId. |
| reader := imacho.NewLoadCmdUpdater(outf, exem.ByteOrder, cmdOffset) |
| for i := uint32(0); i < exem.Ncmd; i++ { |
| cmd, err := reader.Next() |
| if err != nil { |
| return err |
| } |
| if cmd.Cmd == imacho.LC_UUID { |
| var u uuidCmd |
| if err := reader.ReadAt(0, &u); err != nil { |
| return err |
| } |
| clear(u.Uuid[:]) |
| copy(u.Uuid[:], buildinfo) |
| if err := reader.WriteAt(0, &u); err != nil { |
| return err |
| } |
| break |
| } |
| } |
| |
| // We're done |
| return nil |
| } |