| // Copyright 2025 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 |
| |
| package ld |
| |
| import ( |
| "debug/macho" |
| "fmt" |
| "internal/testenv" |
| "os" |
| "path/filepath" |
| "testing" |
| ) |
| |
| func TestMachoSectionsReadOnly(t *testing.T) { |
| t.Parallel() |
| testenv.MustHaveGoBuild(t) |
| |
| const ( |
| prog = `package main; func main() {}` |
| progC = `package main; import "C"; func main() {}` |
| ) |
| |
| tests := []struct { |
| name string |
| args []string |
| prog string |
| wantSecsRO []string |
| mustHaveCGO bool |
| mustInternalLink bool |
| }{ |
| { |
| name: "linkmode-internal", |
| args: []string{"-ldflags", "-linkmode=internal"}, |
| prog: prog, |
| mustInternalLink: true, |
| wantSecsRO: []string{"__got", "__rodata", "__itablink", "__typelink"}, |
| }, |
| { |
| name: "linkmode-external", |
| args: []string{"-ldflags", "-linkmode=external"}, |
| prog: prog, |
| mustHaveCGO: true, |
| wantSecsRO: []string{"__got", "__rodata", "__itablink", "__typelink"}, |
| }, |
| { |
| name: "cgo-linkmode-internal", |
| args: []string{"-ldflags", "-linkmode=external"}, |
| prog: progC, |
| mustHaveCGO: true, |
| mustInternalLink: true, |
| wantSecsRO: []string{"__got", "__rodata", "__itablink", "__typelink"}, |
| }, |
| { |
| name: "cgo-linkmode-external", |
| args: []string{"-ldflags", "-linkmode=external"}, |
| prog: progC, |
| mustHaveCGO: true, |
| wantSecsRO: []string{"__got", "__rodata", "__itablink", "__typelink"}, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| if test.mustInternalLink { |
| testenv.MustInternalLink(t, testenv.SpecialBuildTypes{Cgo: test.mustHaveCGO}) |
| } |
| if test.mustHaveCGO { |
| testenv.MustHaveCGO(t) |
| } |
| |
| var ( |
| dir = t.TempDir() |
| src = filepath.Join(dir, fmt.Sprintf("macho_%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() |
| |
| machoFile, err := macho.NewFile(fi) |
| if err != nil { |
| t.Fatalf("failed to parse macho file: %v", err) |
| } |
| defer machoFile.Close() |
| |
| // Load segments |
| segs := make(map[string]*macho.Segment) |
| for _, l := range machoFile.Loads { |
| if s, ok := l.(*macho.Segment); ok { |
| segs[s.Name] = s |
| } |
| } |
| |
| for _, wsroname := range test.wantSecsRO { |
| // Now walk the sections. Section should be part of |
| // some segment that is made readonly after |
| // relocations are appied. |
| var wsro *macho.Section |
| foundRO := false |
| for _, s := range machoFile.Sections { |
| if s.Name == wsroname { |
| seg := segs[s.Seg] |
| if seg == nil { |
| t.Fatalf("test %s: can't locate segment for %q section", |
| test.name, wsroname) |
| } |
| if seg.Flag == 0x10 { // SG_READ_ONLY |
| foundRO = true |
| wsro = s |
| break |
| } |
| } |
| } |
| if wsro == nil { |
| t.Fatalf("test %s: can't locate %q section", |
| test.name, wsroname) |
| continue |
| } |
| if !foundRO { |
| // Things went off the rails. Write out some |
| // useful information for a human looking at the |
| // test failure. |
| t.Logf("test %s: %q section not in readonly segment", |
| wsro.Name, test.name) |
| t.Logf("section %s location: st=0x%x en=0x%x\n", |
| wsro.Name, wsro.Addr, wsro.Addr+wsro.Size) |
| t.Logf("sec %s found in this segment: ", wsro.Seg) |
| t.Logf("\nall segments: \n") |
| for _, l := range machoFile.Loads { |
| if s, ok := l.(*macho.Segment); ok { |
| t.Logf("cmd=%s fl=%d st=0x%x en=0x%x\n", |
| s.Cmd, s.Flag, s.Addr, s.Addr+s.Filesz) |
| } |
| } |
| t.Fatalf("test %s failed", test.name) |
| } |
| } |
| }) |
| } |
| } |