Source file src/debug/macho/file_test.go

     1  // Copyright 2009 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  package macho
     6  
     7  import (
     8  	"bytes"
     9  	"internal/obscuretestdata"
    10  	"io"
    11  	"reflect"
    12  	"slices"
    13  	"testing"
    14  )
    15  
    16  type fileTest struct {
    17  	file         string
    18  	hdr          FileHeader
    19  	loads        []any
    20  	sections     []*SectionHeader
    21  	relocations  map[string][]Reloc
    22  	importedSyms []string
    23  }
    24  
    25  var fileTests = []fileTest{
    26  	{
    27  		"testdata/gcc-386-darwin-exec.base64",
    28  		FileHeader{0xfeedface, Cpu386, 0x3, 0x2, 0xc, 0x3c0, 0x85},
    29  		[]any{
    30  			&SegmentHeader{LoadCmdSegment, 0x38, "__PAGEZERO", 0x0, 0x1000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
    31  			&SegmentHeader{LoadCmdSegment, 0xc0, "__TEXT", 0x1000, 0x1000, 0x0, 0x1000, 0x7, 0x5, 0x2, 0x0},
    32  			&SegmentHeader{LoadCmdSegment, 0xc0, "__DATA", 0x2000, 0x1000, 0x1000, 0x1000, 0x7, 0x3, 0x2, 0x0},
    33  			&SegmentHeader{LoadCmdSegment, 0x7c, "__IMPORT", 0x3000, 0x1000, 0x2000, 0x1000, 0x7, 0x7, 0x1, 0x0},
    34  			&SegmentHeader{LoadCmdSegment, 0x38, "__LINKEDIT", 0x4000, 0x1000, 0x3000, 0x12c, 0x7, 0x1, 0x0, 0x0},
    35  			nil, // LC_SYMTAB
    36  			nil, // LC_DYSYMTAB
    37  			nil, // LC_LOAD_DYLINKER
    38  			nil, // LC_UUID
    39  			nil, // LC_UNIXTHREAD
    40  			&Dylib{nil, "/usr/lib/libgcc_s.1.dylib", 0x2, 0x10000, 0x10000},
    41  			&Dylib{nil, "/usr/lib/libSystem.B.dylib", 0x2, 0x6f0104, 0x10000},
    42  		},
    43  		[]*SectionHeader{
    44  			{"__text", "__TEXT", 0x1f68, 0x88, 0xf68, 0x2, 0x0, 0x0, 0x80000400},
    45  			{"__cstring", "__TEXT", 0x1ff0, 0xd, 0xff0, 0x0, 0x0, 0x0, 0x2},
    46  			{"__data", "__DATA", 0x2000, 0x14, 0x1000, 0x2, 0x0, 0x0, 0x0},
    47  			{"__dyld", "__DATA", 0x2014, 0x1c, 0x1014, 0x2, 0x0, 0x0, 0x0},
    48  			{"__jump_table", "__IMPORT", 0x3000, 0xa, 0x2000, 0x6, 0x0, 0x0, 0x4000008},
    49  		},
    50  		nil,
    51  		nil,
    52  	},
    53  	{
    54  		"testdata/gcc-amd64-darwin-exec.base64",
    55  		FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0x2, 0xb, 0x568, 0x85},
    56  		[]any{
    57  			&SegmentHeader{LoadCmdSegment64, 0x48, "__PAGEZERO", 0x0, 0x100000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
    58  			&SegmentHeader{LoadCmdSegment64, 0x1d8, "__TEXT", 0x100000000, 0x1000, 0x0, 0x1000, 0x7, 0x5, 0x5, 0x0},
    59  			&SegmentHeader{LoadCmdSegment64, 0x138, "__DATA", 0x100001000, 0x1000, 0x1000, 0x1000, 0x7, 0x3, 0x3, 0x0},
    60  			&SegmentHeader{LoadCmdSegment64, 0x48, "__LINKEDIT", 0x100002000, 0x1000, 0x2000, 0x140, 0x7, 0x1, 0x0, 0x0},
    61  			nil, // LC_SYMTAB
    62  			nil, // LC_DYSYMTAB
    63  			nil, // LC_LOAD_DYLINKER
    64  			nil, // LC_UUID
    65  			nil, // LC_UNIXTHREAD
    66  			&Dylib{nil, "/usr/lib/libgcc_s.1.dylib", 0x2, 0x10000, 0x10000},
    67  			&Dylib{nil, "/usr/lib/libSystem.B.dylib", 0x2, 0x6f0104, 0x10000},
    68  		},
    69  		[]*SectionHeader{
    70  			{"__text", "__TEXT", 0x100000f14, 0x6d, 0xf14, 0x2, 0x0, 0x0, 0x80000400},
    71  			{"__symbol_stub1", "__TEXT", 0x100000f81, 0xc, 0xf81, 0x0, 0x0, 0x0, 0x80000408},
    72  			{"__stub_helper", "__TEXT", 0x100000f90, 0x18, 0xf90, 0x2, 0x0, 0x0, 0x0},
    73  			{"__cstring", "__TEXT", 0x100000fa8, 0xd, 0xfa8, 0x0, 0x0, 0x0, 0x2},
    74  			{"__eh_frame", "__TEXT", 0x100000fb8, 0x48, 0xfb8, 0x3, 0x0, 0x0, 0x6000000b},
    75  			{"__data", "__DATA", 0x100001000, 0x1c, 0x1000, 0x3, 0x0, 0x0, 0x0},
    76  			{"__dyld", "__DATA", 0x100001020, 0x38, 0x1020, 0x3, 0x0, 0x0, 0x0},
    77  			{"__la_symbol_ptr", "__DATA", 0x100001058, 0x10, 0x1058, 0x2, 0x0, 0x0, 0x7},
    78  		},
    79  		nil,
    80  		nil,
    81  	},
    82  	{
    83  		"testdata/gcc-amd64-darwin-exec-debug.base64",
    84  		FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0xa, 0x4, 0x5a0, 0},
    85  		[]any{
    86  			nil, // LC_UUID
    87  			&SegmentHeader{LoadCmdSegment64, 0x1d8, "__TEXT", 0x100000000, 0x1000, 0x0, 0x0, 0x7, 0x5, 0x5, 0x0},
    88  			&SegmentHeader{LoadCmdSegment64, 0x138, "__DATA", 0x100001000, 0x1000, 0x0, 0x0, 0x7, 0x3, 0x3, 0x0},
    89  			&SegmentHeader{LoadCmdSegment64, 0x278, "__DWARF", 0x100002000, 0x1000, 0x1000, 0x1bc, 0x7, 0x3, 0x7, 0x0},
    90  		},
    91  		[]*SectionHeader{
    92  			{"__text", "__TEXT", 0x100000f14, 0x0, 0x0, 0x2, 0x0, 0x0, 0x80000400},
    93  			{"__symbol_stub1", "__TEXT", 0x100000f81, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000408},
    94  			{"__stub_helper", "__TEXT", 0x100000f90, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0},
    95  			{"__cstring", "__TEXT", 0x100000fa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
    96  			{"__eh_frame", "__TEXT", 0x100000fb8, 0x0, 0x0, 0x3, 0x0, 0x0, 0x6000000b},
    97  			{"__data", "__DATA", 0x100001000, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0},
    98  			{"__dyld", "__DATA", 0x100001020, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0},
    99  			{"__la_symbol_ptr", "__DATA", 0x100001058, 0x0, 0x0, 0x2, 0x0, 0x0, 0x7},
   100  			{"__debug_abbrev", "__DWARF", 0x100002000, 0x36, 0x1000, 0x0, 0x0, 0x0, 0x0},
   101  			{"__debug_aranges", "__DWARF", 0x100002036, 0x30, 0x1036, 0x0, 0x0, 0x0, 0x0},
   102  			{"__debug_frame", "__DWARF", 0x100002066, 0x40, 0x1066, 0x0, 0x0, 0x0, 0x0},
   103  			{"__debug_info", "__DWARF", 0x1000020a6, 0x54, 0x10a6, 0x0, 0x0, 0x0, 0x0},
   104  			{"__debug_line", "__DWARF", 0x1000020fa, 0x47, 0x10fa, 0x0, 0x0, 0x0, 0x0},
   105  			{"__debug_pubnames", "__DWARF", 0x100002141, 0x1b, 0x1141, 0x0, 0x0, 0x0, 0x0},
   106  			{"__debug_str", "__DWARF", 0x10000215c, 0x60, 0x115c, 0x0, 0x0, 0x0, 0x0},
   107  		},
   108  		nil,
   109  		nil,
   110  	},
   111  	{
   112  		"testdata/clang-386-darwin-exec-with-rpath.base64",
   113  		FileHeader{0xfeedface, Cpu386, 0x3, 0x2, 0x10, 0x42c, 0x1200085},
   114  		[]any{
   115  			nil, // LC_SEGMENT
   116  			nil, // LC_SEGMENT
   117  			nil, // LC_SEGMENT
   118  			nil, // LC_SEGMENT
   119  			nil, // LC_DYLD_INFO_ONLY
   120  			nil, // LC_SYMTAB
   121  			nil, // LC_DYSYMTAB
   122  			nil, // LC_LOAD_DYLINKER
   123  			nil, // LC_UUID
   124  			nil, // LC_VERSION_MIN_MACOSX
   125  			nil, // LC_SOURCE_VERSION
   126  			nil, // LC_MAIN
   127  			nil, // LC_LOAD_DYLIB
   128  			&Rpath{nil, "/my/rpath"},
   129  			nil, // LC_FUNCTION_STARTS
   130  			nil, // LC_DATA_IN_CODE
   131  		},
   132  		nil,
   133  		nil,
   134  		nil,
   135  	},
   136  	{
   137  		"testdata/clang-amd64-darwin-exec-with-rpath.base64",
   138  		FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0x2, 0x10, 0x4c8, 0x200085},
   139  		[]any{
   140  			nil, // LC_SEGMENT
   141  			nil, // LC_SEGMENT
   142  			nil, // LC_SEGMENT
   143  			nil, // LC_SEGMENT
   144  			nil, // LC_DYLD_INFO_ONLY
   145  			nil, // LC_SYMTAB
   146  			nil, // LC_DYSYMTAB
   147  			nil, // LC_LOAD_DYLINKER
   148  			nil, // LC_UUID
   149  			nil, // LC_VERSION_MIN_MACOSX
   150  			nil, // LC_SOURCE_VERSION
   151  			nil, // LC_MAIN
   152  			nil, // LC_LOAD_DYLIB
   153  			&Rpath{nil, "/my/rpath"},
   154  			nil, // LC_FUNCTION_STARTS
   155  			nil, // LC_DATA_IN_CODE
   156  		},
   157  		nil,
   158  		nil,
   159  		nil,
   160  	},
   161  	{
   162  		"testdata/clang-386-darwin.obj.base64",
   163  		FileHeader{0xfeedface, Cpu386, 0x3, 0x1, 0x4, 0x138, 0x2000},
   164  		nil,
   165  		nil,
   166  		map[string][]Reloc{
   167  			"__text": {
   168  				{
   169  					Addr:      0x1d,
   170  					Type:      uint8(GENERIC_RELOC_VANILLA),
   171  					Len:       2,
   172  					Pcrel:     true,
   173  					Extern:    true,
   174  					Value:     1,
   175  					Scattered: false,
   176  				},
   177  				{
   178  					Addr:      0xe,
   179  					Type:      uint8(GENERIC_RELOC_LOCAL_SECTDIFF),
   180  					Len:       2,
   181  					Pcrel:     false,
   182  					Value:     0x2d,
   183  					Scattered: true,
   184  				},
   185  				{
   186  					Addr:      0x0,
   187  					Type:      uint8(GENERIC_RELOC_PAIR),
   188  					Len:       2,
   189  					Pcrel:     false,
   190  					Value:     0xb,
   191  					Scattered: true,
   192  				},
   193  			},
   194  		},
   195  		nil,
   196  	},
   197  	{
   198  		"testdata/clang-amd64-darwin.obj.base64",
   199  		FileHeader{0xfeedfacf, CpuAmd64, 0x3, 0x1, 0x4, 0x200, 0x2000},
   200  		nil,
   201  		nil,
   202  		map[string][]Reloc{
   203  			"__text": {
   204  				{
   205  					Addr:   0x19,
   206  					Type:   uint8(X86_64_RELOC_BRANCH),
   207  					Len:    2,
   208  					Pcrel:  true,
   209  					Extern: true,
   210  					Value:  1,
   211  				},
   212  				{
   213  					Addr:   0xb,
   214  					Type:   uint8(X86_64_RELOC_SIGNED),
   215  					Len:    2,
   216  					Pcrel:  true,
   217  					Extern: false,
   218  					Value:  2,
   219  				},
   220  			},
   221  			"__compact_unwind": {
   222  				{
   223  					Addr:   0x0,
   224  					Type:   uint8(X86_64_RELOC_UNSIGNED),
   225  					Len:    3,
   226  					Pcrel:  false,
   227  					Extern: false,
   228  					Value:  1,
   229  				},
   230  			},
   231  		},
   232  		[]string{"_printf"},
   233  	},
   234  	{
   235  		"testdata/clang-amd64-darwin-ld-r.obj.base64",
   236  		FileHeader{0xfeedfacf, CpuAmd64, 0x3, 0x1, 0x4, 0x1c0, 0x2000},
   237  		nil,
   238  		nil,
   239  		nil,
   240  		[]string{"_printf"},
   241  	},
   242  }
   243  
   244  func readerAtFromObscured(name string) (io.ReaderAt, error) {
   245  	b, err := obscuretestdata.ReadFile(name)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  	return bytes.NewReader(b), nil
   250  }
   251  
   252  func openObscured(name string) (*File, error) {
   253  	ra, err := readerAtFromObscured(name)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	ff, err := NewFile(ra)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	return ff, nil
   262  }
   263  
   264  func openFatObscured(name string) (*FatFile, error) {
   265  	ra, err := readerAtFromObscured(name)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	ff, err := NewFatFile(ra)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  	return ff, nil
   274  }
   275  
   276  func TestOpen(t *testing.T) {
   277  	for i := range fileTests {
   278  		tt := &fileTests[i]
   279  
   280  		// Use obscured files to prevent Apple’s notarization service from
   281  		// mistaking them as candidates for notarization and rejecting the entire
   282  		// toolchain.
   283  		// See golang.org/issue/34986
   284  		f, err := openObscured(tt.file)
   285  		if err != nil {
   286  			t.Error(err)
   287  			continue
   288  		}
   289  		if !reflect.DeepEqual(f.FileHeader, tt.hdr) {
   290  			t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.FileHeader, tt.hdr)
   291  			continue
   292  		}
   293  		for i, l := range f.Loads {
   294  			if len(l.Raw()) < 8 {
   295  				t.Errorf("open %s, command %d:\n\tload command %T don't have enough data\n", tt.file, i, l)
   296  			}
   297  		}
   298  		if tt.loads != nil {
   299  			for i, l := range f.Loads {
   300  				if i >= len(tt.loads) {
   301  					break
   302  				}
   303  
   304  				want := tt.loads[i]
   305  				if want == nil {
   306  					continue
   307  				}
   308  
   309  				switch l := l.(type) {
   310  				case *Segment:
   311  					have := &l.SegmentHeader
   312  					if !reflect.DeepEqual(have, want) {
   313  						t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
   314  					}
   315  				case *Dylib:
   316  					have := l
   317  					have.LoadBytes = nil
   318  					if !reflect.DeepEqual(have, want) {
   319  						t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
   320  					}
   321  				case *Rpath:
   322  					have := l
   323  					have.LoadBytes = nil
   324  					if !reflect.DeepEqual(have, want) {
   325  						t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
   326  					}
   327  				default:
   328  					t.Errorf("open %s, command %d: unknown load command\n\thave %#v\n\twant %#v\n", tt.file, i, l, want)
   329  				}
   330  			}
   331  			tn := len(tt.loads)
   332  			fn := len(f.Loads)
   333  			if tn != fn {
   334  				t.Errorf("open %s: len(Loads) = %d, want %d", tt.file, fn, tn)
   335  			}
   336  		}
   337  
   338  		if tt.sections != nil {
   339  			for i, sh := range f.Sections {
   340  				if i >= len(tt.sections) {
   341  					break
   342  				}
   343  				have := &sh.SectionHeader
   344  				want := tt.sections[i]
   345  				if !reflect.DeepEqual(have, want) {
   346  					t.Errorf("open %s, section %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
   347  				}
   348  			}
   349  			tn := len(tt.sections)
   350  			fn := len(f.Sections)
   351  			if tn != fn {
   352  				t.Errorf("open %s: len(Sections) = %d, want %d", tt.file, fn, tn)
   353  			}
   354  		}
   355  
   356  		if tt.relocations != nil {
   357  			for i, sh := range f.Sections {
   358  				have := sh.Relocs
   359  				want := tt.relocations[sh.Name]
   360  				if !reflect.DeepEqual(have, want) {
   361  					t.Errorf("open %s, relocations in section %d (%s):\n\thave %#v\n\twant %#v\n", tt.file, i, sh.Name, have, want)
   362  				}
   363  			}
   364  		}
   365  
   366  		if tt.importedSyms != nil {
   367  			ss, err := f.ImportedSymbols()
   368  			if err != nil {
   369  				t.Errorf("open %s: fail to read imported symbols: %v", tt.file, err)
   370  			}
   371  			want := tt.importedSyms
   372  			if !slices.Equal(ss, want) {
   373  				t.Errorf("open %s: imported symbols differ:\n\thave %v\n\twant %v", tt.file, ss, want)
   374  			}
   375  		}
   376  	}
   377  }
   378  
   379  func TestOpenFailure(t *testing.T) {
   380  	filename := "file.go"    // not a Mach-O file
   381  	_, err := Open(filename) // don't crash
   382  	if err == nil {
   383  		t.Errorf("open %s: succeeded unexpectedly", filename)
   384  	}
   385  }
   386  
   387  func TestOpenFat(t *testing.T) {
   388  	ff, err := openFatObscured("testdata/fat-gcc-386-amd64-darwin-exec.base64")
   389  	if err != nil {
   390  		t.Fatal(err)
   391  	}
   392  
   393  	if ff.Magic != MagicFat {
   394  		t.Errorf("OpenFat: got magic number %#x, want %#x", ff.Magic, MagicFat)
   395  	}
   396  	if len(ff.Arches) != 2 {
   397  		t.Errorf("OpenFat: got %d architectures, want 2", len(ff.Arches))
   398  	}
   399  
   400  	for i := range ff.Arches {
   401  		arch := &ff.Arches[i]
   402  		ftArch := &fileTests[i]
   403  
   404  		if arch.Cpu != ftArch.hdr.Cpu || arch.SubCpu != ftArch.hdr.SubCpu {
   405  			t.Errorf("OpenFat: architecture #%d got cpu=%#x subtype=%#x, expected cpu=%#x, subtype=%#x", i, arch.Cpu, arch.SubCpu, ftArch.hdr.Cpu, ftArch.hdr.SubCpu)
   406  		}
   407  
   408  		if !reflect.DeepEqual(arch.FileHeader, ftArch.hdr) {
   409  			t.Errorf("OpenFat header:\n\tgot %#v\n\twant %#v\n", arch.FileHeader, ftArch.hdr)
   410  		}
   411  	}
   412  }
   413  
   414  func TestOpenFatFailure(t *testing.T) {
   415  	filename := "file.go" // not a Mach-O file
   416  	if _, err := OpenFat(filename); err == nil {
   417  		t.Errorf("OpenFat %s: succeeded unexpectedly", filename)
   418  	}
   419  
   420  	filename = "testdata/gcc-386-darwin-exec.base64" // not a fat Mach-O
   421  	ff, err := openFatObscured(filename)
   422  	if err != ErrNotFat {
   423  		t.Errorf("OpenFat %s: got %v, want ErrNotFat", filename, err)
   424  	}
   425  	if ff != nil {
   426  		t.Errorf("OpenFat %s: got %v, want nil", filename, ff)
   427  	}
   428  }
   429  
   430  func TestRelocTypeString(t *testing.T) {
   431  	if X86_64_RELOC_BRANCH.String() != "X86_64_RELOC_BRANCH" {
   432  		t.Errorf("got %v, want %v", X86_64_RELOC_BRANCH.String(), "X86_64_RELOC_BRANCH")
   433  	}
   434  	if X86_64_RELOC_BRANCH.GoString() != "macho.X86_64_RELOC_BRANCH" {
   435  		t.Errorf("got %v, want %v", X86_64_RELOC_BRANCH.GoString(), "macho.X86_64_RELOC_BRANCH")
   436  	}
   437  }
   438  
   439  func TestTypeString(t *testing.T) {
   440  	if TypeExec.String() != "Exec" {
   441  		t.Errorf("got %v, want %v", TypeExec.String(), "Exec")
   442  	}
   443  	if TypeExec.GoString() != "macho.Exec" {
   444  		t.Errorf("got %v, want %v", TypeExec.GoString(), "macho.Exec")
   445  	}
   446  }
   447  
   448  func TestOpenBadDysymCmd(t *testing.T) {
   449  	_, err := openObscured("testdata/gcc-amd64-darwin-exec-with-bad-dysym.base64")
   450  	if err == nil {
   451  		t.Fatal("openObscured did not fail when opening a file with an invalid dynamic symbol table command")
   452  	}
   453  }
   454  

View as plain text