mirror of
https://github.com/golang/go.git
synced 2026-04-03 09:49:56 +09:00
cmd/link: support Mach-O UNSIGNED relocations for dynamic imports on darwin
When internally linking darwin binaries, the linker rejected Mach-O
UNSIGNED (pointer) relocations targeting dynamic import symbols,
producing errors like:
unexpected reloc for dynamic symbol _swift_FORCE_LOAD_$_swiftIOKit
These relocations are legitimate and appear in data sections (e.g.
__DATA/__const) of object files that reference external symbols such as
Swift force-load symbols. The dynamic linker (dyld) needs to bind these
pointers at load time.
Cq-Include-Trybots: luci.golang.try:gotip-darwin-arm64_15,gotip-darwin-amd64_14
Change-Id: I1cc759dec28b8aa076602a45062f403d0d9f45fe
Reviewed-on: https://go-review.googlesource.com/c/go/+/745220
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
This commit is contained in:
committed by
Gopher Robot
parent
76ebf63307
commit
f4afab14d0
@@ -8,4 +8,5 @@ package cgotest
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIssue76023(t *testing.T) { issue76023(t) }
|
||||
func TestIssue76023(t *testing.T) { issue76023(t) }
|
||||
func TestGlobalDataDynimport(t *testing.T) { unsignedRelocDynimport(t) }
|
||||
|
||||
41
src/cmd/cgo/internal/test/unsigned_reloc_darwin.go
Normal file
41
src/cmd/cgo/internal/test/unsigned_reloc_darwin.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2026 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.
|
||||
|
||||
//go:build darwin
|
||||
|
||||
package cgotest
|
||||
|
||||
/*
|
||||
#include <stdio.h>
|
||||
|
||||
// Global function pointer to a dynamically-linked libc function.
|
||||
// When compiled to a Mach-O object, this produces a RELOC_UNSIGNED
|
||||
// relocation targeting the external symbol _puts.
|
||||
int (*_cgo_test_dynref_puts)(const char *) = puts;
|
||||
|
||||
static int cgo_call_dynref(void) {
|
||||
if (_cgo_test_dynref_puts == 0) {
|
||||
return -1;
|
||||
}
|
||||
// Call the resolved function pointer. puts returns a non-negative
|
||||
// value on success.
|
||||
return _cgo_test_dynref_puts("cgo unsigned reloc test");
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import "testing"
|
||||
|
||||
// unsignedRelocDynimport verifies that the Go internal linker correctly
|
||||
// handles Mach-O UNSIGNED relocations targeting dynamic import symbols.
|
||||
// The C preamble above contains a global function pointer initialized
|
||||
// to puts, which produces a RELOC_UNSIGNED relocation to the external
|
||||
// symbol _puts. If the linker can't handle this, the test binary
|
||||
// won't link at all.
|
||||
func unsignedRelocDynimport(t *testing.T) {
|
||||
got := C.cgo_call_dynref()
|
||||
if got < 0 {
|
||||
t.Fatal("C function pointer to puts not resolved")
|
||||
}
|
||||
}
|
||||
@@ -164,9 +164,6 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
|
||||
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
|
||||
@@ -179,6 +176,9 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
|
||||
ldr.Errorf(s, "unsupported relocation for PIE: %v", rt)
|
||||
}
|
||||
}
|
||||
if targType == sym.SDYNIMPORT {
|
||||
ldr.Errorf(s, "unexpected reloc for dynamic symbol %s", ldr.SymName(targ))
|
||||
}
|
||||
return true
|
||||
|
||||
case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SUBTRACTOR*2 + 0:
|
||||
@@ -419,7 +419,13 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
|
||||
// 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.
|
||||
ld.MachoAddRebase(s, int64(r.Off()))
|
||||
if targType == sym.SDYNIMPORT {
|
||||
// Dynamic import: the pointer must be bound by
|
||||
// the dynamic linker at load time.
|
||||
ld.MachoAddBind(s, int64(r.Off()), targ)
|
||||
} else {
|
||||
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
|
||||
|
||||
@@ -211,9 +211,6 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
|
||||
|
||||
// Handle relocations found in Mach-O object files.
|
||||
case objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_UNSIGNED*2:
|
||||
if targType == sym.SDYNIMPORT {
|
||||
ldr.Errorf(s, "unexpected reloc for dynamic symbol %s", ldr.SymName(targ))
|
||||
}
|
||||
su := ldr.MakeSymbolUpdater(s)
|
||||
su.SetRelocType(rIdx, objabi.R_ADDR)
|
||||
if target.IsPIE() && target.IsInternal() {
|
||||
@@ -222,6 +219,9 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
|
||||
// relocation. Let the code below handle it.
|
||||
break
|
||||
}
|
||||
if targType == sym.SDYNIMPORT {
|
||||
ldr.Errorf(s, "unexpected reloc for dynamic symbol %s", ldr.SymName(targ))
|
||||
}
|
||||
return true
|
||||
|
||||
case objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_SUBTRACTOR*2:
|
||||
@@ -469,7 +469,13 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
|
||||
// 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.
|
||||
ld.MachoAddRebase(s, int64(r.Off()))
|
||||
if targType == sym.SDYNIMPORT {
|
||||
// Dynamic import: the pointer must be bound by
|
||||
// the dynamic linker at load time.
|
||||
ld.MachoAddBind(s, int64(r.Off()), targ)
|
||||
} else {
|
||||
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
|
||||
|
||||
@@ -3040,7 +3040,7 @@ func AddGotSym(target *Target, ldr *loader.Loader, syms *ArchSyms, s loader.Sym,
|
||||
// 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)
|
||||
MachoAddBind(syms.GOT, int64(ldr.SymGot(s)), s)
|
||||
}
|
||||
} else {
|
||||
ldr.Errorf(s, "addgotsym: unsupported binary format")
|
||||
|
||||
@@ -1338,21 +1338,24 @@ 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
|
||||
// A bind entry tells the dynamic linker the data at sym+off should be bound
|
||||
// to the address of the target symbol, which is a dynamic import.
|
||||
// sym is the symbol containing the pointer (e.g. the GOT or a data symbol),
|
||||
// off is the offset within that symbol, and targ is the dynamic import target.
|
||||
// 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.
|
||||
// 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 machoBindRecord struct {
|
||||
sym loader.Sym
|
||||
off int64
|
||||
targ loader.Sym
|
||||
}
|
||||
|
||||
var machobind []machoBindRecord
|
||||
|
||||
func MachoAddBind(off int64, targ loader.Sym) {
|
||||
machobind = append(machobind, machoBindRecord{off, targ})
|
||||
func MachoAddBind(sym loader.Sym, off int64, targ loader.Sym) {
|
||||
machobind = append(machobind, machoBindRecord{sym, off, targ})
|
||||
}
|
||||
|
||||
// Generate data for the dynamic linker, used in LC_DYLD_INFO_ONLY load command.
|
||||
@@ -1412,12 +1415,10 @@ func machoDyldInfo(ctxt *Link) {
|
||||
// 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
|
||||
seg := ldr.SymSect(r.sym).Seg
|
||||
off := uint64(ldr.SymValue(r.sym)+r.off) - seg.Vaddr
|
||||
bind.AddUint8(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | segId(seg))
|
||||
bind.AddUleb(off)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user