From: qmuntal Date: Mon, 22 Sep 2025 10:02:13 +0000 (+0200) Subject: cmd/cgo: don't hardcode section name in TestNumberOfExportedFunctions X-Git-Tag: go1.26rc1~803 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=a13d085a5b;p=gostls13.git cmd/cgo: don't hardcode section name in TestNumberOfExportedFunctions TestNumberOfExportedFunctions checks the number of exported functions announced in the PE export table, getting it from the .edata section. If the section is not found, the test is skipped. However, the PE spec doesn't mandate that the export table be in a section named .edata, making this test prone to being skipped unnecessarily. While here, remove a check in cmd/go.testBuildmodePIE that was testing the same thing in order to verify that the binary had a relocation table . Not only the test is duplicated, but also it in unnecessary because it already testing that the PE characteristics doesn't contain the IMAGE_FILE_RELOCS_STRIPPED flag. Closes #46719 Cq-Include-Trybots: luci.golang.try:gotip-windows-arm64 Change-Id: I28d1e261b38388868dd3c19ef6ddddad7bf105ef Reviewed-on: https://go-review.googlesource.com/c/go/+/705755 Reviewed-by: Junyang Shao Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI --- diff --git a/src/cmd/cgo/internal/testcshared/cshared_test.go b/src/cmd/cgo/internal/testcshared/cshared_test.go index 2c4d33f599..f1c30f8f9a 100644 --- a/src/cmd/cgo/internal/testcshared/cshared_test.go +++ b/src/cmd/cgo/internal/testcshared/cshared_test.go @@ -375,26 +375,7 @@ func TestExportedSymbols(t *testing.T) { } } -func checkNumberOfExportedFunctionsWindows(t *testing.T, exportAllSymbols bool) { - const prog = ` -package main - -import "C" - -//export GoFunc -func GoFunc() { - println(42) -} - -//export GoFunc2 -func GoFunc2() { - println(24) -} - -func main() { -} -` - +func checkNumberOfExportedFunctionsWindows(t *testing.T, prog string, exportedFunctions int, wantAll bool) { tmpdir := t.TempDir() srcfile := filepath.Join(tmpdir, "test.go") @@ -403,7 +384,7 @@ func main() { t.Fatal(err) } argv := []string{"build", "-buildmode=c-shared"} - if exportAllSymbols { + if wantAll { argv = append(argv, "-ldflags", "-extldflags=-Wl,--export-all-symbols") } argv = append(argv, "-o", objfile, srcfile) @@ -417,10 +398,36 @@ func main() { t.Fatalf("pe.Open failed: %v", err) } defer f.Close() - section := f.Section(".edata") + + _, pe64 := f.OptionalHeader.(*pe.OptionalHeader64) + // grab the export data directory entry + var idd pe.DataDirectory + if pe64 { + idd = f.OptionalHeader.(*pe.OptionalHeader64).DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT] + } else { + idd = f.OptionalHeader.(*pe.OptionalHeader32).DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT] + } + + // figure out which section contains the import directory table + var section *pe.Section + for _, s := range f.Sections { + if s.Offset == 0 { + continue + } + if s.VirtualAddress <= idd.VirtualAddress && idd.VirtualAddress-s.VirtualAddress < s.VirtualSize { + section = s + break + } + } if section == nil { - t.Skip(".edata section is not present") + t.Fatal("no section contains export directory") } + d, err := section.Data() + if err != nil { + t.Fatal(err) + } + // seek to the virtual address specified in the export data directory + d = d[idd.VirtualAddress-section.VirtualAddress:] // TODO: deduplicate this struct from cmd/link/internal/ld/pe.go type IMAGE_EXPORT_DIRECTORY struct { @@ -432,26 +439,22 @@ func main() { _ [3]uint32 } var e IMAGE_EXPORT_DIRECTORY - if err := binary.Read(section.Open(), binary.LittleEndian, &e); err != nil { + if err := binary.Read(bytes.NewReader(d), binary.LittleEndian, &e); err != nil { t.Fatalf("binary.Read failed: %v", err) } - // Only the two exported functions and _cgo_dummy_export should be exported - expectedNumber := uint32(3) - - if exportAllSymbols { - if e.NumberOfFunctions <= expectedNumber { - t.Fatalf("missing exported functions: %v", e.NumberOfFunctions) - } - if e.NumberOfNames <= expectedNumber { - t.Fatalf("missing exported names: %v", e.NumberOfNames) + // Only the two exported functions and _cgo_dummy_export should be exported. + // NumberOfNames is the number of functions exported with a unique name. + // NumberOfFunctions can be higher than that because it also counts + // functions exported only by ordinal, a unique number asigned by the linker, + // and linkers might add an unknown number of their own ordinal-only functions. + if wantAll { + if e.NumberOfNames <= uint32(exportedFunctions) { + t.Errorf("got %d exported names, want > %d", e.NumberOfNames, exportedFunctions) } } else { - if e.NumberOfFunctions != expectedNumber { - t.Fatalf("got %d exported functions; want %d", e.NumberOfFunctions, expectedNumber) - } - if e.NumberOfNames != expectedNumber { - t.Fatalf("got %d exported names; want %d", e.NumberOfNames, expectedNumber) + if e.NumberOfNames > uint32(exportedFunctions) { + t.Errorf("got %d exported names, want <= %d", e.NumberOfNames, exportedFunctions) } } } @@ -467,11 +470,42 @@ func TestNumberOfExportedFunctions(t *testing.T) { t.Parallel() - t.Run("OnlyExported", func(t *testing.T) { - checkNumberOfExportedFunctionsWindows(t, false) + const prog0 = ` +package main + +import "C" + +func main() { +} +` + + const prog2 = ` +package main + +import "C" + +//export GoFunc +func GoFunc() { + println(42) +} + +//export GoFunc2 +func GoFunc2() { + println(24) +} + +func main() { +} +` + // All programs export _cgo_dummy_export, so add 1 to the expected counts. + t.Run("OnlyExported/0", func(t *testing.T) { + checkNumberOfExportedFunctionsWindows(t, prog0, 0+1, false) + }) + t.Run("OnlyExported/2", func(t *testing.T) { + checkNumberOfExportedFunctionsWindows(t, prog2, 2+1, false) }) t.Run("All", func(t *testing.T) { - checkNumberOfExportedFunctionsWindows(t, true) + checkNumberOfExportedFunctionsWindows(t, prog2, 2+1, true) }) } diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 3e691abe41..e4ee9bd1e8 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -9,7 +9,6 @@ import ( "debug/elf" "debug/macho" "debug/pe" - "encoding/binary" "flag" "fmt" "go/format" @@ -2131,38 +2130,6 @@ func testBuildmodePIE(t *testing.T, useCgo, setBuildmodeToPIE bool) { if (dc & pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) == 0 { t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag is not set") } - if useCgo { - // Test that only one symbol is exported (#40795). - // PIE binaries don´t require .edata section but unfortunately - // binutils doesn´t generate a .reloc section unless there is - // at least one symbol exported. - // See https://sourceware.org/bugzilla/show_bug.cgi?id=19011 - section := f.Section(".edata") - if section == nil { - t.Skip(".edata section is not present") - } - // TODO: deduplicate this struct from cmd/link/internal/ld/pe.go - type IMAGE_EXPORT_DIRECTORY struct { - _ [2]uint32 - _ [2]uint16 - _ [2]uint32 - NumberOfFunctions uint32 - NumberOfNames uint32 - _ [3]uint32 - } - var e IMAGE_EXPORT_DIRECTORY - if err := binary.Read(section.Open(), binary.LittleEndian, &e); err != nil { - t.Fatalf("binary.Read failed: %v", err) - } - - // Only _cgo_dummy_export should be exported - if e.NumberOfFunctions != 1 { - t.Fatalf("got %d exported functions; want 1", e.NumberOfFunctions) - } - if e.NumberOfNames != 1 { - t.Fatalf("got %d exported names; want 1", e.NumberOfNames) - } - } default: // testBuildmodePIE opens object files, so it needs to understand the object // file format.