Skip to content

Commit ac81156

Browse files
committed
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 <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Than McIntosh <[email protected]>
1 parent 3ffc8a2 commit ac81156

File tree

7 files changed

+156
-0
lines changed

7 files changed

+156
-0
lines changed

src/cmd/link/internal/amd64/asm.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,29 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
251251
// nothing to do, the relocation will be laid out in reloc
252252
return true
253253
}
254+
if r.Type() == objabi.R_PCREL && ldr.SymType(s) == sym.STEXT && target.IsDarwin() {
255+
// Loading the address of a dynamic symbol. Rewrite to use GOT.
256+
// turn LEAQ symbol address to MOVQ of GOT entry
257+
if r.Add() != 0 {
258+
ldr.Errorf(s, "unexpected nonzero addend for dynamic symbol %s", ldr.SymName(targ))
259+
return false
260+
}
261+
su := ldr.MakeSymbolUpdater(s)
262+
if r.Off() >= 2 && su.Data()[r.Off()-2] == 0x8d {
263+
su.MakeWritable()
264+
su.Data()[r.Off()-2] = 0x8b
265+
if target.IsInternal() {
266+
ld.AddGotSym(target, ldr, syms, targ, 0)
267+
su.SetRelocSym(rIdx, syms.GOT)
268+
su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ)))
269+
} else {
270+
su.SetRelocType(rIdx, objabi.R_GOTPCREL)
271+
}
272+
return true
273+
}
274+
ldr.Errorf(s, "unexpected R_PCREL reloc for dynamic symbol %s: not preceded by LEAQ instruction", ldr.SymName(targ))
275+
return false
276+
}
254277
if target.IsExternal() {
255278
// External linker will do this relocation.
256279
return true

src/cmd/link/internal/arm64/asm.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,40 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
306306
su.SetRelocAdd(rIdx, int64(ldr.SymPlt(targ)))
307307
return true
308308

309+
case objabi.R_ADDRARM64:
310+
if targType == sym.SDYNIMPORT && ldr.SymType(s) == sym.STEXT && target.IsDarwin() {
311+
// Loading the address of a dynamic symbol. Rewrite to use GOT.
312+
// turn MOVD $sym (adrp+add) into MOVD sym@GOT (adrp+ldr)
313+
if r.Add() != 0 {
314+
ldr.Errorf(s, "unexpected nonzero addend for dynamic symbol %s", ldr.SymName(targ))
315+
return false
316+
}
317+
su := ldr.MakeSymbolUpdater(s)
318+
data := ldr.Data(s)
319+
off := r.Off()
320+
if int(off+8) > len(data) {
321+
ldr.Errorf(s, "unexpected R_ADDRARM64 reloc for dynamic symbol %s", ldr.SymName(targ))
322+
return false
323+
}
324+
o := target.Arch.ByteOrder.Uint32(data[off+4:])
325+
if o>>24 == 0x91 { // add
326+
// rewrite to ldr
327+
o = (0xf9 << 24) | 1<<22 | (o & (1<<22 - 1))
328+
su.MakeWritable()
329+
su.SetUint32(target.Arch, int64(off+4), o)
330+
if target.IsInternal() {
331+
ld.AddGotSym(target, ldr, syms, targ, 0)
332+
su.SetRelocSym(rIdx, syms.GOT)
333+
su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ)))
334+
su.SetRelocType(rIdx, objabi.R_ARM64_PCREL_LDST64)
335+
} else {
336+
su.SetRelocType(rIdx, objabi.R_ARM64_GOTPCREL)
337+
}
338+
return true
339+
}
340+
ldr.Errorf(s, "unexpected R_ADDRARM64 reloc for dynamic symbol %s", ldr.SymName(targ))
341+
}
342+
309343
case objabi.R_ADDR:
310344
if ldr.SymType(s) == sym.STEXT && target.IsElf() {
311345
// The code is asking for the address of an external

src/cmd/link/link_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,3 +1209,33 @@ func TestResponseFile(t *testing.T) {
12091209
t.Error(err)
12101210
}
12111211
}
1212+
1213+
func TestDynimportVar(t *testing.T) {
1214+
// Test that we can access dynamically imported variables.
1215+
// Currently darwin only.
1216+
if runtime.GOOS != "darwin" {
1217+
t.Skip("skip on non-darwin platform")
1218+
}
1219+
1220+
testenv.MustHaveGoBuild(t)
1221+
testenv.MustHaveCGO(t)
1222+
1223+
t.Parallel()
1224+
1225+
tmpdir := t.TempDir()
1226+
exe := filepath.Join(tmpdir, "a.exe")
1227+
src := filepath.Join("testdata", "dynimportvar", "main.go")
1228+
1229+
for _, mode := range []string{"internal", "external"} {
1230+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode="+mode, "-o", exe, src)
1231+
out, err := cmd.CombinedOutput()
1232+
if err != nil {
1233+
t.Fatalf("build (linkmode=%s) failed: %v\n%s", mode, err, out)
1234+
}
1235+
cmd = testenv.Command(t, exe)
1236+
out, err = cmd.CombinedOutput()
1237+
if err != nil {
1238+
t.Errorf("executable failed to run (%s): %v\n%s", mode, err, out)
1239+
}
1240+
}
1241+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// This is a separate package because we cannot have Go
6+
// assembly code and cgo code in the same package.
7+
8+
//go:build darwin
9+
10+
package asm
11+
12+
//go:cgo_import_dynamic libc_mach_task_self_ mach_task_self_ "/usr/lib/libSystem.B.dylib"
13+
14+
// load mach_task_self_ from assembly code
15+
func Mach_task_self() uint32
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build darwin
6+
7+
TEXT ·Mach_task_self(SB),0,$0-4
8+
MOVQ $libc_mach_task_self_(SB), AX
9+
MOVQ (AX), AX
10+
MOVL AX, ret+0(FP)
11+
RET
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build darwin
6+
7+
TEXT ·Mach_task_self(SB),0,$0-4
8+
MOVD $libc_mach_task_self_(SB), R0
9+
MOVD (R0), R0
10+
MOVW R0, ret+0(FP)
11+
RET
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Test that we can access dynamically imported variables.
6+
// We ues mach_task_self_ from darwin's system library.
7+
// Check that loading the variable from C and Go gets the
8+
// same result.
9+
10+
//go:build darwin
11+
12+
package main
13+
14+
/*
15+
#include <mach/mach_init.h>
16+
17+
unsigned int Mach_task_self(void) {
18+
return mach_task_self();
19+
}
20+
*/
21+
import "C"
22+
23+
import "cmd/link/testdata/dynimportvar/asm"
24+
25+
func main() {
26+
c := uint32(C.Mach_task_self())
27+
a := asm.Mach_task_self()
28+
if a != c {
29+
println("got", a, "want", c)
30+
panic("FAIL")
31+
}
32+
}

0 commit comments

Comments
 (0)