mirror of
https://github.com/golang/go
synced 2024-11-24 01:30:10 -07:00
cmd/link: Mach-O (OS X) file formatter
See CL 48870044 for basic structure. R=iant CC=golang-codereviews https://golang.org/cl/48910043
This commit is contained in:
parent
146897b031
commit
8449863d31
385
src/cmd/link/macho.go
Normal file
385
src/cmd/link/macho.go
Normal file
@ -0,0 +1,385 @@
|
||||
// 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.
|
||||
|
||||
// Mach-O (Darwin) object file writing.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// machoFormat is the implementation of formatter.
|
||||
type machoFormat struct{}
|
||||
|
||||
// machoHeader and friends are data structures
|
||||
// corresponding to the Mach-O file header
|
||||
// to be written to disk.
|
||||
|
||||
const (
|
||||
macho64Bit = 1 << 24
|
||||
machoSubCPU386 = 3
|
||||
)
|
||||
|
||||
// machoArch describes a Mach-O target architecture.
|
||||
type machoArch struct {
|
||||
CPU uint32
|
||||
SubCPU uint32
|
||||
}
|
||||
|
||||
// machoHeader is the Mach-O file header.
|
||||
type machoHeader struct {
|
||||
machoArch
|
||||
FileType uint32
|
||||
Loads []*machoLoad
|
||||
Segments []*machoSegment
|
||||
p *Prog // for reporting errors
|
||||
}
|
||||
|
||||
// machoLoad is a Mach-O load command.
|
||||
type machoLoad struct {
|
||||
Type uint32
|
||||
Data []uint32
|
||||
}
|
||||
|
||||
// machoSegment is a Mach-O segment.
|
||||
type machoSegment struct {
|
||||
Name string
|
||||
VirtAddr Addr
|
||||
VirtSize Addr
|
||||
FileOffset Addr
|
||||
FileSize Addr
|
||||
Prot1 uint32
|
||||
Prot2 uint32
|
||||
Flags uint32
|
||||
Sections []*machoSection
|
||||
}
|
||||
|
||||
// machoSection is a Mach-O section, inside a segment.
|
||||
type machoSection struct {
|
||||
Name string
|
||||
Segment string
|
||||
Addr Addr
|
||||
Size Addr
|
||||
Offset uint32
|
||||
Align uint32
|
||||
Reloc uint32
|
||||
Nreloc uint32
|
||||
Flags uint32
|
||||
Res1 uint32
|
||||
Res2 uint32
|
||||
}
|
||||
|
||||
// layout positions the segments and sections in p
|
||||
// to make room for the Mach-O file header.
|
||||
// That is, it edits their VirtAddr fields to adjust for the presence
|
||||
// of the Mach-O header at the beginning of the address space.
|
||||
func (machoFormat) headerSize(p *Prog) (virt, file Addr) {
|
||||
var h machoHeader
|
||||
h.init(p)
|
||||
size := Addr(h.size())
|
||||
size = round(size, 4096)
|
||||
p.HeaderSize = size
|
||||
return size, size
|
||||
}
|
||||
|
||||
// write writes p to w as a Mach-O executable.
|
||||
// layout(p) must have already been called,
|
||||
// and the number, sizes, and addresses of the segments
|
||||
// and sections must not have been modified since the call.
|
||||
func (machoFormat) write(w io.Writer, p *Prog) {
|
||||
var h machoHeader
|
||||
h.init(p)
|
||||
off := Addr(0)
|
||||
enc := h.encode()
|
||||
w.Write(enc)
|
||||
off += Addr(len(enc))
|
||||
for _, seg := range p.Segments {
|
||||
if seg.FileOffset < off {
|
||||
h.p.errorf("mach-o error: invalid file offset")
|
||||
}
|
||||
w.Write(make([]byte, int(seg.FileOffset-off)))
|
||||
if seg.FileSize != Addr(len(seg.Data)) {
|
||||
h.p.errorf("mach-o error: invalid file size")
|
||||
}
|
||||
w.Write(seg.Data)
|
||||
off = seg.FileOffset + Addr(len(seg.Data))
|
||||
}
|
||||
}
|
||||
|
||||
// Conversion of Prog to macho data structures.
|
||||
|
||||
// machoArches maps from GOARCH to machoArch.
|
||||
var machoArches = map[string]machoArch{
|
||||
"amd64": {
|
||||
CPU: uint32(macho.CpuAmd64),
|
||||
SubCPU: uint32(machoSubCPU386),
|
||||
},
|
||||
}
|
||||
|
||||
// init initializes the header h to describe p.
|
||||
func (h *machoHeader) init(p *Prog) {
|
||||
h.p = p
|
||||
h.Segments = nil
|
||||
h.Loads = nil
|
||||
var ok bool
|
||||
h.machoArch, ok = machoArches[p.GOARCH]
|
||||
if !ok {
|
||||
p.errorf("mach-o: unknown target GOARCH %q", p.GOARCH)
|
||||
return
|
||||
}
|
||||
h.FileType = uint32(macho.TypeExec)
|
||||
|
||||
mseg := h.addSegment(p, "__PAGEZERO", nil)
|
||||
mseg.VirtSize = p.UnmappedSize
|
||||
|
||||
for _, seg := range p.Segments {
|
||||
h.addSegment(p, "__"+strings.ToUpper(seg.Name), seg)
|
||||
}
|
||||
|
||||
var data []uint32
|
||||
switch h.CPU {
|
||||
default:
|
||||
p.errorf("mach-o: unknown cpu %#x for GOARCH %q", h.CPU, p.GOARCH)
|
||||
case uint32(macho.CpuAmd64):
|
||||
data = make([]uint32, 2+42)
|
||||
data[0] = 4 // thread type
|
||||
data[1] = 42 // word count
|
||||
data[2+32] = uint32(p.Entry) // RIP register, in two parts
|
||||
data[2+32+1] = uint32(p.Entry >> 32)
|
||||
}
|
||||
|
||||
h.Loads = append(h.Loads, &machoLoad{
|
||||
Type: uint32(macho.LoadCmdUnixThread),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// addSegment adds to h a Mach-O segment like seg with the given name.
|
||||
func (h *machoHeader) addSegment(p *Prog, name string, seg *Segment) *machoSegment {
|
||||
mseg := &machoSegment{
|
||||
Name: name,
|
||||
}
|
||||
h.Segments = append(h.Segments, mseg)
|
||||
if seg == nil {
|
||||
return mseg
|
||||
}
|
||||
|
||||
mseg.VirtAddr = seg.VirtAddr
|
||||
mseg.VirtSize = seg.VirtSize
|
||||
mseg.FileOffset = round(seg.FileOffset, 4096)
|
||||
mseg.FileSize = seg.FileSize
|
||||
|
||||
if name == "__TEXT" {
|
||||
// Initially RWX, then just RX
|
||||
mseg.Prot1 = 7
|
||||
mseg.Prot2 = 5
|
||||
|
||||
// Text segment maps Mach-O header, needed by dynamic linker.
|
||||
mseg.VirtAddr -= p.HeaderSize
|
||||
mseg.VirtSize += p.HeaderSize
|
||||
mseg.FileOffset -= p.HeaderSize
|
||||
mseg.FileSize += p.HeaderSize
|
||||
} else {
|
||||
// RW
|
||||
mseg.Prot1 = 3
|
||||
mseg.Prot2 = 3
|
||||
}
|
||||
|
||||
for _, sect := range seg.Sections {
|
||||
h.addSection(mseg, seg, sect)
|
||||
}
|
||||
return mseg
|
||||
}
|
||||
|
||||
// addSection adds to mseg a Mach-O section like sect, inside seg, with the given name.
|
||||
func (h *machoHeader) addSection(mseg *machoSegment, seg *Segment, sect *Section) {
|
||||
msect := &machoSection{
|
||||
Name: "__" + sect.Name,
|
||||
Segment: mseg.Name,
|
||||
// Reloc: sect.RelocOffset,
|
||||
// NumReloc: sect.RelocLen / 8,
|
||||
Addr: sect.VirtAddr,
|
||||
Size: sect.Size,
|
||||
}
|
||||
mseg.Sections = append(mseg.Sections, msect)
|
||||
|
||||
for 1<<msect.Align < sect.Align {
|
||||
msect.Align++
|
||||
}
|
||||
|
||||
if off := sect.VirtAddr - seg.VirtAddr; off < seg.FileSize {
|
||||
// Data in file.
|
||||
if sect.Size > seg.FileSize-off {
|
||||
h.p.errorf("mach-o error: section crosses file boundary")
|
||||
}
|
||||
msect.Offset = uint32(seg.FileOffset + off)
|
||||
} else {
|
||||
// Zero filled.
|
||||
msect.Flags |= 1
|
||||
}
|
||||
|
||||
if sect.Name == "text" {
|
||||
msect.Flags |= 0x400 // contains executable instructions
|
||||
}
|
||||
}
|
||||
|
||||
// A machoWriter helps write Mach-O headers.
|
||||
// It is basically a buffer with some helper routines for writing integers.
|
||||
type machoWriter struct {
|
||||
dst []byte
|
||||
tmp [8]byte
|
||||
order binary.ByteOrder
|
||||
is64 bool
|
||||
p *Prog
|
||||
}
|
||||
|
||||
// if64 returns x if w is writing a 64-bit object file; otherwise it returns y.
|
||||
func (w *machoWriter) if64(x, y interface{}) interface{} {
|
||||
if w.is64 {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// encode encodes each of the given arguments into the writer.
|
||||
// It encodes uint32, []uint32, uint64, and []uint64 by writing each value
|
||||
// in turn in the correct byte order for the output file.
|
||||
// It encodes an Addr as a uint64 if writing a 64-bit output file, or else as a uint32.
|
||||
// It encodes []byte and string by writing the raw bytes (no length prefix).
|
||||
// It skips nil values in the args list.
|
||||
func (w *machoWriter) encode(args ...interface{}) {
|
||||
for _, arg := range args {
|
||||
switch arg := arg.(type) {
|
||||
default:
|
||||
w.p.errorf("mach-o error: cannot encode %T", arg)
|
||||
case nil:
|
||||
// skip
|
||||
case []byte:
|
||||
w.dst = append(w.dst, arg...)
|
||||
case string:
|
||||
w.dst = append(w.dst, arg...)
|
||||
case uint32:
|
||||
w.order.PutUint32(w.tmp[:], arg)
|
||||
w.dst = append(w.dst, w.tmp[:4]...)
|
||||
case []uint32:
|
||||
for _, x := range arg {
|
||||
w.order.PutUint32(w.tmp[:], x)
|
||||
w.dst = append(w.dst, w.tmp[:4]...)
|
||||
}
|
||||
case uint64:
|
||||
w.order.PutUint64(w.tmp[:], arg)
|
||||
w.dst = append(w.dst, w.tmp[:8]...)
|
||||
case Addr:
|
||||
if w.is64 {
|
||||
w.order.PutUint64(w.tmp[:], uint64(arg))
|
||||
w.dst = append(w.dst, w.tmp[:8]...)
|
||||
} else {
|
||||
if Addr(uint32(arg)) != arg {
|
||||
w.p.errorf("mach-o error: truncating address %#x to uint32", arg)
|
||||
}
|
||||
w.order.PutUint32(w.tmp[:], uint32(arg))
|
||||
w.dst = append(w.dst, w.tmp[:4]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// segmentSize returns the size of the encoding of seg in bytes.
|
||||
func (w *machoWriter) segmentSize(seg *machoSegment) int {
|
||||
if w.is64 {
|
||||
return 18*4 + 20*4*len(seg.Sections)
|
||||
}
|
||||
return 14*4 + 22*4*len(seg.Sections)
|
||||
}
|
||||
|
||||
// zeroPad returns the string s truncated or padded with NULs to n bytes.
|
||||
func zeroPad(s string, n int) string {
|
||||
if len(s) >= n {
|
||||
return s[:n]
|
||||
}
|
||||
return s + strings.Repeat("\x00", n-len(s))
|
||||
}
|
||||
|
||||
// size returns the encoded size of the header.
|
||||
func (h *machoHeader) size() int {
|
||||
// Could write separate code, but encoding is cheap; encode and throw it away.
|
||||
return len(h.encode())
|
||||
}
|
||||
|
||||
// encode returns the Mach-O encoding of the header.
|
||||
func (h *machoHeader) encode() []byte {
|
||||
w := &machoWriter{p: h.p}
|
||||
w.is64 = h.CPU&macho64Bit != 0
|
||||
switch h.SubCPU {
|
||||
default:
|
||||
h.p.errorf("mach-o error: unknown CPU")
|
||||
case machoSubCPU386:
|
||||
w.order = binary.LittleEndian
|
||||
}
|
||||
|
||||
loadSize := 0
|
||||
for _, seg := range h.Segments {
|
||||
loadSize += w.segmentSize(seg)
|
||||
}
|
||||
for _, l := range h.Loads {
|
||||
loadSize += 4 * (2 + len(l.Data))
|
||||
}
|
||||
|
||||
w.encode(
|
||||
w.if64(macho.Magic64, macho.Magic32),
|
||||
uint32(h.CPU),
|
||||
uint32(h.SubCPU),
|
||||
uint32(h.FileType),
|
||||
uint32(len(h.Loads)+len(h.Segments)),
|
||||
uint32(loadSize),
|
||||
uint32(1),
|
||||
w.if64(uint32(0), nil),
|
||||
)
|
||||
|
||||
for _, seg := range h.Segments {
|
||||
w.encode(
|
||||
w.if64(uint32(macho.LoadCmdSegment64), uint32(macho.LoadCmdSegment)),
|
||||
uint32(w.segmentSize(seg)),
|
||||
zeroPad(seg.Name, 16),
|
||||
seg.VirtAddr,
|
||||
seg.VirtSize,
|
||||
seg.FileOffset,
|
||||
seg.FileSize,
|
||||
seg.Prot1,
|
||||
seg.Prot2,
|
||||
uint32(len(seg.Sections)),
|
||||
seg.Flags,
|
||||
)
|
||||
for _, sect := range seg.Sections {
|
||||
w.encode(
|
||||
zeroPad(sect.Name, 16),
|
||||
zeroPad(seg.Name, 16),
|
||||
sect.Addr,
|
||||
sect.Size,
|
||||
sect.Offset,
|
||||
sect.Align,
|
||||
sect.Reloc,
|
||||
sect.Nreloc,
|
||||
sect.Flags,
|
||||
sect.Res1,
|
||||
sect.Res2,
|
||||
w.if64(uint32(0), nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for _, load := range h.Loads {
|
||||
w.encode(
|
||||
load.Type,
|
||||
uint32(4*(2+len(load.Data))),
|
||||
load.Data,
|
||||
)
|
||||
}
|
||||
|
||||
return w.dst
|
||||
}
|
403
src/cmd/link/macho_test.go
Normal file
403
src/cmd/link/macho_test.go
Normal file
@ -0,0 +1,403 @@
|
||||
// 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",
|
||||
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",
|
||||
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",
|
||||
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)
|
||||
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.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.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
|
||||
}
|
BIN
src/cmd/link/testdata/macho.amd64.exit9
vendored
Executable file
BIN
src/cmd/link/testdata/macho.amd64.exit9
vendored
Executable file
Binary file not shown.
BIN
src/cmd/link/testdata/macho.amd64.hello
vendored
Executable file
BIN
src/cmd/link/testdata/macho.amd64.hello
vendored
Executable file
Binary file not shown.
BIN
src/cmd/link/testdata/macho.amd64.helloro
vendored
Executable file
BIN
src/cmd/link/testdata/macho.amd64.helloro
vendored
Executable file
Binary file not shown.
Loading…
Reference in New Issue
Block a user