]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link: support PIE internal linking on darwin/amd64
authorCherry Zhang <cherryyz@google.com>
Sun, 11 Oct 2020 19:48:22 +0000 (15:48 -0400)
committerCherry Zhang <cherryyz@google.com>
Wed, 14 Oct 2020 00:19:23 +0000 (00:19 +0000)
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>
misc/cgo/test/issue4029.c
misc/cgo/test/issue4029.go
misc/cgo/test/issue4029w.go
src/cmd/dist/test.go
src/cmd/internal/sys/supported.go
src/cmd/link/internal/amd64/asm.go
src/cmd/link/internal/ld/config.go
src/cmd/link/internal/ld/lib.go
src/cmd/link/internal/ld/macho.go
src/cmd/link/internal/loader/symbolbuilder.go

index 30646ade021d84441329452092d2df5b6b045a99..e6a777fe6465efa0c59959e3e185502a5a1d5ca7 100644 (file)
@@ -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>
index 1bf029d760ab0c50844356a0ac128098e26d7052..8602ce19e2dc68ec2312e7d130a82fd211d26714 100644 (file)
@@ -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
 
index eee33f7010191de587efdd54651dc3045474c0f3..de0cf2138ae531168ecca79a8527acb168c94d5d 100644 (file)
@@ -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
 
index 03e6866d620be3e981efeeda8b50f988ec480625..94b81613a930647b653de221f07c865e8a2078d2 100644 (file)
@@ -964,10 +964,10 @@ func (t *tester) internalLink() bool {
 
 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 @@ func (t *tester) cgoTest(dt *distTest) error {
 
                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 @@ func (t *tester) cgoTest(dt *distTest) error {
                        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")
index 55709f39151967c68755b83df184de0e83d5ebaf..07be998035015f3a48b627d3011253eb245fe4dc 100644 (file)
@@ -118,7 +118,8 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool {
 
 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
index e5a6ef51b05ed4e56de49300074a8aba1172aed6..3658ac0be0ba6f32f66a1935a0c0928623fb8382 100644 (file)
@@ -76,9 +76,9 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
                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 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
        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 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
                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 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
                        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 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
 
                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 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
                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,
index aaf74b58def1b18ff036df43f1bb255c7fa35846..c680d11c1d6f82ee4a9e158e0c9b30b8d557b061 100644 (file)
@@ -222,6 +222,7 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) {
                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"
index 5fe028d3219af3fff42d17a7b0d55ab78ec491f1..8d04973fcf8291aa7ce50605eb9cce3a910a5f59 100644 (file)
@@ -2526,6 +2526,12 @@ func AddGotSym(target *Target, ldr *loader.Loader, syms *ArchSyms, s loader.Sym,
        } 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")
        }
index eab6537c75e559f52bdd34616e2d54965f8a4bc0..a19a4afd9a62ec3af45215757fdae85c0db62088 100644 (file)
@@ -118,6 +118,8 @@ const (
        MH_EXECUTE = 0x2
 
        MH_NOUNDEFS = 0x1
+       MH_DYLDLINK = 0x4
+       MH_PIE      = 0x200000
 )
 
 const (
@@ -193,6 +195,56 @@ const (
        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 dylib []string
 
 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 @@ func machowrite(arch *sys.Arch, out *OutBuf, linkmode LinkMode) int {
        }
        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 @@ func asmbMacho(ctxt *Link) {
                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 @@ func asmbMacho(ctxt *Link) {
                                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 machodysymtab(ctxt *Link) {
 func doMachoLink(ctxt *Link) int64 {
        machosymtab(ctxt)
 
+       machoDyldInfo(ctxt)
+
        ldr := ctxt.loader
 
        // write data that will be linkedit section
@@ -996,6 +1069,8 @@ func doMachoLink(ctxt *Link) int64 {
        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 @@ func doMachoLink(ctxt *Link) int64 {
                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 @@ func doMachoLink(ctxt *Link) int64 {
                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 @@ func peekMachoPlatform(m *macho.File) (*MachoPlatformLoad, error) {
        }
        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.
+}
index c0c723d7f00d94122bd4c0a84983e2466030005a..5d37da8ac6fac22d80a2316411766f1c16b6aee2 100644 (file)
@@ -420,3 +420,21 @@ func (sb *SymbolBuilder) MakeWritable() {
                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
+               }
+       }
+}