cmd/link: add new linker testpoint for "ld -r" host object

This adds a new test that builds a small Go program with linked
against a *.syso file that is the result of an "ld -r" link. The
sysobj in question has multiple static symbols in the same section
with the same name, which triggered a bug in the loader in -newobj
mode.

Updates #35779.

Change-Id: Ibe1a75662dc1d49c4347279e55646ee65a81508e
Reviewed-on: https://go-review.googlesource.com/c/go/+/208478
Reviewed-by: Cherry Zhang <cherryyz@google.com>
diff --git a/src/cmd/link/elf_test.go b/src/cmd/link/elf_test.go
index 3df9869..84f373a 100644
--- a/src/cmd/link/elf_test.go
+++ b/src/cmd/link/elf_test.go
@@ -7,6 +7,7 @@
 package main
 
 import (
+	"fmt"
 	"internal/testenv"
 	"io/ioutil"
 	"os"
@@ -16,6 +17,27 @@
 	"testing"
 )
 
+func getCCAndCCFLAGS(t *testing.T, env []string) (string, []string) {
+	goTool := testenv.GoToolPath(t)
+	cmd := exec.Command(goTool, "env", "CC")
+	cmd.Env = env
+	ccb, err := cmd.Output()
+	if err != nil {
+		t.Fatal(err)
+	}
+	cc := strings.TrimSpace(string(ccb))
+
+	cmd = exec.Command(goTool, "env", "GOGCCFLAGS")
+	cmd.Env = env
+	cflagsb, err := cmd.Output()
+	if err != nil {
+		t.Fatal(err)
+	}
+	cflags := strings.Fields(string(cflagsb))
+
+	return cc, cflags
+}
+
 var asmSource = `
 	.section .text1,"ax"
 s1:
@@ -61,21 +83,7 @@
 	}
 
 	goTool := testenv.GoToolPath(t)
-	cmd := exec.Command(goTool, "env", "CC")
-	cmd.Env = env
-	ccb, err := cmd.Output()
-	if err != nil {
-		t.Fatal(err)
-	}
-	cc := strings.TrimSpace(string(ccb))
-
-	cmd = exec.Command(goTool, "env", "GOGCCFLAGS")
-	cmd.Env = env
-	cflagsb, err := cmd.Output()
-	if err != nil {
-		t.Fatal(err)
-	}
-	cflags := strings.Fields(string(cflagsb))
+	cc, cflags := getCCAndCCFLAGS(t, env)
 
 	asmObj := filepath.Join(dir, "x.o")
 	t.Logf("%s %v -c -o %s %s", cc, cflags, asmObj, asmFile)
@@ -102,7 +110,91 @@
 		t.Fatal(err)
 	}
 
-	cmd = exec.Command(goTool, "build")
+	cmd := exec.Command(goTool, "build")
+	cmd.Dir = dir
+	cmd.Env = env
+	t.Logf("%s build", goTool)
+	if out, err := cmd.CombinedOutput(); err != nil {
+		t.Logf("%s", out)
+		t.Fatal(err)
+	}
+}
+
+var cSources35779 = []string{`
+static int blah() { return 42; }
+int Cfunc1() { return blah(); }
+`, `
+static int blah() { return 42; }
+int Cfunc2() { return blah(); }
+`,
+}
+
+// TestMinusRSymsWithSameName tests a corner case in the new
+// loader. Prior to the fix this failed with the error 'loadelf:
+// $WORK/b001/_pkg_.a(ldr.syso): duplicate symbol reference: blah in
+// both main(.text) and main(.text)'
+func TestMinusRSymsWithSameName(t *testing.T) {
+	testenv.MustHaveGoBuild(t)
+	testenv.MustHaveCGO(t)
+	t.Parallel()
+
+	dir, err := ioutil.TempDir("", "go-link-TestMinusRSymsWithSameName")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	gopath := filepath.Join(dir, "GOPATH")
+	env := append(os.Environ(), "GOPATH="+gopath)
+
+	if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil {
+		t.Fatal(err)
+	}
+
+	goTool := testenv.GoToolPath(t)
+	cc, cflags := getCCAndCCFLAGS(t, env)
+
+	objs := []string{}
+	csrcs := []string{}
+	for i, content := range cSources35779 {
+		csrcFile := filepath.Join(dir, fmt.Sprintf("x%d.c", i))
+		csrcs = append(csrcs, csrcFile)
+		if err := ioutil.WriteFile(csrcFile, []byte(content), 0444); err != nil {
+			t.Fatal(err)
+		}
+
+		obj := filepath.Join(dir, fmt.Sprintf("x%d.o", i))
+		objs = append(objs, obj)
+		t.Logf("%s %v -c -o %s %s", cc, cflags, obj, csrcFile)
+		if out, err := exec.Command(cc, append(cflags, "-c", "-o", obj, csrcFile)...).CombinedOutput(); err != nil {
+			t.Logf("%s", out)
+			t.Fatal(err)
+		}
+	}
+
+	sysoObj := filepath.Join(dir, "ldr.syso")
+	t.Logf("%s %v -nostdlib -r -o %s %v", cc, cflags, sysoObj, objs)
+	if out, err := exec.Command(cc, append(cflags, "-nostdlib", "-r", "-o", sysoObj, objs[0], objs[1])...).CombinedOutput(); err != nil {
+		t.Logf("%s", out)
+		t.Fatal(err)
+	}
+
+	cruft := [][]string{objs, csrcs}
+	for _, sl := range cruft {
+		for _, s := range sl {
+			if err := os.Remove(s); err != nil {
+				t.Fatal(err)
+			}
+		}
+	}
+
+	goFile := filepath.Join(dir, "main.go")
+	if err := ioutil.WriteFile(goFile, []byte(goSource), 0444); err != nil {
+		t.Fatal(err)
+	}
+
+	t.Logf("%s build", goTool)
+	cmd := exec.Command(goTool, "build")
 	cmd.Dir = dir
 	cmd.Env = env
 	t.Logf("%s build", goTool)