1
0
mirror of https://github.com/golang/go synced 2024-11-23 04:30:03 -07:00

cmd/link: handle dynamic import variables on Darwin

Currently, on darwin, we only support cgo_dynamic_import for
functions, but not variables, as we don't need it before.
mach_task_self_ is a variable defined in the system library, which
can be used to e.g. access the process's memory mappings via the
mach API. The C header defines a macro mach_task_self(), which
refers to the variable. To use mach_task_self_ (in pure-Go
programs) we need to access it in Go.

This CL handles cgo_dynamic_import for variables in the linker,
loading its address via the GOT. (Currently only on Darwin, as
we only need it there.)

For #50891.

Change-Id: Idf64fa88ba2f2381443a1ed0b42b14b581843493
Reviewed-on: https://go-review.googlesource.com/c/go/+/501855
Run-TryBot: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
Cherry Mui 2023-06-08 12:19:54 -04:00
parent 3ffc8a2556
commit ac81156bce
7 changed files with 156 additions and 0 deletions

View File

@ -251,6 +251,29 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
// nothing to do, the relocation will be laid out in reloc
return true
}
if r.Type() == objabi.R_PCREL && ldr.SymType(s) == sym.STEXT && target.IsDarwin() {
// Loading the address of a dynamic symbol. Rewrite to use GOT.
// turn LEAQ symbol address to MOVQ of GOT entry
if r.Add() != 0 {
ldr.Errorf(s, "unexpected nonzero addend for dynamic symbol %s", ldr.SymName(targ))
return false
}
su := ldr.MakeSymbolUpdater(s)
if r.Off() >= 2 && su.Data()[r.Off()-2] == 0x8d {
su.MakeWritable()
su.Data()[r.Off()-2] = 0x8b
if target.IsInternal() {
ld.AddGotSym(target, ldr, syms, targ, 0)
su.SetRelocSym(rIdx, syms.GOT)
su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ)))
} else {
su.SetRelocType(rIdx, objabi.R_GOTPCREL)
}
return true
}
ldr.Errorf(s, "unexpected R_PCREL reloc for dynamic symbol %s: not preceded by LEAQ instruction", ldr.SymName(targ))
return false
}
if target.IsExternal() {
// External linker will do this relocation.
return true

View File

@ -306,6 +306,40 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
su.SetRelocAdd(rIdx, int64(ldr.SymPlt(targ)))
return true
case objabi.R_ADDRARM64:
if targType == sym.SDYNIMPORT && ldr.SymType(s) == sym.STEXT && target.IsDarwin() {
// Loading the address of a dynamic symbol. Rewrite to use GOT.
// turn MOVD $sym (adrp+add) into MOVD sym@GOT (adrp+ldr)
if r.Add() != 0 {
ldr.Errorf(s, "unexpected nonzero addend for dynamic symbol %s", ldr.SymName(targ))
return false
}
su := ldr.MakeSymbolUpdater(s)
data := ldr.Data(s)
off := r.Off()
if int(off+8) > len(data) {
ldr.Errorf(s, "unexpected R_ADDRARM64 reloc for dynamic symbol %s", ldr.SymName(targ))
return false
}
o := target.Arch.ByteOrder.Uint32(data[off+4:])
if o>>24 == 0x91 { // add
// rewrite to ldr
o = (0xf9 << 24) | 1<<22 | (o & (1<<22 - 1))
su.MakeWritable()
su.SetUint32(target.Arch, int64(off+4), o)
if target.IsInternal() {
ld.AddGotSym(target, ldr, syms, targ, 0)
su.SetRelocSym(rIdx, syms.GOT)
su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ)))
su.SetRelocType(rIdx, objabi.R_ARM64_PCREL_LDST64)
} else {
su.SetRelocType(rIdx, objabi.R_ARM64_GOTPCREL)
}
return true
}
ldr.Errorf(s, "unexpected R_ADDRARM64 reloc for dynamic symbol %s", ldr.SymName(targ))
}
case objabi.R_ADDR:
if ldr.SymType(s) == sym.STEXT && target.IsElf() {
// The code is asking for the address of an external

View File

@ -1209,3 +1209,33 @@ func TestResponseFile(t *testing.T) {
t.Error(err)
}
}
func TestDynimportVar(t *testing.T) {
// Test that we can access dynamically imported variables.
// Currently darwin only.
if runtime.GOOS != "darwin" {
t.Skip("skip on non-darwin platform")
}
testenv.MustHaveGoBuild(t)
testenv.MustHaveCGO(t)
t.Parallel()
tmpdir := t.TempDir()
exe := filepath.Join(tmpdir, "a.exe")
src := filepath.Join("testdata", "dynimportvar", "main.go")
for _, mode := range []string{"internal", "external"} {
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode="+mode, "-o", exe, src)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("build (linkmode=%s) failed: %v\n%s", mode, err, out)
}
cmd = testenv.Command(t, exe)
out, err = cmd.CombinedOutput()
if err != nil {
t.Errorf("executable failed to run (%s): %v\n%s", mode, err, out)
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright 2023 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.
// This is a separate package because we cannot have Go
// assembly code and cgo code in the same package.
//go:build darwin
package asm
//go:cgo_import_dynamic libc_mach_task_self_ mach_task_self_ "/usr/lib/libSystem.B.dylib"
// load mach_task_self_ from assembly code
func Mach_task_self() uint32

View File

@ -0,0 +1,11 @@
// Copyright 2023 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
TEXT ·Mach_task_self(SB),0,$0-4
MOVQ $libc_mach_task_self_(SB), AX
MOVQ (AX), AX
MOVL AX, ret+0(FP)
RET

View File

@ -0,0 +1,11 @@
// Copyright 2023 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
TEXT ·Mach_task_self(SB),0,$0-4
MOVD $libc_mach_task_self_(SB), R0
MOVD (R0), R0
MOVW R0, ret+0(FP)
RET

View File

@ -0,0 +1,32 @@
// Copyright 2023 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.
// Test that we can access dynamically imported variables.
// We ues mach_task_self_ from darwin's system library.
// Check that loading the variable from C and Go gets the
// same result.
//go:build darwin
package main
/*
#include <mach/mach_init.h>
unsigned int Mach_task_self(void) {
return mach_task_self();
}
*/
import "C"
import "cmd/link/testdata/dynimportvar/asm"
func main() {
c := uint32(C.Mach_task_self())
a := asm.Mach_task_self()
if a != c {
println("got", a, "want", c)
panic("FAIL")
}
}