| // 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 | |
| w.order = w.p.byteorder | |
| 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 | |
| } |