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