cmd/link: support PIE internal linking on darwin/amd64

This CL adds support of PIE internal linking on darwin/amd64.

This is also preparation for supporting internal linking on
darwin/arm64 (macOS), which requires PIE for everything.

Updates #38485.

Change-Id: I2ed58583dcc102f5e0521982491fc7ba6f2754ed
Reviewed-on: https://go-review.googlesource.com/c/go/+/261642
Trust: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
diff --git a/misc/cgo/test/issue4029.c b/misc/cgo/test/issue4029.c
index 30646ad..e6a777f 100644
--- a/misc/cgo/test/issue4029.c
+++ b/misc/cgo/test/issue4029.c
@@ -3,6 +3,7 @@
 // license that can be found in the LICENSE file.
 
 // +build !windows,!static
+// +build !darwin !internal_pie
 
 #include <stdint.h>
 #include <dlfcn.h>
diff --git a/misc/cgo/test/issue4029.go b/misc/cgo/test/issue4029.go
index 1bf029d..8602ce19 100644
--- a/misc/cgo/test/issue4029.go
+++ b/misc/cgo/test/issue4029.go
@@ -3,6 +3,10 @@
 // license that can be found in the LICENSE file.
 
 // +build !windows,!static
+// +build !darwin !internal_pie
+
+// Excluded in darwin internal linking PIE mode, as dynamic export is not
+// supported.
 
 package cgotest
 
diff --git a/misc/cgo/test/issue4029w.go b/misc/cgo/test/issue4029w.go
index eee33f7..de0cf21 100644
--- a/misc/cgo/test/issue4029w.go
+++ b/misc/cgo/test/issue4029w.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build windows static
+// +build windows static darwin,internal_pie
 
 package cgotest
 
diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go
index 03e6866..94b8161 100644
--- a/src/cmd/dist/test.go
+++ b/src/cmd/dist/test.go
@@ -964,10 +964,10 @@
 
 func (t *tester) internalLinkPIE() bool {
 	switch goos + "-" + goarch {
-	case "linux-amd64", "linux-arm64",
-		"android-arm64":
-		return true
-	case "windows-amd64", "windows-386", "windows-arm":
+	case "darwin-amd64",
+		"linux-amd64", "linux-arm64",
+		"android-arm64",
+		"windows-amd64", "windows-386", "windows-arm":
 		return true
 	}
 	return false
@@ -1100,6 +1100,13 @@
 
 		cmd = t.addCmd(dt, "misc/cgo/test", t.goTest(), "-ldflags", "-linkmode=external -s")
 
+		if t.supportedBuildmode("pie") {
+			t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie")
+			if t.internalLink() && t.internalLinkPIE() {
+				t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie", "-ldflags=-linkmode=internal", "-tags=internal,internal_pie")
+			}
+		}
+
 	case "aix-ppc64",
 		"android-arm", "android-arm64",
 		"dragonfly-amd64",
@@ -1151,7 +1158,7 @@
 			if t.supportedBuildmode("pie") {
 				t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie")
 				if t.internalLink() && t.internalLinkPIE() {
-					t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie", "-ldflags=-linkmode=internal")
+					t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie", "-ldflags=-linkmode=internal", "-tags=internal,internal_pie")
 				}
 				t.addCmd(dt, "misc/cgo/testtls", t.goTest(), "-buildmode=pie")
 				t.addCmd(dt, "misc/cgo/nocgo", t.goTest(), "-buildmode=pie")
diff --git a/src/cmd/internal/sys/supported.go b/src/cmd/internal/sys/supported.go
index 55709f3..07be998 100644
--- a/src/cmd/internal/sys/supported.go
+++ b/src/cmd/internal/sys/supported.go
@@ -118,7 +118,8 @@
 
 func InternalLinkPIESupported(goos, goarch string) bool {
 	switch goos + "/" + goarch {
-	case "linux/amd64", "linux/arm64",
+	case "darwin/amd64",
+		"linux/amd64", "linux/arm64",
 		"android/arm64",
 		"windows-amd64", "windows-386", "windows-arm":
 		return true
diff --git a/src/cmd/link/internal/amd64/asm.go b/src/cmd/link/internal/amd64/asm.go
index e5a6ef5..3658ac0 100644
--- a/src/cmd/link/internal/amd64/asm.go
+++ b/src/cmd/link/internal/amd64/asm.go
@@ -76,9 +76,9 @@
 		targType = ldr.SymType(targ)
 	}
 
-	switch r.Type() {
+	switch rt := r.Type(); rt {
 	default:
-		if r.Type() >= objabi.ElfRelocOffset {
+		if rt >= objabi.ElfRelocOffset {
 			ldr.Errorf(s, "unexpected relocation type %d (%s)", r.Type(), sym.RelocName(target.Arch, r.Type()))
 			return false
 		}
@@ -167,13 +167,24 @@
 	case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_UNSIGNED*2 + 0,
 		objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SIGNED*2 + 0,
 		objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_BRANCH*2 + 0:
-		// TODO: What is the difference between all these?
 		su := ldr.MakeSymbolUpdater(s)
 		su.SetRelocType(rIdx, objabi.R_ADDR)
 
 		if targType == sym.SDYNIMPORT {
 			ldr.Errorf(s, "unexpected reloc for dynamic symbol %s", ldr.SymName(targ))
 		}
+		if target.IsPIE() && target.IsInternal() {
+			// For internal linking PIE, this R_ADDR relocation cannot
+			// be resolved statically. We need to generate a dynamic
+			// relocation. Let the code below handle it.
+			if rt == objabi.MachoRelocOffset+ld.MACHO_X86_64_RELOC_UNSIGNED*2 {
+				break
+			} else {
+				// MACHO_X86_64_RELOC_SIGNED or MACHO_X86_64_RELOC_BRANCH
+				// Can this happen? The object is expected to be PIC.
+				ldr.Errorf(s, "unsupported relocation for PIE: %v", rt)
+			}
+		}
 		return true
 
 	case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_BRANCH*2 + 1:
@@ -223,7 +234,7 @@
 		if targType != sym.SDYNIMPORT {
 			ldr.Errorf(s, "unexpected GOT reloc for non-dynamic symbol %s", ldr.SymName(targ))
 		}
-		ld.AddGotSym(target, ldr, syms, targ, uint32(elf.R_X86_64_GLOB_DAT))
+		ld.AddGotSym(target, ldr, syms, targ, 0)
 		su := ldr.MakeSymbolUpdater(s)
 		su.SetRelocType(rIdx, objabi.R_PCREL)
 		su.SetRelocSym(rIdx, syms.GOT)
@@ -355,28 +366,15 @@
 			return true
 		}
 
-		if target.IsDarwin() && ldr.SymSize(s) == int64(target.Arch.PtrSize) && r.Off() == 0 {
+		if target.IsDarwin() {
 			// Mach-O relocations are a royal pain to lay out.
-			// They use a compact stateful bytecode representation
-			// that is too much bother to deal with.
-			// Instead, interpret the C declaration
-			//	void *_Cvar_stderr = &stderr;
-			// as making _Cvar_stderr the name of a GOT entry
-			// for stderr. This is separate from the usual GOT entry,
-			// just in case the C code assigns to the variable,
-			// and of course it only works for single pointers,
-			// but we only need to support cgo and that's all it needs.
-			ld.Adddynsym(ldr, target, syms, targ)
-
-			got := ldr.MakeSymbolUpdater(syms.GOT)
-			su := ldr.MakeSymbolUpdater(s)
-			su.SetType(got.Type())
-			got.AddInteriorSym(s)
-			su.SetValue(got.Size())
-			got.AddUint64(target.Arch, 0)
-			leg := ldr.MakeSymbolUpdater(syms.LinkEditGOT)
-			leg.AddUint32(target.Arch, uint32(ldr.SymDynid(targ)))
-			su.SetRelocType(rIdx, objabi.ElfRelocOffset) // ignore during relocsym
+			// They use a compact stateful bytecode representation.
+			// Here we record what are needed and encode them later.
+			ld.MachoAddRebase(s, int64(r.Off()))
+			// Not mark r done here. So we still apply it statically,
+			// so in the file content we'll also have the right offset
+			// to the relocation target. So it can be examined statically
+			// (e.g. go version).
 			return true
 		}
 	}
@@ -627,26 +625,16 @@
 
 		ldr.SetPlt(s, int32(plt.Size()-16))
 	} else if target.IsDarwin() {
-		// To do lazy symbol lookup right, we're supposed
-		// to tell the dynamic loader which library each
-		// symbol comes from and format the link info
-		// section just so. I'm too lazy (ha!) to do that
-		// so for now we'll just use non-lazy pointers,
-		// which don't need to be told which library to use.
-		//
-		// https://networkpx.blogspot.com/2009/09/about-lcdyldinfoonly-command.html
-		// has details about what we're avoiding.
-
-		ld.AddGotSym(target, ldr, syms, s, uint32(elf.R_X86_64_GLOB_DAT))
-		plt := ldr.MakeSymbolUpdater(syms.PLT)
+		ld.AddGotSym(target, ldr, syms, s, 0)
 
 		sDynid := ldr.SymDynid(s)
 		lep := ldr.MakeSymbolUpdater(syms.LinkEditPLT)
 		lep.AddUint32(target.Arch, uint32(sDynid))
 
-		// jmpq *got+size(IP)
+		plt := ldr.MakeSymbolUpdater(syms.PLT)
 		ldr.SetPlt(s, int32(plt.Size()))
 
+		// jmpq *got+size(IP)
 		plt.AddUint8(0xff)
 		plt.AddUint8(0x25)
 		plt.AddPCRelPlus(target.Arch, syms.GOT, int64(ldr.SymGot(s)))
@@ -654,6 +642,7 @@
 		ldr.Errorf(s, "addpltsym: unsupported binary format")
 	}
 }
+
 func tlsIEtoLE(P []byte, off, size int) {
 	// Transform the PC-relative instruction into a constant load.
 	// That is,
diff --git a/src/cmd/link/internal/ld/config.go b/src/cmd/link/internal/ld/config.go
index aaf74b5..c680d11 100644
--- a/src/cmd/link/internal/ld/config.go
+++ b/src/cmd/link/internal/ld/config.go
@@ -222,6 +222,7 @@
 		switch objabi.GOOS + "/" + objabi.GOARCH {
 		case "linux/amd64", "linux/arm64", "android/arm64":
 		case "windows/386", "windows/amd64", "windows/arm":
+		case "darwin/amd64":
 		default:
 			// Internal linking does not support TLS_IE.
 			return true, "buildmode=pie"
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index 5fe028d..8d04973 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -2526,6 +2526,12 @@
 	} else if target.IsDarwin() {
 		leg := ldr.MakeSymbolUpdater(syms.LinkEditGOT)
 		leg.AddUint32(target.Arch, uint32(ldr.SymDynid(s)))
+		if target.IsPIE() && target.IsInternal() {
+			// Mach-O relocations are a royal pain to lay out.
+			// They use a compact stateful bytecode representation.
+			// Here we record what are needed and encode them later.
+			MachoAddBind(int64(ldr.SymGot(s)), s)
+		}
 	} else {
 		ldr.Errorf(s, "addgotsym: unsupported binary format")
 	}
diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go
index eab6537..a19a4af 100644
--- a/src/cmd/link/internal/ld/macho.go
+++ b/src/cmd/link/internal/ld/macho.go
@@ -118,6 +118,8 @@
 	MH_EXECUTE = 0x2
 
 	MH_NOUNDEFS = 0x1
+	MH_DYLDLINK = 0x4
+	MH_PIE      = 0x200000
 )
 
 const (
@@ -193,6 +195,56 @@
 	PLATFORM_BRIDGEOS MachoPlatform = 5
 )
 
+// rebase table opcode
+const (
+	REBASE_TYPE_POINTER         = 1
+	REBASE_TYPE_TEXT_ABSOLUTE32 = 2
+	REBASE_TYPE_TEXT_PCREL32    = 3
+
+	REBASE_OPCODE_MASK                               = 0xF0
+	REBASE_IMMEDIATE_MASK                            = 0x0F
+	REBASE_OPCODE_DONE                               = 0x00
+	REBASE_OPCODE_SET_TYPE_IMM                       = 0x10
+	REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB        = 0x20
+	REBASE_OPCODE_ADD_ADDR_ULEB                      = 0x30
+	REBASE_OPCODE_ADD_ADDR_IMM_SCALED                = 0x40
+	REBASE_OPCODE_DO_REBASE_IMM_TIMES                = 0x50
+	REBASE_OPCODE_DO_REBASE_ULEB_TIMES               = 0x60
+	REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB            = 0x70
+	REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB = 0x80
+)
+
+// bind table opcode
+const (
+	BIND_TYPE_POINTER         = 1
+	BIND_TYPE_TEXT_ABSOLUTE32 = 2
+	BIND_TYPE_TEXT_PCREL32    = 3
+
+	BIND_SPECIAL_DYLIB_SELF            = 0
+	BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = -1
+	BIND_SPECIAL_DYLIB_FLAT_LOOKUP     = -2
+	BIND_SPECIAL_DYLIB_WEAK_LOOKUP     = -3
+
+	BIND_OPCODE_MASK                                         = 0xF0
+	BIND_IMMEDIATE_MASK                                      = 0x0F
+	BIND_OPCODE_DONE                                         = 0x00
+	BIND_OPCODE_SET_DYLIB_ORDINAL_IMM                        = 0x10
+	BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB                       = 0x20
+	BIND_OPCODE_SET_DYLIB_SPECIAL_IMM                        = 0x30
+	BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM                = 0x40
+	BIND_OPCODE_SET_TYPE_IMM                                 = 0x50
+	BIND_OPCODE_SET_ADDEND_SLEB                              = 0x60
+	BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB                  = 0x70
+	BIND_OPCODE_ADD_ADDR_ULEB                                = 0x80
+	BIND_OPCODE_DO_BIND                                      = 0x90
+	BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB                        = 0xA0
+	BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED                  = 0xB0
+	BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB             = 0xC0
+	BIND_OPCODE_THREADED                                     = 0xD0
+	BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB = 0x00
+	BIND_SUBOPCODE_THREADED_APPLY                            = 0x01
+)
+
 // Mach-O file writing
 // https://developer.apple.com/mac/library/DOCUMENTATION/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
 
@@ -279,7 +331,7 @@
 
 var linkoff int64
 
-func machowrite(arch *sys.Arch, out *OutBuf, linkmode LinkMode) int {
+func machowrite(ctxt *Link, arch *sys.Arch, out *OutBuf, linkmode LinkMode) int {
 	o1 := out.Offset()
 
 	loadsize := 4 * 4 * ndebug
@@ -308,11 +360,14 @@
 	}
 	out.Write32(uint32(len(load)) + uint32(nseg) + uint32(ndebug))
 	out.Write32(uint32(loadsize))
+	flags := uint32(0)
 	if nkind[SymKindUndef] == 0 {
-		out.Write32(MH_NOUNDEFS) /* flags - no undefines */
-	} else {
-		out.Write32(0) /* flags */
+		flags |= MH_NOUNDEFS
 	}
+	if ctxt.IsPIE() && linkmode == LinkInternal {
+		flags |= MH_PIE | MH_DYLDLINK
+	}
+	out.Write32(flags) /* flags */
 	if arch.PtrSize == 8 {
 		out.Write32(0) /* reserved */
 	}
@@ -712,15 +767,17 @@
 		s2 := ldr.SymSize(ctxt.ArchSyms.LinkEditPLT)
 		s3 := ldr.SymSize(ctxt.ArchSyms.LinkEditGOT)
 		s4 := ldr.SymSize(ldr.Lookup(".machosymstr", 0))
+		s5 := ldr.SymSize(ldr.Lookup(".machorebase", 0))
+		s6 := ldr.SymSize(ldr.Lookup(".machobind", 0))
 
 		if ctxt.LinkMode != LinkExternal {
 			ms := newMachoSeg("__LINKEDIT", 0)
 			ms.vaddr = uint64(Rnd(int64(Segdata.Vaddr+Segdata.Length), int64(*FlagRound)))
-			ms.vsize = uint64(s1) + uint64(s2) + uint64(s3) + uint64(s4)
+			ms.vsize = uint64(s1 + s2 + s3 + s4 + s5 + s6)
 			ms.fileoffset = uint64(linkoff)
 			ms.filesize = ms.vsize
-			ms.prot1 = 7
-			ms.prot2 = 3
+			ms.prot1 = 1
+			ms.prot2 = 1
 		}
 
 		ml := newMachoLoad(ctxt.Arch, LC_SYMTAB, 4)
@@ -745,9 +802,23 @@
 				stringtouint32(ml.data[4:], lib)
 			}
 		}
+
+		if ctxt.LinkMode != LinkExternal && ctxt.IsPIE() {
+			ml := newMachoLoad(ctxt.Arch, LC_DYLD_INFO_ONLY, 10)
+			ml.data[0] = uint32(linkoff + s1 + s2 + s3 + s4)      // rebase off
+			ml.data[1] = uint32(s5)                               // rebase size
+			ml.data[2] = uint32(linkoff + s1 + s2 + s3 + s4 + s5) // bind off
+			ml.data[3] = uint32(s6)                               // bind size
+			ml.data[4] = 0                                        // weak bind off
+			ml.data[5] = 0                                        // weak bind size
+			ml.data[6] = 0                                        // lazy bind off
+			ml.data[7] = 0                                        // lazy bind size
+			ml.data[8] = 0                                        // export
+			ml.data[9] = 0                                        // export size
+		}
 	}
 
-	a := machowrite(ctxt.Arch, ctxt.Out, ctxt.LinkMode)
+	a := machowrite(ctxt, ctxt.Arch, ctxt.Out, ctxt.LinkMode)
 	if int32(a) > HEADR {
 		Exitf("HEADR too small: %d > %d", a, HEADR)
 	}
@@ -989,6 +1060,8 @@
 func doMachoLink(ctxt *Link) int64 {
 	machosymtab(ctxt)
 
+	machoDyldInfo(ctxt)
+
 	ldr := ctxt.loader
 
 	// write data that will be linkedit section
@@ -996,6 +1069,8 @@
 	s2 := ctxt.ArchSyms.LinkEditPLT
 	s3 := ctxt.ArchSyms.LinkEditGOT
 	s4 := ldr.Lookup(".machosymstr", 0)
+	s5 := ldr.Lookup(".machorebase", 0)
+	s6 := ldr.Lookup(".machobind", 0)
 
 	// Force the linkedit section to end on a 16-byte
 	// boundary. This allows pure (non-cgo) Go binaries
@@ -1019,7 +1094,7 @@
 		s4b.AddUint8(0)
 	}
 
-	size := int(ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4))
+	size := int(ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4) + ldr.SymSize(s5) + ldr.SymSize(s6))
 
 	if size > 0 {
 		linkoff = Rnd(int64(uint64(HEADR)+Segtext.Length), int64(*FlagRound)) + Rnd(int64(Segrelrodata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdwarf.Filelen), int64(*FlagRound))
@@ -1029,6 +1104,8 @@
 		ctxt.Out.Write(ldr.Data(s2))
 		ctxt.Out.Write(ldr.Data(s3))
 		ctxt.Out.Write(ldr.Data(s4))
+		ctxt.Out.Write(ldr.Data(s5))
+		ctxt.Out.Write(ldr.Data(s6))
 	}
 
 	return Rnd(int64(size), int64(*FlagRound))
