github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/src/cmd/link/macho_test.go (about)

     1  // Copyright 2014 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 main
     6  
     7  import (
     8  	"bytes"
     9  	"debug/macho"
    10  	"encoding/binary"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"strings"
    14  	"testing"
    15  )
    16  
    17  // Test macho writing by checking that each generated prog can be written
    18  // and then read back using debug/macho to get the same prog.
    19  // Also check against golden testdata file.
    20  var machoWriteTests = []struct {
    21  	name   string
    22  	golden bool
    23  	prog   *Prog
    24  }{
    25  	// amd64 exit 9
    26  	{
    27  		name:   "exit9",
    28  		golden: true,
    29  		prog: &Prog{
    30  			GOARCH:       "amd64",
    31  			GOOS:         "darwin",
    32  			UnmappedSize: 0x1000,
    33  			Entry:        0x1000,
    34  			Segments: []*Segment{
    35  				{
    36  					Name:       "text",
    37  					VirtAddr:   0x1000,
    38  					VirtSize:   13,
    39  					FileOffset: 0,
    40  					FileSize:   13,
    41  					Data: []byte{
    42  						0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
    43  						0xbf, 0x09, 0x00, 0x00, 0x00, // MOVL $9, DI
    44  						0x0f, 0x05, // SYSCALL
    45  						0xf4, // HLT
    46  					},
    47  					Sections: []*Section{
    48  						{
    49  							Name:     "text",
    50  							VirtAddr: 0x1000,
    51  							Size:     13,
    52  							Align:    64,
    53  						},
    54  					},
    55  				},
    56  			},
    57  		},
    58  	},
    59  
    60  	// amd64 write hello world & exit 9
    61  	{
    62  		name:   "hello",
    63  		golden: true,
    64  		prog: &Prog{
    65  			GOARCH:       "amd64",
    66  			GOOS:         "darwin",
    67  			UnmappedSize: 0x1000,
    68  			Entry:        0x1000,
    69  			Segments: []*Segment{
    70  				{
    71  					Name:       "text",
    72  					VirtAddr:   0x1000,
    73  					VirtSize:   35,
    74  					FileOffset: 0,
    75  					FileSize:   35,
    76  					Data: []byte{
    77  						0xb8, 0x04, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
    78  						0xbf, 0x01, 0x00, 0x00, 0x00, // MOVL $1, DI
    79  						0xbe, 0x00, 0x30, 0x00, 0x00, // MOVL $0x3000, SI
    80  						0xba, 0x0c, 0x00, 0x00, 0x00, // MOVL $12, DX
    81  						0x0f, 0x05, // SYSCALL
    82  						0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
    83  						0xbf, 0x09, 0x00, 0x00, 0x00, // MOVL $9, DI
    84  						0x0f, 0x05, // SYSCALL
    85  						0xf4, // HLT
    86  					},
    87  					Sections: []*Section{
    88  						{
    89  							Name:     "text",
    90  							VirtAddr: 0x1000,
    91  							Size:     35,
    92  							Align:    64,
    93  						},
    94  					},
    95  				},
    96  				{
    97  					Name:       "data",
    98  					VirtAddr:   0x2000,
    99  					VirtSize:   12,
   100  					FileOffset: 0x1000,
   101  					FileSize:   12,
   102  					Data:       []byte("hello world\n"),
   103  					Sections: []*Section{
   104  						{
   105  							Name:     "data",
   106  							VirtAddr: 0x2000,
   107  							Size:     12,
   108  							Align:    64,
   109  						},
   110  					},
   111  				},
   112  			},
   113  		},
   114  	},
   115  
   116  	// amd64 write hello world from rodata & exit 0
   117  	{
   118  		name:   "helloro",
   119  		golden: true,
   120  		prog: &Prog{
   121  			GOARCH:       "amd64",
   122  			GOOS:         "darwin",
   123  			UnmappedSize: 0x1000,
   124  			Entry:        0x1000,
   125  			Segments: []*Segment{
   126  				{
   127  					Name:       "text",
   128  					VirtAddr:   0x1000,
   129  					VirtSize:   0x100c,
   130  					FileOffset: 0,
   131  					FileSize:   0x100c,
   132  					Data: concat(
   133  						[]byte{
   134  							0xb8, 0x04, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
   135  							0xbf, 0x01, 0x00, 0x00, 0x00, // MOVL $1, DI
   136  							0xbe, 0x00, 0x30, 0x00, 0x00, // MOVL $0x3000, SI
   137  							0xba, 0x0c, 0x00, 0x00, 0x00, // MOVL $12, DX
   138  							0x0f, 0x05, // SYSCALL
   139  							0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX
   140  							0xbf, 0x00, 0x00, 0x00, 0x00, // MOVL $0, DI
   141  							0x0f, 0x05, // SYSCALL
   142  							0xf4, // HLT
   143  						},
   144  						make([]byte, 0x1000-35),
   145  						[]byte("hello world\n"),
   146  					),
   147  					Sections: []*Section{
   148  						{
   149  							Name:     "text",
   150  							VirtAddr: 0x1000,
   151  							Size:     35,
   152  							Align:    64,
   153  						},
   154  						{
   155  							Name:     "rodata",
   156  							VirtAddr: 0x2000,
   157  							Size:     12,
   158  							Align:    64,
   159  						},
   160  					},
   161  				},
   162  			},
   163  		},
   164  	},
   165  }
   166  
   167  func concat(xs ...[]byte) []byte {
   168  	var out []byte
   169  	for _, x := range xs {
   170  		out = append(out, x...)
   171  	}
   172  	return out
   173  }
   174  
   175  func TestMachoWrite(t *testing.T) {
   176  	for _, tt := range machoWriteTests {
   177  		name := tt.prog.GOARCH + "." + tt.name
   178  		prog := cloneProg(tt.prog)
   179  		prog.init()
   180  		var f machoFormat
   181  		vsize, fsize := f.headerSize(prog)
   182  		shiftProg(prog, vsize, fsize)
   183  		var buf bytes.Buffer
   184  		f.write(&buf, prog)
   185  		if false { // enable to debug
   186  			ioutil.WriteFile("a.out", buf.Bytes(), 0777)
   187  		}
   188  		read, err := machoRead(machoArches[tt.prog.GOARCH], buf.Bytes())
   189  		if err != nil {
   190  			t.Errorf("%s: reading mach-o output:\n\t%v", name, err)
   191  			continue
   192  		}
   193  		diffs := diffProg(read, prog)
   194  		if diffs != nil {
   195  			t.Errorf("%s: mismatched prog:\n\t%s", name, strings.Join(diffs, "\n\t"))
   196  			continue
   197  		}
   198  		if !tt.golden {
   199  			continue
   200  		}
   201  		checkGolden(t, buf.Bytes(), "testdata/macho."+name)
   202  	}
   203  }
   204  
   205  // machoRead reads the mach-o file in data and returns a corresponding prog.
   206  func machoRead(arch machoArch, data []byte) (*Prog, error) {
   207  	f, err := macho.NewFile(bytes.NewReader(data))
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	var errors []string
   213  	errorf := func(format string, args ...interface{}) {
   214  		errors = append(errors, fmt.Sprintf(format, args...))
   215  	}
   216  
   217  	magic := uint32(0xFEEDFACE)
   218  	if arch.CPU&macho64Bit != 0 {
   219  		magic |= 1
   220  	}
   221  	if f.Magic != magic {
   222  		errorf("header: Magic = %#x, want %#x", f.Magic, magic)
   223  	}
   224  	if f.Cpu != macho.CpuAmd64 {
   225  		errorf("header: CPU = %#x, want %#x", f.Cpu, macho.CpuAmd64)
   226  	}
   227  	if f.SubCpu != 3 {
   228  		errorf("header: SubCPU = %#x, want %#x", f.SubCpu, 3)
   229  	}
   230  	if f.Type != 2 {
   231  		errorf("header: FileType = %d, want %d", f.Type, 2)
   232  	}
   233  	if f.Flags != 1 {
   234  		errorf("header: Flags = %d, want %d", f.Flags, 1)
   235  	}
   236  
   237  	msects := f.Sections
   238  	var limit uint64
   239  	prog := new(Prog)
   240  	for _, load := range f.Loads {
   241  		switch load := load.(type) {
   242  		default:
   243  			errorf("unexpected macho load %T %x", load, load.Raw())
   244  
   245  		case macho.LoadBytes:
   246  			if len(load) < 8 || len(load)%4 != 0 {
   247  				errorf("unexpected load length %d", len(load))
   248  				continue
   249  			}
   250  			cmd := f.ByteOrder.Uint32(load)
   251  			switch macho.LoadCmd(cmd) {
   252  			default:
   253  				errorf("unexpected macho load cmd %s", macho.LoadCmd(cmd))
   254  			case macho.LoadCmdUnixThread:
   255  				data := make([]uint32, len(load[8:])/4)
   256  				binary.Read(bytes.NewReader(load[8:]), f.ByteOrder, data)
   257  				if len(data) != 44 {
   258  					errorf("macho thread len(data) = %d, want 42", len(data))
   259  					continue
   260  				}
   261  				if data[0] != 4 {
   262  					errorf("macho thread type = %d, want 4", data[0])
   263  				}
   264  				if data[1] != uint32(len(data))-2 {
   265  					errorf("macho thread desc len = %d, want %d", data[1], uint32(len(data))-2)
   266  					continue
   267  				}
   268  				for i, val := range data[2:] {
   269  					switch i {
   270  					default:
   271  						if val != 0 {
   272  							errorf("macho thread data[%d] = %#x, want 0", i, val)
   273  						}
   274  					case 32:
   275  						prog.Entry = Addr(val)
   276  					case 33:
   277  						prog.Entry |= Addr(val) << 32
   278  					}
   279  				}
   280  			}
   281  
   282  		case *macho.Segment:
   283  			if load.Addr < limit {
   284  				errorf("segments out of order: %q at %#x after %#x", load.Name, load.Addr, limit)
   285  			}
   286  			limit = load.Addr + load.Memsz
   287  			if load.Name == "__PAGEZERO" || load.Addr == 0 && load.Filesz == 0 {
   288  				if load.Name != "__PAGEZERO" {
   289  					errorf("segment with Addr=0, Filesz=0 is named %q, want %q", load.Name, "__PAGEZERO")
   290  				} else if load.Addr != 0 || load.Filesz != 0 {
   291  					errorf("segment %q has Addr=%#x, Filesz=%d, want Addr=%#x, Filesz=%d", load.Name, load.Addr, load.Filesz, 0, 0)
   292  				}
   293  				prog.UnmappedSize = Addr(load.Memsz)
   294  				continue
   295  			}
   296  
   297  			if !strings.HasPrefix(load.Name, "__") {
   298  				errorf("segment name %q does not begin with %q", load.Name, "__")
   299  			}
   300  			if strings.ToUpper(load.Name) != load.Name {
   301  				errorf("segment name %q is not all upper case", load.Name)
   302  			}
   303  
   304  			seg := &Segment{
   305  				Name:       strings.ToLower(strings.TrimPrefix(load.Name, "__")),
   306  				VirtAddr:   Addr(load.Addr),
   307  				VirtSize:   Addr(load.Memsz),
   308  				FileOffset: Addr(load.Offset),
   309  				FileSize:   Addr(load.Filesz),
   310  			}
   311  			prog.Segments = append(prog.Segments, seg)
   312  
   313  			data, err := load.Data()
   314  			if err != nil {
   315  				errorf("loading data from %q: %v", load.Name, err)
   316  			}
   317  			seg.Data = data
   318  
   319  			var maxprot, prot uint32
   320  			if load.Name == "__TEXT" {
   321  				maxprot, prot = 7, 5
   322  			} else {
   323  				maxprot, prot = 3, 3
   324  			}
   325  			if load.Maxprot != maxprot || load.Prot != prot {
   326  				errorf("segment %q protection is %d, %d, want %d, %d",
   327  					load.Name, load.Maxprot, load.Prot, maxprot, prot)
   328  			}
   329  
   330  			for len(msects) > 0 && msects[0].Addr < load.Addr+load.Memsz {
   331  				msect := msects[0]
   332  				msects = msects[1:]
   333  
   334  				if msect.Offset > 0 && prog.HeaderSize == 0 {
   335  					prog.HeaderSize = Addr(msect.Offset)
   336  					if seg.FileOffset != 0 {
   337  						errorf("initial segment %q does not map header", load.Name)
   338  					}
   339  					seg.VirtAddr += prog.HeaderSize
   340  					seg.VirtSize -= prog.HeaderSize
   341  					seg.FileOffset += prog.HeaderSize
   342  					seg.FileSize -= prog.HeaderSize
   343  					seg.Data = seg.Data[prog.HeaderSize:]
   344  				}
   345  
   346  				if msect.Addr < load.Addr {
   347  					errorf("section %q at address %#x is missing segment", msect.Name, msect.Addr)
   348  					continue
   349  				}
   350  
   351  				if !strings.HasPrefix(msect.Name, "__") {
   352  					errorf("section name %q does not begin with %q", msect.Name, "__")
   353  				}
   354  				if strings.ToLower(msect.Name) != msect.Name {
   355  					errorf("section name %q is not all lower case", msect.Name)
   356  				}
   357  				if msect.Seg != load.Name {
   358  					errorf("section %q is lists segment name %q, want %q",
   359  						msect.Name, msect.Seg, load.Name)
   360  				}
   361  				if uint64(msect.Offset) != uint64(load.Offset)+msect.Addr-load.Addr {
   362  					errorf("section %q file offset is %#x, want %#x",
   363  						msect.Name, msect.Offset, load.Offset+msect.Addr-load.Addr)
   364  				}
   365  				if msect.Reloff != 0 || msect.Nreloc != 0 {
   366  					errorf("section %q has reloff %d,%d, want %d,%d",
   367  						msect.Name, msect.Reloff, msect.Nreloc, 0, 0)
   368  				}
   369  				flags := uint32(0)
   370  				if msect.Name == "__text" {
   371  					flags = 0x400
   372  				}
   373  				if msect.Offset == 0 {
   374  					flags = 1
   375  				}
   376  				if msect.Flags != flags {
   377  					errorf("section %q flags = %#x, want %#x", msect.Name, msect.Flags, flags)
   378  				}
   379  				sect := &Section{
   380  					Name:     strings.ToLower(strings.TrimPrefix(msect.Name, "__")),
   381  					VirtAddr: Addr(msect.Addr),
   382  					Size:     Addr(msect.Size),
   383  					Align:    1 << msect.Align,
   384  				}
   385  				seg.Sections = append(seg.Sections, sect)
   386  			}
   387  		}
   388  	}
   389  
   390  	for _, msect := range msects {
   391  		errorf("section %q has no segment", msect.Name)
   392  	}
   393  
   394  	limit = 0
   395  	for _, msect := range f.Sections {
   396  		if msect.Addr < limit {
   397  			errorf("sections out of order: %q at %#x after %#x", msect.Name, msect.Addr, limit)
   398  		}
   399  		limit = msect.Addr + msect.Size
   400  	}
   401  
   402  	err = nil
   403  	if errors != nil {
   404  		err = fmt.Errorf("%s", strings.Join(errors, "\n\t"))
   405  	}
   406  	return prog, err
   407  }