| // Copyright 2014 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. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "debug/macho" |
| "encoding/binary" |
| "fmt" |
| "io/ioutil" |
| "strings" |
| "testing" |
| ) |
| |
| // Test macho writing by checking that each generated prog can be written |
| // and then read back using debug/macho to get the same prog. |
| // Also check against golden testdata file. |
| var machoWriteTests = []struct { |
| name string |
| golden bool |
| prog *Prog |
| }{ |
| // amd64 exit 9 |
| { |
| name: "exit9", |
| golden: true, |
| prog: &Prog{ |
| GOARCH: "amd64", |
| GOOS: "darwin", |
| UnmappedSize: 0x1000, |
| Entry: 0x1000, |
| Segments: []*Segment{ |
| { |
| Name: "text", |
| VirtAddr: 0x1000, |
| VirtSize: 13, |
| FileOffset: 0, |
| FileSize: 13, |
| Data: []byte{ |
| 0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX |
| 0xbf, 0x09, 0x00, 0x00, 0x00, // MOVL $9, DI |
| 0x0f, 0x05, // SYSCALL |
| 0xf4, // HLT |
| }, |
| Sections: []*Section{ |
| { |
| Name: "text", |
| VirtAddr: 0x1000, |
| Size: 13, |
| Align: 64, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| |
| // amd64 write hello world & exit 9 |
| { |
| name: "hello", |
| golden: true, |
| prog: &Prog{ |
| GOARCH: "amd64", |
| GOOS: "darwin", |
| UnmappedSize: 0x1000, |
| Entry: 0x1000, |
| Segments: []*Segment{ |
| { |
| Name: "text", |
| VirtAddr: 0x1000, |
| VirtSize: 35, |
| FileOffset: 0, |
| FileSize: 35, |
| Data: []byte{ |
| 0xb8, 0x04, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX |
| 0xbf, 0x01, 0x00, 0x00, 0x00, // MOVL $1, DI |
| 0xbe, 0x00, 0x30, 0x00, 0x00, // MOVL $0x3000, SI |
| 0xba, 0x0c, 0x00, 0x00, 0x00, // MOVL $12, DX |
| 0x0f, 0x05, // SYSCALL |
| 0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX |
| 0xbf, 0x09, 0x00, 0x00, 0x00, // MOVL $9, DI |
| 0x0f, 0x05, // SYSCALL |
| 0xf4, // HLT |
| }, |
| Sections: []*Section{ |
| { |
| Name: "text", |
| VirtAddr: 0x1000, |
| Size: 35, |
| Align: 64, |
| }, |
| }, |
| }, |
| { |
| Name: "data", |
| VirtAddr: 0x2000, |
| VirtSize: 12, |
| FileOffset: 0x1000, |
| FileSize: 12, |
| Data: []byte("hello world\n"), |
| Sections: []*Section{ |
| { |
| Name: "data", |
| VirtAddr: 0x2000, |
| Size: 12, |
| Align: 64, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| |
| // amd64 write hello world from rodata & exit 0 |
| { |
| name: "helloro", |
| golden: true, |
| prog: &Prog{ |
| GOARCH: "amd64", |
| GOOS: "darwin", |
| UnmappedSize: 0x1000, |
| Entry: 0x1000, |
| Segments: []*Segment{ |
| { |
| Name: "text", |
| VirtAddr: 0x1000, |
| VirtSize: 0x100c, |
| FileOffset: 0, |
| FileSize: 0x100c, |
| Data: concat( |
| []byte{ |
| 0xb8, 0x04, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX |
| 0xbf, 0x01, 0x00, 0x00, 0x00, // MOVL $1, DI |
| 0xbe, 0x00, 0x30, 0x00, 0x00, // MOVL $0x3000, SI |
| 0xba, 0x0c, 0x00, 0x00, 0x00, // MOVL $12, DX |
| 0x0f, 0x05, // SYSCALL |
| 0xb8, 0x01, 0x00, 0x00, 0x02, // MOVL $0x2000001, AX |
| 0xbf, 0x00, 0x00, 0x00, 0x00, // MOVL $0, DI |
| 0x0f, 0x05, // SYSCALL |
| 0xf4, // HLT |
| }, |
| make([]byte, 0x1000-35), |
| []byte("hello world\n"), |
| ), |
| Sections: []*Section{ |
| { |
| Name: "text", |
| VirtAddr: 0x1000, |
| Size: 35, |
| Align: 64, |
| }, |
| { |
| Name: "rodata", |
| VirtAddr: 0x2000, |
| Size: 12, |
| Align: 64, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| func concat(xs ...[]byte) []byte { |
| var out []byte |
| for _, x := range xs { |
| out = append(out, x...) |
| } |
| return out |
| } |
| |
| func TestMachoWrite(t *testing.T) { |
| for _, tt := range machoWriteTests { |
| name := tt.prog.GOARCH + "." + tt.name |
| prog := cloneProg(tt.prog) |
| prog.init() |
| var f machoFormat |
| vsize, fsize := f.headerSize(prog) |
| shiftProg(prog, vsize, fsize) |
| var buf bytes.Buffer |
| f.write(&buf, prog) |
| if false { // enable to debug |
| ioutil.WriteFile("a.out", buf.Bytes(), 0777) |
| } |
| read, err := machoRead(machoArches[tt.prog.GOARCH], buf.Bytes()) |
| if err != nil { |
| t.Errorf("%s: reading mach-o output:\n\t%v", name, err) |
| continue |
| } |
| diffs := diffProg(read, prog) |
| if diffs != nil { |
| t.Errorf("%s: mismatched prog:\n\t%s", name, strings.Join(diffs, "\n\t")) |
| continue |
| } |
| if !tt.golden { |
| continue |
| } |
| checkGolden(t, buf.Bytes(), "testdata/macho."+name) |
| } |
| } |
| |
| // machoRead reads the mach-o file in data and returns a corresponding prog. |
| func machoRead(arch machoArch, data []byte) (*Prog, error) { |
| f, err := macho.NewFile(bytes.NewReader(data)) |
| if err != nil { |
| return nil, err |
| } |
| |
| var errors []string |
| errorf := func(format string, args ...interface{}) { |
| errors = append(errors, fmt.Sprintf(format, args...)) |
| } |
| |
| magic := uint32(0xFEEDFACE) |
| if arch.CPU&macho64Bit != 0 { |
| magic |= 1 |
| } |
| if f.Magic != magic { |
| errorf("header: Magic = %#x, want %#x", f.Magic, magic) |
| } |
| if f.Cpu != macho.CpuAmd64 { |
| errorf("header: CPU = %#x, want %#x", f.Cpu, macho.CpuAmd64) |
| } |
| if f.SubCpu != 3 { |
| errorf("header: SubCPU = %#x, want %#x", f.SubCpu, 3) |
| } |
| if f.Type != 2 { |
| errorf("header: FileType = %d, want %d", f.Type, 2) |
| } |
| if f.Flags != 1 { |
| errorf("header: Flags = %d, want %d", f.Flags, 1) |
| } |
| |
| msects := f.Sections |
| var limit uint64 |
| prog := new(Prog) |
| for _, load := range f.Loads { |
| switch load := load.(type) { |
| default: |
| errorf("unexpected macho load %T %x", load, load.Raw()) |
| |
| case macho.LoadBytes: |
| if len(load) < 8 || len(load)%4 != 0 { |
| errorf("unexpected load length %d", len(load)) |
| continue |
| } |
| cmd := f.ByteOrder.Uint32(load) |
| switch macho.LoadCmd(cmd) { |
| default: |
| errorf("unexpected macho load cmd %s", macho.LoadCmd(cmd)) |
| case macho.LoadCmdUnixThread: |
| data := make([]uint32, len(load[8:])/4) |
| binary.Read(bytes.NewReader(load[8:]), f.ByteOrder, data) |
| if len(data) != 44 { |
| errorf("macho thread len(data) = %d, want 42", len(data)) |
| continue |
| } |
| if data[0] != 4 { |
| errorf("macho thread type = %d, want 4", data[0]) |
| } |
| if data[1] != uint32(len(data))-2 { |
| errorf("macho thread desc len = %d, want %d", data[1], uint32(len(data))-2) |
| continue |
| } |
| for i, val := range data[2:] { |
| switch i { |
| default: |
| if val != 0 { |
| errorf("macho thread data[%d] = %#x, want 0", i, val) |
| } |
| case 32: |
| prog.Entry = Addr(val) |
| case 33: |
| prog.Entry |= Addr(val) << 32 |
| } |
| } |
| } |
| |
| case *macho.Segment: |
| if load.Addr < limit { |
| errorf("segments out of order: %q at %#x after %#x", load.Name, load.Addr, limit) |
| } |
| limit = load.Addr + load.Memsz |
| if load.Name == "__PAGEZERO" || load.Addr == 0 && load.Filesz == 0 { |
| if load.Name != "__PAGEZERO" { |
| errorf("segment with Addr=0, Filesz=0 is named %q, want %q", load.Name, "__PAGEZERO") |
| } else if load.Addr != 0 || load.Filesz != 0 { |
| errorf("segment %q has Addr=%#x, Filesz=%d, want Addr=%#x, Filesz=%d", load.Name, load.Addr, load.Filesz, 0, 0) |
| } |
| prog.UnmappedSize = Addr(load.Memsz) |
| continue |
| } |
| |
| if !strings.HasPrefix(load.Name, "__") { |
| errorf("segment name %q does not begin with %q", load.Name, "__") |
| } |
| if strings.ToUpper(load.Name) != load.Name { |
| errorf("segment name %q is not all upper case", load.Name) |
| } |
| |
| seg := &Segment{ |
| Name: strings.ToLower(strings.TrimPrefix(load.Name, "__")), |
| VirtAddr: Addr(load.Addr), |
| VirtSize: Addr(load.Memsz), |
| FileOffset: Addr(load.Offset), |
| FileSize: Addr(load.Filesz), |
| } |
| prog.Segments = append(prog.Segments, seg) |
| |
| data, err := load.Data() |
| if err != nil { |
| errorf("loading data from %q: %v", load.Name, err) |
| } |
| seg.Data = data |
| |
| var maxprot, prot uint32 |
| if load.Name == "__TEXT" { |
| maxprot, prot = 7, 5 |
| } else { |
| maxprot, prot = 3, 3 |
| } |
| if load.Maxprot != maxprot || load.Prot != prot { |
| errorf("segment %q protection is %d, %d, want %d, %d", |
| load.Name, load.Maxprot, load.Prot, maxprot, prot) |
| } |
| |
| for len(msects) > 0 && msects[0].Addr < load.Addr+load.Memsz { |
| msect := msects[0] |
| msects = msects[1:] |
| |
| if msect.Offset > 0 && prog.HeaderSize == 0 { |
| prog.HeaderSize = Addr(msect.Offset) |
| if seg.FileOffset != 0 { |
| errorf("initial segment %q does not map header", load.Name) |
| } |
| seg.VirtAddr += prog.HeaderSize |
| seg.VirtSize -= prog.HeaderSize |
| seg.FileOffset += prog.HeaderSize |
| seg.FileSize -= prog.HeaderSize |
| seg.Data = seg.Data[prog.HeaderSize:] |
| } |
| |
| if msect.Addr < load.Addr { |
| errorf("section %q at address %#x is missing segment", msect.Name, msect.Addr) |
| continue |
| } |
| |
| if !strings.HasPrefix(msect.Name, "__") { |
| errorf("section name %q does not begin with %q", msect.Name, "__") |
| } |
| if strings.ToLower(msect.Name) != msect.Name { |
| errorf("section name %q is not all lower case", msect.Name) |
| } |
| if msect.Seg != load.Name { |
| errorf("section %q is lists segment name %q, want %q", |
| msect.Name, msect.Seg, load.Name) |
| } |
| if uint64(msect.Offset) != uint64(load.Offset)+msect.Addr-load.Addr { |
| errorf("section %q file offset is %#x, want %#x", |
| msect.Name, msect.Offset, load.Offset+msect.Addr-load.Addr) |
| } |
| if msect.Reloff != 0 || msect.Nreloc != 0 { |
| errorf("section %q has reloff %d,%d, want %d,%d", |
| msect.Name, msect.Reloff, msect.Nreloc, 0, 0) |
| } |
| flags := uint32(0) |
| if msect.Name == "__text" { |
| flags = 0x400 |
| } |
| if msect.Offset == 0 { |
| flags = 1 |
| } |
| if msect.Flags != flags { |
| errorf("section %q flags = %#x, want %#x", msect.Name, msect.Flags, flags) |
| } |
| sect := &Section{ |
| Name: strings.ToLower(strings.TrimPrefix(msect.Name, "__")), |
| VirtAddr: Addr(msect.Addr), |
| Size: Addr(msect.Size), |
| Align: 1 << msect.Align, |
| } |
| seg.Sections = append(seg.Sections, sect) |
| } |
| } |
| } |
| |
| for _, msect := range msects { |
| errorf("section %q has no segment", msect.Name) |
| } |
| |
| limit = 0 |
| for _, msect := range f.Sections { |
| if msect.Addr < limit { |
| errorf("sections out of order: %q at %#x after %#x", msect.Name, msect.Addr, limit) |
| } |
| limit = msect.Addr + msect.Size |
| } |
| |
| err = nil |
| if errors != nil { |
| err = fmt.Errorf("%s", strings.Join(errors, "\n\t")) |
| } |
| return prog, err |
| } |