import (
"debug/elf"
+ "fmt"
"internal/testenv"
"os"
"path/filepath"
}
}
}
+
+func TestElfBindNow(t *testing.T) {
+ t.Parallel()
+ testenv.MustHaveGoBuild(t)
+
+ const (
+ prog = `package main; func main() {}`
+ // with default buildmode code compiles in a statically linked binary, hence CGO
+ progC = `package main; import "C"; func main() {}`
+ )
+
+ tests := []struct {
+ name string
+ args []string
+ prog string
+ mustHaveBuildModePIE bool
+ mustHaveCGO bool
+ mustInternalLink bool
+ wantDfBindNow bool
+ wantDf1Now bool
+ wantDf1Pie bool
+ }{
+ {name: "default", prog: prog},
+ {
+ name: "pie-linkmode-internal",
+ args: []string{"-buildmode=pie", "-ldflags", "-linkmode=internal"},
+ prog: prog,
+ mustHaveBuildModePIE: true,
+ mustInternalLink: true,
+ wantDf1Pie: true,
+ },
+ {
+ name: "bindnow-linkmode-internal",
+ args: []string{"-ldflags", "-bindnow -linkmode=internal"},
+ prog: progC,
+ mustHaveCGO: true,
+ mustInternalLink: true,
+ wantDfBindNow: true,
+ wantDf1Now: true,
+ },
+ {
+ name: "bindnow-pie-linkmode-internal",
+ args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=internal"},
+ prog: prog,
+ mustHaveBuildModePIE: true,
+ mustInternalLink: true,
+ wantDfBindNow: true,
+ wantDf1Now: true,
+ wantDf1Pie: true,
+ },
+ {
+ name: "bindnow-pie-linkmode-external",
+ args: []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=external"},
+ prog: prog,
+ mustHaveBuildModePIE: true,
+ mustHaveCGO: true,
+ wantDfBindNow: true,
+ wantDf1Now: true,
+ wantDf1Pie: true,
+ },
+ }
+
+ gotDynFlag := func(flags []uint64, dynFlag uint64) bool {
+ for _, flag := range flags {
+ if gotFlag := dynFlag&flag != 0; gotFlag {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ if test.mustInternalLink {
+ testenv.MustInternalLink(t, test.mustHaveCGO)
+ }
+ if test.mustHaveCGO {
+ testenv.MustHaveCGO(t)
+ }
+ if test.mustHaveBuildModePIE {
+ testenv.MustHaveBuildMode(t, "pie")
+ }
+ if test.mustHaveBuildModePIE && test.mustInternalLink {
+ testenv.MustInternalLinkPIE(t)
+ }
+
+ var (
+ dir = t.TempDir()
+ src = filepath.Join(dir, fmt.Sprintf("elf_%s.go", test.name))
+ binFile = filepath.Join(dir, test.name)
+ )
+
+ if err := os.WriteFile(src, []byte(test.prog), 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...)
+ cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...)
+
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out)
+ }
+
+ fi, err := os.Open(binFile)
+ if err != nil {
+ t.Fatalf("failed to open built file: %v", err)
+ }
+ defer fi.Close()
+
+ elfFile, err := elf.NewFile(fi)
+ if err != nil {
+ t.Skip("The system may not support ELF, skipped.")
+ }
+ defer elfFile.Close()
+
+ flags, err := elfFile.DynValue(elf.DT_FLAGS)
+ if err != nil {
+ t.Fatalf("failed to get DT_FLAGS: %v", err)
+ }
+
+ flags1, err := elfFile.DynValue(elf.DT_FLAGS_1)
+ if err != nil {
+ t.Fatalf("failed to get DT_FLAGS_1: %v", err)
+ }
+
+ gotDfBindNow := gotDynFlag(flags, uint64(elf.DF_BIND_NOW))
+ gotDf1Now := gotDynFlag(flags1, uint64(elf.DF_1_NOW))
+
+ bindNowFlagsMatch := gotDfBindNow == test.wantDfBindNow && gotDf1Now == test.wantDf1Now
+
+ // some external linkers may set one of the two flags but not both.
+ if !test.mustInternalLink {
+ bindNowFlagsMatch = gotDfBindNow == test.wantDfBindNow || gotDf1Now == test.wantDf1Now
+ }
+
+ if !bindNowFlagsMatch {
+ t.Fatalf("Dynamic flags mismatch:\n"+
+ "DT_FLAGS BIND_NOW got: %v, want: %v\n"+
+ "DT_FLAGS_1 DF_1_NOW got: %v, want: %v",
+ gotDfBindNow, test.wantDfBindNow, gotDf1Now, test.wantDf1Now)
+ }
+
+ if gotDf1Pie := gotDynFlag(flags1, uint64(elf.DF_1_PIE)); gotDf1Pie != test.wantDf1Pie {
+ t.Fatalf("DT_FLAGS_1 DF_1_PIE got: %v, want: %v", gotDf1Pie, test.wantDf1Pie)
+ }
+ })
+ }
+}