Skip to content

Commit 1f29f39

Browse files
ianlancetaylorgopherbot
authored andcommitted
cmd/link: don't export all symbols for ELF external linking
Since this may add a large number of --export-dynamic-symbol options, use a response file if the command line gets large. Fixes #53579 Change-Id: Ic226bf372bf1e177a3dae886d1c48f4ce3569c0e Reviewed-on: https://go-review.googlesource.com/c/go/+/414654 Reviewed-by: Michael Pratt <[email protected]> Reviewed-by: Joedian Reid <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Reviewed-by: Than McIntosh <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 2bf0f54 commit 1f29f39

File tree

3 files changed

+129
-17
lines changed

3 files changed

+129
-17
lines changed

src/cmd/link/internal/ld/lib.go

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,7 +1605,13 @@ func (ctxt *Link) hostlink() {
16051605

16061606
// Force global symbols to be exported for dlopen, etc.
16071607
if ctxt.IsELF {
1608-
argv = append(argv, "-rdynamic")
1608+
if ctxt.DynlinkingGo() || ctxt.BuildMode == BuildModeCShared || !linkerFlagSupported(ctxt.Arch, argv[0], altLinker, "-Wl,--export-dynamic-symbol=main") {
1609+
argv = append(argv, "-rdynamic")
1610+
} else {
1611+
ctxt.loader.ForAllCgoExportDynamic(func(s loader.Sym) {
1612+
argv = append(argv, "-Wl,--export-dynamic-symbol="+ctxt.loader.SymExtname(s))
1613+
})
1614+
}
16091615
}
16101616
if ctxt.HeadType == objabi.Haix {
16111617
fileName := xcoffCreateExportFile(ctxt)
@@ -1748,7 +1754,7 @@ func (ctxt *Link) hostlink() {
17481754
// case used has specified "-fuse-ld=...".
17491755
extld := ctxt.extld()
17501756
name, args := extld[0], extld[1:]
1751-
args = append(args, flagExtldflags...)
1757+
args = append(args, trimLinkerArgv(flagExtldflags)...)
17521758
args = append(args, "-Wl,--version")
17531759
cmd := exec.Command(name, args...)
17541760
usingLLD := false
@@ -1775,6 +1781,8 @@ func (ctxt *Link) hostlink() {
17751781
argv = append(argv, peimporteddlls()...)
17761782
}
17771783

1784+
argv = ctxt.passLongArgsInResponseFile(argv, altLinker)
1785+
17781786
if ctxt.Debugvlog != 0 {
17791787
ctxt.Logf("host link:")
17801788
for _, v := range argv {
@@ -1885,6 +1893,47 @@ func (ctxt *Link) hostlink() {
18851893
}
18861894
}
18871895

1896+
// passLongArgsInResponseFile writes the arguments into a file if they
1897+
// are very long.
1898+
func (ctxt *Link) passLongArgsInResponseFile(argv []string, altLinker string) []string {
1899+
c := 0
1900+
for _, arg := range argv {
1901+
c += len(arg)
1902+
}
1903+
1904+
if c < sys.ExecArgLengthLimit {
1905+
return argv
1906+
}
1907+
1908+
// Only use response files if they are supported.
1909+
response := filepath.Join(*flagTmpdir, "response")
1910+
if err := os.WriteFile(response, nil, 0644); err != nil {
1911+
log.Fatalf("failed while testing response file: %v", err)
1912+
}
1913+
if !linkerFlagSupported(ctxt.Arch, argv[0], altLinker, "@"+response) {
1914+
if ctxt.Debugvlog != 0 {
1915+
ctxt.Logf("not using response file because linker does not support one")
1916+
}
1917+
return argv
1918+
}
1919+
1920+
var buf bytes.Buffer
1921+
for _, arg := range argv[1:] {
1922+
// The external linker response file supports quoted strings.
1923+
fmt.Fprintf(&buf, "%q\n", arg)
1924+
}
1925+
if err := os.WriteFile(response, buf.Bytes(), 0644); err != nil {
1926+
log.Fatalf("failed while writing response file: %v", err)
1927+
}
1928+
if ctxt.Debugvlog != 0 {
1929+
ctxt.Logf("response file %s contents:\n%s", response, buf.Bytes())
1930+
}
1931+
return []string{
1932+
argv[0],
1933+
"@" + response,
1934+
}
1935+
}
1936+
18881937
var createTrivialCOnce sync.Once
18891938

18901939
func linkerFlagSupported(arch *sys.Arch, linker, altLinker, flag string) bool {
@@ -1895,6 +1944,28 @@ func linkerFlagSupported(arch *sys.Arch, linker, altLinker, flag string) bool {
18951944
}
18961945
})
18971946

1947+
flags := hostlinkArchArgs(arch)
1948+
1949+
moreFlags := trimLinkerArgv(append(flagExtldflags, ldflag...))
1950+
flags = append(flags, moreFlags...)
1951+
1952+
if altLinker != "" {
1953+
flags = append(flags, "-fuse-ld="+altLinker)
1954+
}
1955+
flags = append(flags, flag, "trivial.c")
1956+
1957+
cmd := exec.Command(linker, flags...)
1958+
cmd.Dir = *flagTmpdir
1959+
cmd.Env = append([]string{"LC_ALL=C"}, os.Environ()...)
1960+
out, err := cmd.CombinedOutput()
1961+
// GCC says "unrecognized command line option ‘-no-pie’"
1962+
// clang says "unknown argument: '-no-pie'"
1963+
return err == nil && !bytes.Contains(out, []byte("unrecognized")) && !bytes.Contains(out, []byte("unknown"))
1964+
}
1965+
1966+
// trimLinkerArgv returns a new copy of argv that does not include flags
1967+
// that are not relevant for testing whether some linker option works.
1968+
func trimLinkerArgv(argv []string) []string {
18981969
flagsWithNextArgSkip := []string{
18991970
"-F",
19001971
"-l",
@@ -1921,10 +1992,10 @@ func linkerFlagSupported(arch *sys.Arch, linker, altLinker, flag string) bool {
19211992
"-target",
19221993
}
19231994

1924-
flags := hostlinkArchArgs(arch)
1995+
var flags []string
19251996
keep := false
19261997
skip := false
1927-
for _, f := range append(flagExtldflags, ldflag...) {
1998+
for _, f := range argv {
19281999
if keep {
19292000
flags = append(flags, f)
19302001
keep = false
@@ -1945,19 +2016,7 @@ func linkerFlagSupported(arch *sys.Arch, linker, altLinker, flag string) bool {
19452016
}
19462017
}
19472018
}
1948-
1949-
if altLinker != "" {
1950-
flags = append(flags, "-fuse-ld="+altLinker)
1951-
}
1952-
flags = append(flags, flag, "trivial.c")
1953-
1954-
cmd := exec.Command(linker, flags...)
1955-
cmd.Dir = *flagTmpdir
1956-
cmd.Env = append([]string{"LC_ALL=C"}, os.Environ()...)
1957-
out, err := cmd.CombinedOutput()
1958-
// GCC says "unrecognized command line option ‘-no-pie’"
1959-
// clang says "unknown argument: '-no-pie'"
1960-
return err == nil && !bytes.Contains(out, []byte("unrecognized")) && !bytes.Contains(out, []byte("unknown"))
2019+
return flags
19612020
}
19622021

19632022
// hostlinkArchArgs returns arguments to pass to the external linker

src/cmd/link/internal/loader/loader.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,14 @@ func (l *Loader) SetAttrCgoExportDynamic(i Sym, v bool) {
10411041
}
10421042
}
10431043

1044+
// ForAllAttrCgoExportDynamic calls f for every symbol that has been
1045+
// marked with the "cgo_export_dynamic" compiler directive.
1046+
func (l *Loader) ForAllCgoExportDynamic(f func(Sym)) {
1047+
for s := range l.attrCgoExportDynamic {
1048+
f(s)
1049+
}
1050+
}
1051+
10441052
// AttrCgoExportStatic returns true for a symbol that has been
10451053
// specially marked via the "cgo_export_static" directive
10461054
// written by cgo.

src/cmd/link/link_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"runtime"
1818
"strings"
1919
"testing"
20+
21+
"cmd/internal/sys"
2022
)
2123

2224
var AuthorPaidByTheColumnInch struct {
@@ -1150,3 +1152,46 @@ func TestUnlinkableObj(t *testing.T) {
11501152
t.Errorf("link failed: %v. output:\n%s", err, out)
11511153
}
11521154
}
1155+
1156+
// TestResponseFile tests that creating a response file to pass to the
1157+
// external linker works correctly.
1158+
func TestResponseFile(t *testing.T) {
1159+
t.Parallel()
1160+
1161+
testenv.MustHaveGoBuild(t)
1162+
1163+
// This test requires -linkmode=external. Currently all
1164+
// systems that support cgo support -linkmode=external.
1165+
testenv.MustHaveCGO(t)
1166+
1167+
tmpdir := t.TempDir()
1168+
1169+
src := filepath.Join(tmpdir, "x.go")
1170+
if err := os.WriteFile(src, []byte(`package main; import "C"; func main() {}`), 0666); err != nil {
1171+
t.Fatal(err)
1172+
}
1173+
1174+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "output", "x.go")
1175+
cmd.Dir = tmpdir
1176+
1177+
// Add enough arguments to push cmd/link into creating a response file.
1178+
var sb strings.Builder
1179+
sb.WriteString(`'-ldflags=all="-extldflags=`)
1180+
for i := 0; i < sys.ExecArgLengthLimit/len("-g"); i++ {
1181+
if i > 0 {
1182+
sb.WriteString(" ")
1183+
}
1184+
sb.WriteString("-g")
1185+
}
1186+
sb.WriteString(`"'`)
1187+
cmd = testenv.CleanCmdEnv(cmd)
1188+
cmd.Env = append(cmd.Env, "GOFLAGS="+sb.String())
1189+
1190+
out, err := cmd.CombinedOutput()
1191+
if len(out) > 0 {
1192+
t.Logf("%s", out)
1193+
}
1194+
if err != nil {
1195+
t.Error(err)
1196+
}
1197+
}

0 commit comments

Comments
 (0)