blob: 905de67f6016ed13f672aeb6982b02b01dce423a [file] [log] [blame]
// 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)
}
}
})
}
}