Skip to content

Commit 20ed603

Browse files
committed
cmd/link: generate Mach-O UUID when -B flag is specified
Currently, on Mach-O, the Go linker doesn't generate LC_UUID in internal linking mode. This causes some macOS system tools unable to track the binary, as well as in some cases the binary unable to access local network on macOS 15. This CL makes the linker start generate LC_UUID. Currently, the UUID is generated if the -B flag is specified. And we'll make it generate UUID by default in a later CL. The -B flag is currently for generating GNU build ID on ELF, which is a similar concept to Mach-O's UUID. Instead of introducing another flag, we just use the same flag and the same setting. Specifically, "-B gobuildid" will generate a UUID based on the Go build ID. For #68678. Cq-Include-Trybots: luci.golang.try:gotip-darwin-amd64_14,gotip-darwin-arm64_13 Change-Id: I90089a78ba144110bf06c1c6836daf2d737ff10a Reviewed-on: https://go-review.googlesource.com/c/go/+/618595 Reviewed-by: Michael Knyszek <mknyszek@google.com> Reviewed-by: Ingo Oeser <nightlyone@googlemail.com> Reviewed-by: Than McIntosh <thanm@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent acd072a commit 20ed603

File tree

5 files changed

+47
-12
lines changed

5 files changed

+47
-12
lines changed

src/cmd/link/internal/ld/elf.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -805,13 +805,19 @@ func elfwritefreebsdsig(out *OutBuf) int {
805805
return int(sh.Size)
806806
}
807807

