| // 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 |
| } |