blob: 71e1fbf6b9b6d492b3027380e5ec36f45cc5415a [file] [edit]
// 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 (
imacho "cmd/internal/macho"
"debug/macho"
"io"
"os"
)
// uuidFromHash converts a hash to a UUID
// suitable for use in the Macho LC_UUID load command.
func uuidFromHash(hashed [32]byte) []byte {
rv := make([]byte, 16)
copy(rv, hashed[:16])
// Mark the UUID as version 3, variant 1, which is an MD5-hash-based UUID.
// We are not actually using MD5, but we're also not using SHA1 (version 5),
// and LLVM also uses version 3 [1] so this seems good enough.
//
// [1] https://github.com/llvm/llvm-project/blob/2a3a79ce4c2149d7787d56f9841b66cacc9061d0/lld/MachO/Writer.cpp#L524
rv[6] = (rv[6] &^ 0xF0) | 0x30 // version 3
rv[8] = (rv[8] &^ 0xC0) | 0x80 // variant 1
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
// uuidFromHash.
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
}