Source file src/cmd/link/internal/ld/macho_test.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build darwin
     6  
     7  package ld
     8  
     9  import (
    10  	"debug/macho"
    11  	"fmt"
    12  	"internal/testenv"
    13  	"os"
    14  	"path/filepath"
    15  	"testing"
    16  )
    17  
    18  func TestMachoSectionsReadOnly(t *testing.T) {
    19  	t.Parallel()
    20  	testenv.MustHaveGoBuild(t)
    21  
    22  	const (
    23  		prog  = `package main; func main() {}`
    24  		progC = `package main; import "C"; func main() {}`
    25  	)
    26  
    27  	tests := []struct {
    28  		name             string
    29  		args             []string
    30  		prog             string
    31  		wantSecsRO       []string
    32  		mustHaveCGO      bool
    33  		mustInternalLink bool
    34  	}{
    35  		{
    36  			name:             "linkmode-internal",
    37  			args:             []string{"-ldflags", "-linkmode=internal"},
    38  			prog:             prog,
    39  			mustInternalLink: true,
    40  			wantSecsRO:       []string{"__got", "__rodata", "__itablink", "__typelink", "__gosymtab", "__gopclntab"},
    41  		},
    42  		{
    43  			name:        "linkmode-external",
    44  			args:        []string{"-ldflags", "-linkmode=external"},
    45  			prog:        prog,
    46  			mustHaveCGO: true,
    47  			wantSecsRO:  []string{"__got", "__rodata", "__itablink", "__typelink", "__gopclntab"},
    48  		},
    49  		{
    50  			name:             "cgo-linkmode-internal",
    51  			args:             []string{"-ldflags", "-linkmode=external"},
    52  			prog:             progC,
    53  			mustHaveCGO:      true,
    54  			mustInternalLink: true,
    55  			wantSecsRO:       []string{"__got", "__rodata", "__itablink", "__typelink", "__gopclntab"},
    56  		},
    57  		{
    58  			name:        "cgo-linkmode-external",
    59  			args:        []string{"-ldflags", "-linkmode=external"},
    60  			prog:        progC,
    61  			mustHaveCGO: true,
    62  			wantSecsRO:  []string{"__got", "__rodata", "__itablink", "__typelink", "__gopclntab"},
    63  		},
    64  	}
    65  
    66  	for _, test := range tests {
    67  		t.Run(test.name, func(t *testing.T) {
    68  			if test.mustInternalLink {
    69  				testenv.MustInternalLink(t, test.mustHaveCGO)
    70  			}
    71  			if test.mustHaveCGO {
    72  				testenv.MustHaveCGO(t)
    73  			}
    74  
    75  			var (
    76  				dir     = t.TempDir()
    77  				src     = filepath.Join(dir, fmt.Sprintf("macho_%s.go", test.name))
    78  				binFile = filepath.Join(dir, test.name)
    79  			)
    80  
    81  			if err := os.WriteFile(src, []byte(test.prog), 0666); err != nil {
    82  				t.Fatal(err)
    83  			}
    84  
    85  			cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...)
    86  			cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...)
    87  
    88  			if out, err := cmd.CombinedOutput(); err != nil {
    89  				t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out)
    90  			}
    91  
    92  			fi, err := os.Open(binFile)
    93  			if err != nil {
    94  				t.Fatalf("failed to open built file: %v", err)
    95  			}
    96  			defer fi.Close()
    97  
    98  			machoFile, err := macho.NewFile(fi)
    99  			if err != nil {
   100  				t.Fatalf("failed to parse macho file: %v", err)
   101  			}
   102  			defer machoFile.Close()
   103  
   104  			// Load segments
   105  			segs := make(map[string]*macho.Segment)
   106  			for _, l := range machoFile.Loads {
   107  				if s, ok := l.(*macho.Segment); ok {
   108  					segs[s.Name] = s
   109  				}
   110  			}
   111  
   112  			for _, wsroname := range test.wantSecsRO {
   113  				// Now walk the sections. Section should be part of
   114  				// some segment that is readonly.
   115  				var wsro *macho.Section
   116  				foundRO := false
   117  				for _, s := range machoFile.Sections {
   118  					if s.Name == wsroname {
   119  						seg := segs[s.Seg]
   120  						if seg == nil {
   121  							t.Fatalf("test %s: can't locate segment for %q section",
   122  								test.name, wsroname)
   123  						}
   124  						if seg.Flag == 0x10 { // SG_READ_ONLY
   125  							foundRO = true
   126  							wsro = s
   127  							break
   128  						}
   129  					}
   130  				}
   131  				if wsro == nil {
   132  					t.Fatalf("test %s: can't locate %q section",
   133  						test.name, wsroname)
   134  					continue
   135  				}
   136  				if !foundRO {
   137  					// Things went off the rails. Write out some
   138  					// useful information for a human looking at the
   139  					// test failure.
   140  					t.Logf("test %s: %q section not in readonly segment",
   141  						wsro.Name, test.name)
   142  					t.Logf("section %s location: st=0x%x en=0x%x\n",
   143  						wsro.Name, wsro.Addr, wsro.Addr+wsro.Size)
   144  					t.Logf("sec %s found in this segment: ", wsro.Seg)
   145  					t.Logf("\nall segments: \n")
   146  					for _, l := range machoFile.Loads {
   147  						if s, ok := l.(*macho.Segment); ok {
   148  							t.Logf("cmd=%s fl=%d st=0x%x en=0x%x\n",
   149  								s.Cmd, s.Flag, s.Addr, s.Addr+s.Filesz)
   150  						}
   151  					}
   152  					t.Fatalf("test %s failed", test.name)
   153  				}
   154  			}
   155  		})
   156  	}
   157  }
   158  

View as plain text