]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link: handle dynamic import variables on Darwin
authorCherry Mui <cherryyz@google.com>
Thu, 8 Jun 2023 16:19:54 +0000 (12:19 -0400)
committerCherry Mui <cherryyz@google.com>
Thu, 20 Jul 2023 19:39:32 +0000 (19:39 +0000)
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>
src/cmd/link/internal/amd64/asm.go
src/cmd/link/internal/arm64/asm.go
src/cmd/link/link_test.go
src/cmd/link/testdata/dynimportvar/asm/a.go [new file with mode: 0644]
src/cmd/link/testdata/dynimportvar/asm/a_amd64.s [new file with mode: 0644]
src/cmd/link/testdata/dynimportvar/asm/a_arm64.s [new file with mode: 0644]
src/cmd/link/testdata/dynimportvar/main.go [new file with mode: 0644]

index c91e37584c63d869baa57172cbb6bc4188f9d0ac..c4134262c5aa8510b5a053aa060cf595de2845d6 100644 (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
index 312ee27aa6e0037a0528b206d28298fb8f4de9ca..e3fc1c4dd4cb4be0746f8ada6c53b5434363541e 100644 (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
index c37d6e57bc0920a84f7b00827b40f0b196c47cb5..7059af7cad89c623e906909ff9bbfff5cfd90cd3 100644 (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)
+               }
+       }
+}
diff --git a/src/cmd/link/testdata/dynimportvar/asm/a.go b/src/cmd/link/testdata/dynimportvar/asm/a.go
new file mode 100644 (file)
index 0000000..8d92999
--- /dev/null
@@ -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
diff --git a/src/cmd/link/testdata/dynimportvar/asm/a_amd64.s b/src/cmd/link/testdata/dynimportvar/asm/a_amd64.s
new file mode 100644 (file)
index 0000000..93547e3
--- /dev/null
@@ -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
diff --git a/src/cmd/link/testdata/dynimportvar/asm/a_arm64.s b/src/cmd/link/testdata/dynimportvar/asm/a_arm64.s
new file mode 100644 (file)
index 0000000..bd3c9d7
--- /dev/null
@@ -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
diff --git a/src/cmd/link/testdata/dynimportvar/main.go b/src/cmd/link/testdata/dynimportvar/main.go
new file mode 100644 (file)
index 0000000..658d340
--- /dev/null
@@ -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")
+       }
+}