808-
func addbuildinfo(val string) {
808+
func addbuildinfo(ctxt *Link) {
809+
val := *flagHostBuildid
809810
if val == "gobuildid" {
810811
buildID := *flagBuildid
811812
if buildID == "" {
812813
Exitf("-B gobuildid requires a Go build ID supplied via -buildid")
813814
}
814815

816+
if ctxt.IsDarwin() {
817+
buildinfo = uuidFromGoBuildId(buildID)
818+
return
819+
}
820+
815821
hashedBuildID := hash.Sum32([]byte(buildID))
816822
buildinfo = hashedBuildID[:20]
817823

@@ -821,11 +827,13 @@ func addbuildinfo(val string) {
821827
if !strings.HasPrefix(val, "0x") {
822828
Exitf("-B argument must start with 0x: %s", val)
823829
}
824-
825830
ov := val
826831
val = val[2:]
827832

828-
const maxLen = 32
833+
maxLen := 32
834+
if ctxt.IsDarwin() {
835+
maxLen = 16
836+
}
829837
if hex.DecodedLen(len(val)) > maxLen {
830838
Exitf("-B option too long (max %d digits): %s", maxLen, ov)
831839
}

src/cmd/link/internal/ld/macho.go

+16
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ func getMachoHdr() *MachoHdr {
297297
return &machohdr
298298
}
299299

300+
// Create a new Mach-O load command. ndata is the number of 32-bit words for
301+
// the data (not including the load command header).
300302
func newMachoLoad(arch *sys.Arch, type_ uint32, ndata uint32) *MachoLoad {
301303
if arch.PtrSize == 8 && (ndata&1 != 0) {
302304
ndata++
@@ -849,6 +851,20 @@ func asmbMacho(ctxt *Link) {
849851
}
850852
}
851853

854+
if ctxt.IsInternal() && len(buildinfo) > 0 {
855+
ml := newMachoLoad(ctxt.Arch, LC_UUID, 4)
856+
// Mach-O UUID is 16 bytes
857+
if len(buildinfo) < 16 {
858+
buildinfo = append(buildinfo, make([]byte, 16)...)
859+
}
860+
// By default, buildinfo is already in UUIDv3 format
861+
// (see uuidFromGoBuildId).
862+
ml.data[0] = ctxt.Arch.ByteOrder.Uint32(buildinfo)
863+
ml.data[1] = ctxt.Arch.ByteOrder.Uint32(buildinfo[4:])
864+
ml.data[2] = ctxt.Arch.ByteOrder.Uint32(buildinfo[8:])
865+
ml.data[3] = ctxt.Arch.ByteOrder.Uint32(buildinfo[12:])
866+
}
867+
852868
if ctxt.IsInternal() && ctxt.NeedCodeSign() {
853869
ml := newMachoLoad(ctxt.Arch, LC_CODE_SIGNATURE, 2)
854870
ml.data[0] = uint32(codesigOff)

src/cmd/link/internal/ld/macho_update_uuid.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func uuidFromGoBuildId(buildID string) []byte {
4242
// to use this UUID flavor than any of the others. This is similar
4343
// to how other linkers handle this (for example this code in lld:
4444
// https://github.com/llvm/llvm-project/blob/2a3a79ce4c2149d7787d56f9841b66cacc9061d0/lld/MachO/Writer.cpp#L524).
45-
rv[6] &= 0xcf
45+
rv[6] &= 0x0f
4646
rv[6] |= 0x30
4747
rv[8] &= 0x3f
4848
rv[8] |= 0xc0

src/cmd/link/internal/ld/main.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ var (
9595
flagN = flag.Bool("n", false, "no-op (deprecated)")
9696
FlagS = flag.Bool("s", false, "disable symbol table")
9797
flag8 bool // use 64-bit addresses in symbol table
98+
flagHostBuildid = flag.String("B", "", "set ELF NT_GNU_BUILD_ID `note` or Mach-O UUID; use \"gobuildid\" to generate it from the Go build ID")
9899
flagInterpreter = flag.String("I", "", "use `linker` as ELF dynamic linker")
99100
flagCheckLinkname = flag.Bool("checklinkname", true, "check linkname symbol references")
100101
FlagDebugTramp = flag.Int("debugtramp", 0, "debug trampolines")
@@ -196,7 +197,6 @@ func Main(arch *sys.Arch, theArch Arch) {
196197
flag.Var(&ctxt.LinkMode, "linkmode", "set link `mode`")
197198
flag.Var(&ctxt.BuildMode, "buildmode", "set build `mode`")
198199
flag.BoolVar(&ctxt.compressDWARF, "compressdwarf", true, "compress DWARF if possible")
199-
objabi.Flagfn1("B", "add an ELF NT_GNU_BUILD_ID `note` when using ELF; use \"gobuildid\" to generate it from the Go build ID", addbuildinfo)
200200
objabi.Flagfn1("L", "add specified `directory` to library path", func(a string) { Lflag(ctxt, a) })
201201
objabi.AddVersionFlag() // -V
202202
objabi.Flagfn1("X", "add string value `definition` of the form importpath.name=value", func(s string) { addstrdata1(ctxt, s) })
@@ -294,6 +294,10 @@ func Main(arch *sys.Arch, theArch Arch) {
294294
*flagBuildid = "go-openbsd"
295295
}
296296

297+
if *flagHostBuildid != "" {
298+
addbuildinfo(ctxt)
299+
}
300+
297301
// enable benchmarking
298302
var bench *benchmark.Metrics
299303
if len(*benchmarkFlag) != 0 {

test/fixedbugs/issue14636.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,29 @@ import (
1212
"bytes"
1313
"log"
1414
"os/exec"
15+
"runtime"
1516
"strings"
1617
)
1718

1819
func main() {
19-
checkLinkOutput("", "-B argument must start with 0x")
20+
// The cannot open file error indicates that the parsing of -B flag
21+
// succeeded and it failed at a later step.
2022
checkLinkOutput("0", "-B argument must start with 0x")
21-
checkLinkOutput("0x", "usage")
23+
checkLinkOutput("0x", "cannot open file nonexistent.o")
2224
checkLinkOutput("0x0", "-B argument must have even number of digits")
23-
checkLinkOutput("0x00", "usage")
25+
checkLinkOutput("0x00", "cannot open file nonexistent.o")
2426
checkLinkOutput("0xYZ", "-B argument contains invalid hex digit")
25-
checkLinkOutput("0x"+strings.Repeat("00", 32), "usage")
26-
checkLinkOutput("0x"+strings.Repeat("00", 33), "-B option too long (max 32 digits)")
27+
28+
maxLen := 32
29+
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
30+
maxLen = 16
31+
}
32+
checkLinkOutput("0x"+strings.Repeat("00", maxLen), "cannot open file nonexistent.o")
33+
checkLinkOutput("0x"+strings.Repeat("00", maxLen+1), "-B option too long")
2734
}
2835

2936
func checkLinkOutput(buildid string, message string) {
30-
cmd := exec.Command("go", "tool", "link", "-B", buildid)
37+
cmd := exec.Command("go", "tool", "link", "-B", buildid, "nonexistent.o")
3138
out, err := cmd.CombinedOutput()
3239
if err == nil {
3340
log.Fatalf("expected cmd/link to fail")
@@ -39,6 +46,6 @@ func checkLinkOutput(buildid string, message string) {
3946
}
4047

4148
if !strings.Contains(firstLine, message) {
42-
log.Fatalf("cmd/link output did not include expected message %q: %s", message, firstLine)
49+
log.Fatalf("%s: cmd/link output did not include expected message %q: %s", buildid, message, firstLine)
4350
}
4451
}

0 commit comments

Comments
 (0)