@@ -1172,3 +1249,134 @@
 	}
 	return nil, nil
 }
+
+// A rebase entry tells the dynamic linker the data at sym+off needs to be
+// relocated when the in-memory image moves. (This is somewhat like, say,
+// ELF R_X86_64_RELATIVE).
+// For now, the only kind of entry we support is that the data is an absolute
+// address. That seems all we need.
+// In the binary it uses a compact stateful bytecode encoding. So we record
+// entries as we go and build the table at the end.
+type machoRebaseRecord struct {
+	sym loader.Sym
+	off int64
+}
+
+var machorebase []machoRebaseRecord
+
+func MachoAddRebase(s loader.Sym, off int64) {
+	machorebase = append(machorebase, machoRebaseRecord{s, off})
+}
+
+// A bind entry tells the dynamic linker the data at GOT+off should be bound
+// to the address of the target symbol, which is a dynamic import.
+// For now, the only kind of entry we support is that the data is an absolute
+// address, and the source symbol is always the GOT. That seems all we need.
+// In the binary it uses a compact stateful bytecode encoding. So we record
+// entries as we go and build the table at the end.
+type machoBindRecord struct {
+	off  int64
+	targ loader.Sym
+}
+
+var machobind []machoBindRecord
+
+func MachoAddBind(off int64, targ loader.Sym) {
+	machobind = append(machobind, machoBindRecord{off, targ})
+}
+
+// Generate data for the dynamic linker, used in LC_DYLD_INFO_ONLY load command.
+// See mach-o/loader.h, struct dyld_info_command, for the encoding.
+// e.g. https://opensource.apple.com/source/xnu/xnu-6153.81.5/EXTERNAL_HEADERS/mach-o/loader.h
+func machoDyldInfo(ctxt *Link) {
+	ldr := ctxt.loader
+	rebase := ldr.CreateSymForUpdate(".machorebase", 0)
+	bind := ldr.CreateSymForUpdate(".machobind", 0)
+
+	if !(ctxt.IsPIE() && ctxt.IsInternal()) {
+		return
+	}
+
+	segId := func(seg *sym.Segment) uint8 {
+		switch seg {
+		case &Segtext:
+			return 1
+		case &Segrelrodata:
+			return 2
+		case &Segdata:
+			if Segrelrodata.Length > 0 {
+				return 3
+			}
+			return 2
+		}
+		panic("unknown segment")
+	}
+
+	dylibId := func(s loader.Sym) int {
+		slib := ldr.SymDynimplib(s)
+		for i, lib := range dylib {
+			if lib == slib {
+				return i + 1
+			}
+		}
+		return BIND_SPECIAL_DYLIB_FLAT_LOOKUP // don't know where it is from
+	}
+
+	// Rebase table.
+	// TODO: use more compact encoding. The encoding is stateful, and
+	// we can use delta encoding.
+	rebase.AddUint8(REBASE_OPCODE_SET_TYPE_IMM | REBASE_TYPE_POINTER)
+	for _, r := range machorebase {
+		seg := ldr.SymSect(r.sym).Seg
+		off := uint64(ldr.SymValue(r.sym)+r.off) - seg.Vaddr
+		rebase.AddUint8(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | segId(seg))
+		rebase.AddUleb(off)
+
+		rebase.AddUint8(REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1)
+	}
+	rebase.AddUint8(REBASE_OPCODE_DONE)
+	sz := Rnd(rebase.Size(), 8)
+	rebase.Grow(sz)
+	rebase.SetSize(sz)
+
+	// Bind table.
+	// TODO: compact encoding, as above.
+	// TODO: lazy binding?
+	got := ctxt.GOT
+	seg := ldr.SymSect(got).Seg
+	gotAddr := ldr.SymValue(got)
+	bind.AddUint8(BIND_OPCODE_SET_TYPE_IMM | BIND_TYPE_POINTER)
+	for _, r := range machobind {
+		off := uint64(gotAddr+r.off) - seg.Vaddr
+		bind.AddUint8(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | segId(seg))
+		bind.AddUleb(off)
+
+		d := dylibId(r.targ)
+		if d > 0 && d < 128 {
+			bind.AddUint8(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | uint8(d)&0xf)
+		} else if d >= 128 {
+			bind.AddUint8(BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB)
+			bind.AddUleb(uint64(d))
+		} else { // d <= 0
+			bind.AddUint8(BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | uint8(d)&0xf)
+		}
+
+		bind.AddUint8(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM)
+		// target symbol name as a C string, with _ prefix
+		bind.AddUint8('_')
+		bind.Addstring(ldr.SymExtname(r.targ))
+
+		bind.AddUint8(BIND_OPCODE_DO_BIND)
+	}
+	bind.AddUint8(BIND_OPCODE_DONE)
+	sz = Rnd(bind.Size(), 16) // make it 16-byte aligned, see the comment in doMachoLink
+	bind.Grow(sz)
+	bind.SetSize(sz)
+
+	// TODO: export table.
+	// The symbols names are encoded as a trie. I'm really too lazy to do that
+	// for now.
+	// Without it, the symbols are not dynamically exported, so they cannot be
+	// e.g. dlsym'd. But internal linking is not the default in that case, so
+	// it is fine.
+}
diff --git a/src/cmd/link/internal/loader/symbolbuilder.go b/src/cmd/link/internal/loader/symbolbuilder.go
index c0c723d..5d37da8 100644
--- a/src/cmd/link/internal/loader/symbolbuilder.go
+++ b/src/cmd/link/internal/loader/symbolbuilder.go
@@ -420,3 +420,21 @@
 		sb.l.SetAttrReadOnly(sb.symIdx, false)
 	}
 }
+
+func (sb *SymbolBuilder) AddUleb(v uint64) {
+	if v < 128 { // common case: 1 byte
+		sb.AddUint8(uint8(v))
+		return
+	}
+	for {
+		c := uint8(v & 0x7f)
+		v >>= 7
+		if v != 0 {
+			c |= 0x80
+		}
+		sb.AddUint8(c)
+		if c&0x80 == 0 {
+			break
+		}
+	}
+}