| // Copyright 2009 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 macho implements access to Mach-O object files. |
| package macho |
| |
| // High level access to low level data structures. |
| |
| import ( |
| "bytes" |
| "compress/zlib" |
| "debug/dwarf" |
| "encoding/binary" |
| "fmt" |
| "io" |
| "os" |
| "strings" |
| "unsafe" |
| ) |
| |
| // A File represents an open Mach-O file. |
| type File struct { |
| FileTOC |
| |
| Symtab *Symtab |
| Dysymtab *Dysymtab |
| |
| closer io.Closer |
| } |
| |
| type FileTOC struct { |
| FileHeader |
| ByteOrder binary.ByteOrder |
| Loads []Load |
| Sections []*Section |
| } |
| |
| func (t *FileTOC) AddLoad(l Load) { |
| t.Loads = append(t.Loads, l) |
| t.NCommands++ |
| t.SizeCommands += l.LoadSize(t) |
| } |
| |
| // AddSegment adds segment s to the file table of contents, |
| // and also zeroes out the segment information with the expectation |
| // that this will be added next. |
| func (t *FileTOC) AddSegment(s *Segment) { |
| t.AddLoad(s) |
| s.Nsect = 0 |
| s.Firstsect = 0 |
| } |
| |
| // Adds section to the most recently added Segment |
| func (t *FileTOC) AddSection(s *Section) { |
| g := t.Loads[len(t.Loads)-1].(*Segment) |
| if g.Nsect == 0 { |
| g.Firstsect = uint32(len(t.Sections)) |
| } |
| g.Nsect++ |
| t.Sections = append(t.Sections, s) |
| sectionsize := uint32(unsafe.Sizeof(Section32{})) |
| if g.Command() == LcSegment64 { |
| sectionsize = uint32(unsafe.Sizeof(Section64{})) |
| } |
| t.SizeCommands += sectionsize |
| g.Len += sectionsize |
| } |
| |
| // A Load represents any Mach-O load command. |
| type Load interface { |
| String() string |
| Command() LoadCmd |
| LoadSize(*FileTOC) uint32 // Need the TOC for alignment, sigh. |
| Put([]byte, binary.ByteOrder) int |
| |
| // command LC_DYLD_INFO_ONLY contains offsets into __LINKEDIT |
| // e.g., from "otool -l a.out" |
| // |
| // Load command 3 |
| // cmd LC_SEGMENT_64 |
| // cmdsize 72 |
| // segname __LINKEDIT |
| // vmaddr 0x0000000100002000 |
| // vmsize 0x0000000000001000 |
| // fileoff 8192 |
| // filesize 520 |
| // maxprot 0x00000007 |
| // initprot 0x00000001 |
| // nsects 0 |
| // flags 0x0 |
| // Load command 4 |
| // cmd LC_DYLD_INFO_ONLY |
| // cmdsize 48 |
| // rebase_off 8192 |
| // rebase_size 8 |
| // bind_off 8200 |
| // bind_size 24 |
| // weak_bind_off 0 |
| // weak_bind_size 0 |
| // lazy_bind_off 8224 |
| // lazy_bind_size 16 |
| // export_off 8240 |
| // export_size 48 |
| } |
| |
| // LoadBytes is the uninterpreted bytes of a Mach-O load command. |
| type LoadBytes []byte |
| |
| // A SegmentHeader is the header for a Mach-O 32-bit or 64-bit load segment command. |
| type SegmentHeader struct { |
| LoadCmd |
| Len uint32 |
| Name string // 16 characters or fewer |
| Addr uint64 // memory address |
| Memsz uint64 // memory size |
| Offset uint64 // file offset |
| Filesz uint64 // number of bytes starting at that file offset |
| Maxprot uint32 |
| Prot uint32 |
| Nsect uint32 |
| Flag SegFlags |
| Firstsect uint32 |
| } |
| |
| // A Segment represents a Mach-O 32-bit or 64-bit load segment command. |
| type Segment struct { |
| SegmentHeader |
| |
| // Embed ReaderAt for ReadAt method. |
| // Do not embed SectionReader directly |
| // to avoid having Read and Seek. |
| // If a client wants Read and Seek it must use |
| // Open() to avoid fighting over the seek offset |
| // with other clients. |
| io.ReaderAt |
| sr *io.SectionReader |
| } |
| |
| func (s *Segment) Put32(b []byte, o binary.ByteOrder) int { |
| o.PutUint32(b[0*4:], uint32(s.LoadCmd)) |
| o.PutUint32(b[1*4:], s.Len) |
| putAtMost16Bytes(b[2*4:], s.Name) |
| o.PutUint32(b[6*4:], uint32(s.Addr)) |
| o.PutUint32(b[7*4:], uint32(s.Memsz)) |
| o.PutUint32(b[8*4:], uint32(s.Offset)) |
| o.PutUint32(b[9*4:], uint32(s.Filesz)) |
| o.PutUint32(b[10*4:], s.Maxprot) |
| o.PutUint32(b[11*4:], s.Prot) |
| o.PutUint32(b[12*4:], s.Nsect) |
| o.PutUint32(b[13*4:], uint32(s.Flag)) |
| return 14 * 4 |
| } |
| |
| func (s *Segment) Put64(b []byte, o binary.ByteOrder) int { |
| o.PutUint32(b[0*4:], uint32(s.LoadCmd)) |
| o.PutUint32(b[1*4:], s.Len) |
| putAtMost16Bytes(b[2*4:], s.Name) |
| o.PutUint64(b[6*4+0*8:], s.Addr) |
| o.PutUint64(b[6*4+1*8:], s.Memsz) |
| o.PutUint64(b[6*4+2*8:], s.Offset) |
| o.PutUint64(b[6*4+3*8:], s.Filesz) |
| o.PutUint32(b[6*4+4*8:], s.Maxprot) |
| o.PutUint32(b[7*4+4*8:], s.Prot) |
| o.PutUint32(b[8*4+4*8:], s.Nsect) |
| o.PutUint32(b[9*4+4*8:], uint32(s.Flag)) |
| return 10*4 + 4*8 |
| } |
| |
| // LoadCmdBytes is a command-tagged sequence of bytes. |
| // This is used for Load Commands that are not (yet) |
| // interesting to us, and to common up this behavior for |
| // all those that are. |
| type LoadCmdBytes struct { |
| LoadCmd |
| LoadBytes |
| } |
| |
| type SectionHeader struct { |
| Name string |
| Seg string |
| Addr uint64 |
| Size uint64 |
| Offset uint32 |
| Align uint32 |
| Reloff uint32 |
| Nreloc uint32 |
| Flags SecFlags |
| Reserved1 uint32 |
| Reserved2 uint32 |
| Reserved3 uint32 // only present if original was 64-bit |
| } |
| |
| // A Reloc represents a Mach-O relocation. |
| type Reloc struct { |
| Addr uint32 |
| Value uint32 |
| // when Scattered == false && Extern == true, Value is the symbol number. |
| // when Scattered == false && Extern == false, Value is the section number. |
| // when Scattered == true, Value is the value that this reloc refers to. |
| Type uint8 |
| Len uint8 // 0=byte, 1=word, 2=long, 3=quad |
| Pcrel bool |
| Extern bool // valid if Scattered == false |
| Scattered bool |
| } |
| |
| type Section struct { |
| SectionHeader |
| Relocs []Reloc |
| |
| // Embed ReaderAt for ReadAt method. |
| // Do not embed SectionReader directly |
| // to avoid having Read and Seek. |
| // If a client wants Read and Seek it must use |
| // Open() to avoid fighting over the seek offset |
| // with other clients. |
| io.ReaderAt |
| sr *io.SectionReader |
| } |
| |
| func (s *Section) Put32(b []byte, o binary.ByteOrder) int { |
| putAtMost16Bytes(b[0:], s.Name) |
| putAtMost16Bytes(b[16:], s.Seg) |
| o.PutUint32(b[8*4:], uint32(s.Addr)) |
| o.PutUint32(b[9*4:], uint32(s.Size)) |
| o.PutUint32(b[10*4:], s.Offset) |
| o.PutUint32(b[11*4:], s.Align) |
| o.PutUint32(b[12*4:], s.Reloff) |
| o.PutUint32(b[13*4:], s.Nreloc) |
| o.PutUint32(b[14*4:], uint32(s.Flags)) |
| o.PutUint32(b[15*4:], s.Reserved1) |
| o.PutUint32(b[16*4:], s.Reserved2) |
| a := 17 * 4 |
| return a + s.PutRelocs(b[a:], o) |
| } |
| |
| func (s *Section) Put64(b []byte, o binary.ByteOrder) int { |
| putAtMost16Bytes(b[0:], s.Name) |
| putAtMost16Bytes(b[16:], s.Seg) |
| o.PutUint64(b[8*4+0*8:], s.Addr) |
| o.PutUint64(b[8*4+1*8:], s.Size) |
| o.PutUint32(b[8*4+2*8:], s.Offset) |
| o.PutUint32(b[9*4+2*8:], s.Align) |
| o.PutUint32(b[10*4+2*8:], s.Reloff) |
| o.PutUint32(b[11*4+2*8:], s.Nreloc) |
| o.PutUint32(b[12*4+2*8:], uint32(s.Flags)) |
| o.PutUint32(b[13*4+2*8:], s.Reserved1) |
| o.PutUint32(b[14*4+2*8:], s.Reserved2) |
| o.PutUint32(b[15*4+2*8:], s.Reserved3) |
| a := 16*4 + 2*8 |
| return a + s.PutRelocs(b[a:], o) |
| } |
| |
| func (s *Section) PutRelocs(b []byte, o binary.ByteOrder) int { |
| a := 0 |
| for _, r := range s.Relocs { |
| var ri relocInfo |
| typ := uint32(r.Type) & (1<<4 - 1) |
| len := uint32(r.Len) & (1<<2 - 1) |
| pcrel := uint32(0) |
| if r.Pcrel { |
| pcrel = 1 |
| } |
| ext := uint32(0) |
| if r.Extern { |
| ext = 1 |
| } |
| switch { |
| case r.Scattered: |
| ri.Addr = r.Addr&(1<<24-1) | typ<<24 | len<<28 | 1<<31 | pcrel<<30 |
| ri.Symnum = r.Value |
| case o == binary.LittleEndian: |
| ri.Addr = r.Addr |
| ri.Symnum = r.Value&(1<<24-1) | pcrel<<24 | len<<25 | ext<<27 | typ<<28 |
| case o == binary.BigEndian: |
| ri.Addr = r.Addr |
| ri.Symnum = r.Value<<8 | pcrel<<7 | len<<5 | ext<<4 | typ |
| } |
| o.PutUint32(b, ri.Addr) |
| o.PutUint32(b[4:], ri.Symnum) |
| a += 8 |
| b = b[8:] |
| } |
| return a |
| } |
| |
| func putAtMost16Bytes(b []byte, n string) { |
| for i := range n { // at most 16 bytes |
| if i == 16 { |
| break |
| } |
| b[i] = n[i] |
| } |
| } |
| |
| // A Symbol is a Mach-O 32-bit or 64-bit symbol table entry. |
| type Symbol struct { |
| Name string |
| Type uint8 |
| Sect uint8 |
| Desc uint16 |
| Value uint64 |
| } |
| |
| /* |
| * Mach-O reader |
| */ |
| |
| // FormatError is returned by some operations if the data does |
| // not have the correct format for an object file. |
| type FormatError struct { |
| off int64 |
| msg string |
| } |
| |
| func formatError(off int64, format string, data ...interface{}) *FormatError { |
| return &FormatError{off, fmt.Sprintf(format, data...)} |
| } |
| |
| func (e *FormatError) Error() string { |
| return e.msg + fmt.Sprintf(" in record at byte %#x", e.off) |
| } |
| |
| func (e *FormatError) String() string { |
| return e.Error() |
| } |
| |
| // DerivedCopy returns a modified copy of the TOC, with empty loads and sections, |
| // and with the specified header type and flags. |
| func (t *FileTOC) DerivedCopy(Type HdrType, Flags HdrFlags) *FileTOC { |
| h := t.FileHeader |
| h.NCommands, h.SizeCommands, h.Type, h.Flags = 0, 0, Type, Flags |
| |
| return &FileTOC{FileHeader: h, ByteOrder: t.ByteOrder} |
| } |
| |
| // TOCSize returns the size in bytes of the object file representation |
| // of the header and Load Commands (including Segments and Sections, but |
| // not their contents) at the beginning of a Mach-O file. This typically |
| // overlaps the text segment in the object file. |
| func (t *FileTOC) TOCSize() uint32 { |
| return t.HdrSize() + t.LoadSize() |
| } |
| |
| // LoadAlign returns the required alignment of Load commands in a binary. |
| // This is used to add padding for necessary alignment. |
| func (t *FileTOC) LoadAlign() uint64 { |
| if t.Magic == Magic64 { |
| return 8 |
| } |
| return 4 |
| } |
| |
| // SymbolSize returns the size in bytes of a Symbol (Nlist32 or Nlist64) |
| func (t *FileTOC) SymbolSize() uint32 { |
| if t.Magic == Magic64 { |
| return uint32(unsafe.Sizeof(Nlist64{})) |
| } |
| return uint32(unsafe.Sizeof(Nlist32{})) |
| } |
| |
| // HdrSize returns the size in bytes of the Macho header for a given |
| // magic number (where the magic number has been appropriately byte-swapped). |
| func (t *FileTOC) HdrSize() uint32 { |
| switch t.Magic { |
| case Magic32: |
| return fileHeaderSize32 |
| case Magic64: |
| return fileHeaderSize64 |
| case MagicFat: |
| panic("MagicFat not handled yet") |
| default: |
| panic(fmt.Sprintf("Unexpected magic number 0x%x, expected Mach-O object file", t.Magic)) |
| } |
| } |
| |
| // LoadSize returns the size of all the load commands in a file's table-of contents |
| // (but not their associated data, e.g., sections and symbol tables) |
| func (t *FileTOC) LoadSize() uint32 { |
| cmdsz := uint32(0) |
| for _, l := range t.Loads { |
| s := l.LoadSize(t) |
| cmdsz += s |
| } |
| return cmdsz |
| } |
| |
| // FileSize returns the size in bytes of the header, load commands, and the |
| // in-file contents of all the segments and sections included in those |
| // load commands, accounting for their offsets within the file. |
| func (t *FileTOC) FileSize() uint64 { |
| sz := uint64(t.LoadSize()) // ought to be contained in text segment, but just in case. |
| for _, l := range t.Loads { |
| if s, ok := l.(*Segment); ok { |
| if m := s.Offset + s.Filesz; m > sz { |
| sz = m |
| } |
| } |
| } |
| return sz |
| } |
| |
| // Put writes the header and all load commands to buffer, using |
| // the byte ordering specified in FileTOC t. For sections, this |
| // writes the headers that come in-line with the segment Load commands, |
| // but does not write the reference data for those sections. |
| func (t *FileTOC) Put(buffer []byte) int { |
| next := t.FileHeader.Put(buffer, t.ByteOrder) |
| for _, l := range t.Loads { |
| if s, ok := l.(*Segment); ok { |
| switch t.Magic { |
| case Magic64: |
| next += s.Put64(buffer[next:], t.ByteOrder) |
| for i := uint32(0); i < s.Nsect; i++ { |
| c := t.Sections[i+s.Firstsect] |
| next += c.Put64(buffer[next:], t.ByteOrder) |
| } |
| case Magic32: |
| next += s.Put32(buffer[next:], t.ByteOrder) |
| for i := uint32(0); i < s.Nsect; i++ { |
| c := t.Sections[i+s.Firstsect] |
| next += c.Put32(buffer[next:], t.ByteOrder) |
| } |
| default: |
| panic(fmt.Sprintf("Unexpected magic number 0x%x", t.Magic)) |
| } |
| |
| } else { |
| next += l.Put(buffer[next:], t.ByteOrder) |
| } |
| } |
| return next |
| } |
| |
| // UncompressedSize returns the size of the segment with its sections uncompressed, ignoring |
| // its offset within the file. The returned size is rounded up to the power of two in align. |
| func (s *Segment) UncompressedSize(t *FileTOC, align uint64) uint64 { |
| sz := uint64(0) |
| for j := uint32(0); j < s.Nsect; j++ { |
| c := t.Sections[j+s.Firstsect] |
| sz += c.UncompressedSize() |
| } |
| return (sz + align - 1) & uint64(-int64(align)) |
| } |
| |
| func (s *Section) UncompressedSize() uint64 { |
| if !strings.HasPrefix(s.Name, "__z") { |
| return s.Size |
| } |
| b := make([]byte, 12) |
| n, err := s.sr.ReadAt(b, 0) |
| if err != nil { |
| panic("Malformed object file") |
| } |
| if n != len(b) { |
| return s.Size |
| } |
| if string(b[:4]) == "ZLIB" { |
| return binary.BigEndian.Uint64(b[4:12]) |
| } |
| return s.Size |
| } |
| |
| func (s *Section) PutData(b []byte) { |
| bb := b[0:s.Size] |
| n, err := s.sr.ReadAt(bb, 0) |
| if err != nil || uint64(n) != s.Size { |
| panic("Malformed object file (ReadAt error)") |
| } |
| } |
| |
| func (s *Section) PutUncompressedData(b []byte) { |
| if strings.HasPrefix(s.Name, "__z") { |
| bb := make([]byte, 12) |
| n, err := s.sr.ReadAt(bb, 0) |
| if err != nil { |
| panic("Malformed object file") |
| } |
| if n == len(bb) && string(bb[:4]) == "ZLIB" { |
| size := binary.BigEndian.Uint64(bb[4:12]) |
| // Decompress starting at b[12:] |
| r, err := zlib.NewReader(io.NewSectionReader(s, 12, int64(size)-12)) |
| if err != nil { |
| panic("Malformed object file (zlib.NewReader error)") |
| } |
| n, err := io.ReadFull(r, b[0:size]) |
| if err != nil { |
| panic("Malformed object file (ReadFull error)") |
| } |
| if uint64(n) != size { |
| panic(fmt.Sprintf("PutUncompressedData, expected to read %d bytes, instead read %d", size, n)) |
| } |
| if err := r.Close(); err != nil { |
| panic("Malformed object file (Close error)") |
| } |
| return |
| } |
| } |
| // Not compressed |
| s.PutData(b) |
| } |
| |
| func (b LoadBytes) String() string { |
| s := "[" |
| for i, a := range b { |
| if i > 0 { |
| s += " " |
| if len(b) > 48 && i >= 16 { |
| s += fmt.Sprintf("... (%d bytes)", len(b)) |
| break |
| } |
| } |
| s += fmt.Sprintf("%x", a) |
| } |
| s += "]" |
| return s |
| } |
| |
| func (b LoadBytes) Raw() []byte { return b } |
| func (b LoadBytes) Copy() LoadBytes { return LoadBytes(append([]byte{}, b...)) } |
| func (b LoadBytes) LoadSize(t *FileTOC) uint32 { return uint32(len(b)) } |
| |
| func (lc LoadCmd) Put(b []byte, o binary.ByteOrder) int { |
| panic(fmt.Sprintf("Put not implemented for %s", lc.String())) |
| } |
| |
| func (s LoadCmdBytes) String() string { |
| return s.LoadCmd.String() + ": " + s.LoadBytes.String() |
| } |
| func (s LoadCmdBytes) Copy() LoadCmdBytes { |
| return LoadCmdBytes{LoadCmd: s.LoadCmd, LoadBytes: s.LoadBytes.Copy()} |
| } |
| |
| func (s *SegmentHeader) String() string { |
| return fmt.Sprintf( |
| "Seg %s, len=0x%x, addr=0x%x, memsz=0x%x, offset=0x%x, filesz=0x%x, maxprot=0x%x, prot=0x%x, nsect=%d, flag=0x%x, firstsect=%d", |
| s.Name, s.Len, s.Addr, s.Memsz, s.Offset, s.Filesz, s.Maxprot, s.Prot, s.Nsect, s.Flag, s.Firstsect) |
| } |
| |
| func (s *Segment) String() string { |
| return fmt.Sprintf( |
| "Seg %s, len=0x%x, addr=0x%x, memsz=0x%x, offset=0x%x, filesz=0x%x, maxprot=0x%x, prot=0x%x, nsect=%d, flag=0x%x, firstsect=%d", |
| s.Name, s.Len, s.Addr, s.Memsz, s.Offset, s.Filesz, s.Maxprot, s.Prot, s.Nsect, s.Flag, s.Firstsect) |
| } |
| |
| // Data reads and returns the contents of the segment. |
| func (s *Segment) Data() ([]byte, error) { |
| dat := make([]byte, s.sr.Size()) |
| n, err := s.sr.ReadAt(dat, 0) |
| if n == len(dat) { |
| err = nil |
| } |
| return dat[0:n], err |
| } |
| |
| func (s *Segment) Copy() *Segment { |
| r := &Segment{SegmentHeader: s.SegmentHeader} |
| return r |
| } |
| func (s *Segment) CopyZeroed() *Segment { |
| r := s.Copy() |
| r.Filesz = 0 |
| r.Offset = 0 |
| r.Nsect = 0 |
| r.Firstsect = 0 |
| if s.Command() == LcSegment64 { |
| r.Len = uint32(unsafe.Sizeof(Segment64{})) |
| } else { |
| r.Len = uint32(unsafe.Sizeof(Segment32{})) |
| } |
| return r |
| } |
| |
| func (s *Segment) LoadSize(t *FileTOC) uint32 { |
| if s.Command() == LcSegment64 { |
| return uint32(unsafe.Sizeof(Segment64{})) + uint32(s.Nsect)*uint32(unsafe.Sizeof(Section64{})) |
| } |
| return uint32(unsafe.Sizeof(Segment32{})) + uint32(s.Nsect)*uint32(unsafe.Sizeof(Section32{})) |
| } |
| |
| // Open returns a new ReadSeeker reading the segment. |
| func (s *Segment) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) } |
| |
| // Data reads and returns the contents of the Mach-O section. |
| func (s *Section) Data() ([]byte, error) { |
| dat := make([]byte, s.sr.Size()) |
| n, err := s.sr.ReadAt(dat, 0) |
| if n == len(dat) { |
| err = nil |
| } |
| return dat[0:n], err |
| } |
| |
| func (s *Section) Copy() *Section { |
| return &Section{SectionHeader: s.SectionHeader} |
| } |
| |
| // Open returns a new ReadSeeker reading the Mach-O section. |
| func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) } |
| |
| // A Dylib represents a Mach-O load dynamic library command. |
| type Dylib struct { |
| DylibCmd |
| Name string |
| Time uint32 |
| CurrentVersion uint32 |
| CompatVersion uint32 |
| } |
| |
| func (s *Dylib) String() string { return "Dylib " + s.Name } |
| func (s *Dylib) Copy() *Dylib { |
| r := *s |
| return &r |
| } |
| func (s *Dylib) LoadSize(t *FileTOC) uint32 { |
| return uint32(RoundUp(uint64(unsafe.Sizeof(DylibCmd{}))+uint64(len(s.Name)), t.LoadAlign())) |
| } |
| |
| type Dylinker struct { |
| DylinkerCmd // shared by 3 commands, need the LoadCmd |
| Name string |
| } |
| |
| func (s *Dylinker) String() string { return s.DylinkerCmd.LoadCmd.String() + " " + s.Name } |
| func (s *Dylinker) Copy() *Dylinker { |
| return &Dylinker{DylinkerCmd: s.DylinkerCmd, Name: s.Name} |
| } |
| func (s *Dylinker) LoadSize(t *FileTOC) uint32 { |
| return uint32(RoundUp(uint64(unsafe.Sizeof(DylinkerCmd{}))+uint64(len(s.Name)), t.LoadAlign())) |
| } |
| |
| // A Symtab represents a Mach-O symbol table command. |
| type Symtab struct { |
| SymtabCmd |
| Syms []Symbol |
| } |
| |
| func (s *Symtab) Put(b []byte, o binary.ByteOrder) int { |
| o.PutUint32(b[0*4:], uint32(s.LoadCmd)) |
| o.PutUint32(b[1*4:], s.Len) |
| o.PutUint32(b[2*4:], s.Symoff) |
| o.PutUint32(b[3*4:], s.Nsyms) |
| o.PutUint32(b[4*4:], s.Stroff) |
| o.PutUint32(b[5*4:], s.Strsize) |
| return 6 * 4 |
| } |
| |
| func (s *Symtab) String() string { return fmt.Sprintf("Symtab %#v", s.SymtabCmd) } |
| func (s *Symtab) Copy() *Symtab { |
| return &Symtab{SymtabCmd: s.SymtabCmd, Syms: append([]Symbol{}, s.Syms...)} |
| } |
| func (s *Symtab) LoadSize(t *FileTOC) uint32 { |
| return uint32(unsafe.Sizeof(SymtabCmd{})) |
| } |
| |
| type LinkEditData struct { |
| LinkEditDataCmd |
| } |
| |
| func (s *LinkEditData) String() string { return "LinkEditData " + s.LoadCmd.String() } |
| func (s *LinkEditData) Copy() *LinkEditData { |
| return &LinkEditData{LinkEditDataCmd: s.LinkEditDataCmd} |
| } |
| func (s *LinkEditData) LoadSize(t *FileTOC) uint32 { |
| return uint32(unsafe.Sizeof(LinkEditDataCmd{})) |
| } |
| |
| type Uuid struct { |
| UuidCmd |
| } |
| |
| func (s *Uuid) String() string { |
| return fmt.Sprintf("Uuid %X-%X-%X-%X-%X", |
| s.Id[0:4], s.Id[4:6], s.Id[6:8], s.Id[8:10], s.Id[10:16]) |
| } // 8-4-4-4-12 |
| func (s *Uuid) Copy() *Uuid { |
| return &Uuid{UuidCmd: s.UuidCmd} |
| } |
| func (s *Uuid) LoadSize(t *FileTOC) uint32 { |
| return uint32(unsafe.Sizeof(UuidCmd{})) |
| } |
| func (s *Uuid) Put(b []byte, o binary.ByteOrder) int { |
| o.PutUint32(b[0*4:], uint32(s.LoadCmd)) |
| o.PutUint32(b[1*4:], s.Len) |
| copy(b[2*4:], s.Id[0:]) |
| return int(s.Len) |
| } |
| |
| type DyldInfo struct { |
| DyldInfoCmd |
| } |
| |
| func (s *DyldInfo) String() string { return "DyldInfo " + s.LoadCmd.String() } |
| func (s *DyldInfo) Copy() *DyldInfo { |
| return &DyldInfo{DyldInfoCmd: s.DyldInfoCmd} |
| } |
| func (s *DyldInfo) LoadSize(t *FileTOC) uint32 { |
| return uint32(unsafe.Sizeof(DyldInfoCmd{})) |
| } |
| |
| type EncryptionInfo struct { |
| EncryptionInfoCmd |
| } |
| |
| func (s *EncryptionInfo) String() string { return "EncryptionInfo " + s.LoadCmd.String() } |
| func (s *EncryptionInfo) Copy() *EncryptionInfo { |
| return &EncryptionInfo{EncryptionInfoCmd: s.EncryptionInfoCmd} |
| } |
| func (s *EncryptionInfo) LoadSize(t *FileTOC) uint32 { |
| return uint32(unsafe.Sizeof(EncryptionInfoCmd{})) |
| } |
| |
| // A Dysymtab represents a Mach-O dynamic symbol table command. |
| type Dysymtab struct { |
| DysymtabCmd |
| IndirectSyms []uint32 // indices into Symtab.Syms |
| } |
| |
| func (s *Dysymtab) String() string { return fmt.Sprintf("Dysymtab %#v", s.DysymtabCmd) } |
| func (s *Dysymtab) Copy() *Dysymtab { |
| return &Dysymtab{DysymtabCmd: s.DysymtabCmd, IndirectSyms: append([]uint32{}, s.IndirectSyms...)} |
| } |
| func (s *Dysymtab) LoadSize(t *FileTOC) uint32 { |
| return uint32(unsafe.Sizeof(DysymtabCmd{})) |
| } |
| |
| // A Rpath represents a Mach-O rpath command. |
| type Rpath struct { |
| LoadCmd |
| Path string |
| } |
| |
| func (s *Rpath) String() string { return "Rpath " + s.Path } |
| func (s *Rpath) Command() LoadCmd { return LcRpath } |
| func (s *Rpath) Copy() *Rpath { |
| return &Rpath{Path: s.Path} |
| } |
| func (s *Rpath) LoadSize(t *FileTOC) uint32 { |
| return uint32(RoundUp(uint64(unsafe.Sizeof(RpathCmd{}))+uint64(len(s.Path)), t.LoadAlign())) |
| } |
| |
| // Open opens the named file using os.Open and prepares it for use as a Mach-O binary. |
| func Open(name string) (*File, error) { |
| f, err := os.Open(name) |
| if err != nil { |
| return nil, err |
| } |
| ff, err := NewFile(f) |
| if err != nil { |
| f.Close() |
| return nil, err |
| } |
| ff.closer = f |
| return ff, nil |
| } |
| |
| // Close closes the File. |
| // If the File was created using NewFile directly instead of Open, |
| // Close has no effect. |
| func (f *File) Close() error { |
| var err error |
| if f.closer != nil { |
| err = f.closer.Close() |
| f.closer = nil |
| } |
| return err |
| } |
| |
| // NewFile creates a new File for accessing a Mach-O binary in an underlying reader. |
| // The Mach-O binary is expected to start at position 0 in the ReaderAt. |
| func NewFile(r io.ReaderAt) (*File, error) { |
| f := new(File) |
| sr := io.NewSectionReader(r, 0, 1<<63-1) |
| |
| // Read and decode Mach magic to determine byte order, size. |
| // Magic32 and Magic64 differ only in the bottom bit. |
| var ident [4]byte |
| if _, err := r.ReadAt(ident[0:], 0); err != nil { |
| return nil, err |
| } |
| be := binary.BigEndian.Uint32(ident[0:]) |
| le := binary.LittleEndian.Uint32(ident[0:]) |
| switch Magic32 &^ 1 { |
| case be &^ 1: |
| f.ByteOrder = binary.BigEndian |
| f.Magic = be |
| case le &^ 1: |
| f.ByteOrder = binary.LittleEndian |
| f.Magic = le |
| default: |
| return nil, formatError(0, "invalid magic number be=0x%x, le=0x%x", be, le) |
| } |
| |
| // Read entire file header. |
| if err := binary.Read(sr, f.ByteOrder, &f.FileHeader); err != nil { |
| return nil, err |
| } |
| |
| // Then load commands. |
| offset := int64(fileHeaderSize32) |
| if f.Magic == Magic64 { |
| offset = fileHeaderSize64 |
| } |
| dat := make([]byte, f.SizeCommands) |
| if _, err := r.ReadAt(dat, offset); err != nil { |
| return nil, err |
| } |
| f.Loads = make([]Load, f.NCommands) |
| bo := f.ByteOrder |
| for i := range f.Loads { |
| // Each load command begins with uint32 command and length. |
| if len(dat) < 8 { |
| return nil, formatError(offset, "command block too small, len(dat) = %d", len(dat)) |
| } |
| cmd, siz := LoadCmd(bo.Uint32(dat[0:4])), bo.Uint32(dat[4:8]) |
| if siz < 8 || siz > uint32(len(dat)) { |
| return nil, formatError(offset, "invalid command block size, len(dat)=%d, size=%d", len(dat), siz) |
| } |
| var cmddat []byte |
| cmddat, dat = dat[0:siz], dat[siz:] |
| offset += int64(siz) |
| var s *Segment |
| switch cmd { |
| default: |
| f.Loads[i] = LoadCmdBytes{LoadCmd(cmd), LoadBytes(cmddat)} |
| |
| case LcUuid: |
| var hdr UuidCmd |
| b := bytes.NewReader(cmddat) |
| if err := binary.Read(b, bo, &hdr); err != nil { |
| return nil, err |
| } |
| l := &Uuid{UuidCmd: hdr} |
| |
| f.Loads[i] = l |
| |
| case LcRpath: |
| var hdr RpathCmd |
| b := bytes.NewReader(cmddat) |
| if err := binary.Read(b, bo, &hdr); err != nil { |
| return nil, err |
| } |
| l := &Rpath{LoadCmd: hdr.LoadCmd} |
| if hdr.Path >= uint32(len(cmddat)) { |
| return nil, formatError(offset, "invalid path in rpath command, len(cmddat)=%d, hdr.Path=%d", len(cmddat), hdr.Path) |
| } |
| l.Path = cstring(cmddat[hdr.Path:]) |
| f.Loads[i] = l |
| |
| case LcLoadDylinker, LcIdDylinker, LcDyldEnvironment: |
| var hdr DylinkerCmd |
| b := bytes.NewReader(cmddat) |
| if err := binary.Read(b, bo, &hdr); err != nil { |
| return nil, err |
| } |
| l := new(Dylinker) |
| if hdr.Name >= uint32(len(cmddat)) { |
| return nil, formatError(offset, "invalid name in dynamic linker command, hdr.Name=%d, len(cmddat)=%d", hdr.Name, len(cmddat)) |
| } |
| l.Name = cstring(cmddat[hdr.Name:]) |
| l.DylinkerCmd = hdr |
| f.Loads[i] = l |
| |
| case LcDylib: |
| var hdr DylibCmd |
| b := bytes.NewReader(cmddat) |
| if err := binary.Read(b, bo, &hdr); err != nil { |
| return nil, err |
| } |
| l := new(Dylib) |
| if hdr.Name >= uint32(len(cmddat)) { |
| return nil, formatError(offset, "invalid name in dynamic library command, hdr.Name=%d, len(cmddat)=%d", hdr.Name, len(cmddat)) |
| } |
| l.Name = cstring(cmddat[hdr.Name:]) |
| l.Time = hdr.Time |
| l.CurrentVersion = hdr.CurrentVersion |
| l.CompatVersion = hdr.CompatVersion |
| f.Loads[i] = l |
| |
| case LcSymtab: |
| var hdr SymtabCmd |
| b := bytes.NewReader(cmddat) |
| if err := binary.Read(b, bo, &hdr); err != nil { |
| return nil, err |
| } |
| strtab := make([]byte, hdr.Strsize) |
| if _, err := r.ReadAt(strtab, int64(hdr.Stroff)); err != nil { |
| return nil, err |
| } |
| var symsz int |
| if f.Magic == Magic64 { |
| symsz = 16 |
| } else { |
| symsz = 12 |
| } |
| symdat := make([]byte, int(hdr.Nsyms)*symsz) |
| if _, err := r.ReadAt(symdat, int64(hdr.Symoff)); err != nil { |
| return nil, err |
| } |
| st, err := f.parseSymtab(symdat, strtab, cmddat, &hdr, offset) |
| st.SymtabCmd = hdr |
| if err != nil { |
| return nil, err |
| } |
| f.Loads[i] = st |
| f.Symtab = st |
| |
| case LcDysymtab: |
| var hdr DysymtabCmd |
| b := bytes.NewReader(cmddat) |
| if err := binary.Read(b, bo, &hdr); err != nil { |
| return nil, err |
| } |
| dat := make([]byte, hdr.Nindirectsyms*4) |
| if _, err := r.ReadAt(dat, int64(hdr.Indirectsymoff)); err != nil { |
| return nil, err |
| } |
| x := make([]uint32, hdr.Nindirectsyms) |
| if err := binary.Read(bytes.NewReader(dat), bo, x); err != nil { |
| return nil, err |
| } |
| st := new(Dysymtab) |
| st.DysymtabCmd = hdr |
| st.IndirectSyms = x |
| f.Loads[i] = st |
| f.Dysymtab = st |
| |
| case LcSegment: |
| var seg32 Segment32 |
| b := bytes.NewReader(cmddat) |
| if err := binary.Read(b, bo, &seg32); err != nil { |
| return nil, err |
| } |
| s = new(Segment) |
| s.LoadCmd = cmd |
| s.Len = siz |
| s.Name = cstring(seg32.Name[0:]) |
| s.Addr = uint64(seg32.Addr) |
| s.Memsz = uint64(seg32.Memsz) |
| s.Offset = uint64(seg32.Offset) |
| s.Filesz = uint64(seg32.Filesz) |
| s.Maxprot = seg32.Maxprot |
| s.Prot = seg32.Prot |
| s.Nsect = seg32.Nsect |
| s.Flag = seg32.Flag |
| s.Firstsect = uint32(len(f.Sections)) |
| f.Loads[i] = s |
| for i := 0; i < int(s.Nsect); i++ { |
| var sh32 Section32 |
| if err := binary.Read(b, bo, &sh32); err != nil { |
| return nil, err |
| } |
| sh := new(Section) |
| sh.Name = cstring(sh32.Name[0:]) |
| sh.Seg = cstring(sh32.Seg[0:]) |
| sh.Addr = uint64(sh32.Addr) |
| sh.Size = uint64(sh32.Size) |
| sh.Offset = sh32.Offset |
| sh.Align = sh32.Align |
| sh.Reloff = sh32.Reloff |
| sh.Nreloc = sh32.Nreloc |
| sh.Flags = sh32.Flags |
| sh.Reserved1 = sh32.Reserve1 |
| sh.Reserved2 = sh32.Reserve2 |
| if err := f.pushSection(sh, r); err != nil { |
| return nil, err |
| } |
| } |
| |
| case LcSegment64: |
| var seg64 Segment64 |
| b := bytes.NewReader(cmddat) |
| if err := binary.Read(b, bo, &seg64); err != nil { |
| return nil, err |
| } |
| s = new(Segment) |
| s.LoadCmd = cmd |
| s.Len = siz |
| s.Name = cstring(seg64.Name[0:]) |
| s.Addr = seg64.Addr |
| s.Memsz = seg64.Memsz |
| s.Offset = seg64.Offset |
| s.Filesz = seg64.Filesz |
| s.Maxprot = seg64.Maxprot |
| s.Prot = seg64.Prot |
| s.Nsect = seg64.Nsect |
| s.Flag = seg64.Flag |
| s.Firstsect = uint32(len(f.Sections)) |
| f.Loads[i] = s |
| for i := 0; i < int(s.Nsect); i++ { |
| var sh64 Section64 |
| if err := binary.Read(b, bo, &sh64); err != nil { |
| return nil, err |
| } |
| sh := new(Section) |
| sh.Name = cstring(sh64.Name[0:]) |
| sh.Seg = cstring(sh64.Seg[0:]) |
| sh.Addr = sh64.Addr |
| sh.Size = sh64.Size |
| sh.Offset = sh64.Offset |
| sh.Align = sh64.Align |
| sh.Reloff = sh64.Reloff |
| sh.Nreloc = sh64.Nreloc |
| sh.Flags = sh64.Flags |
| sh.Reserved1 = sh64.Reserve1 |
| sh.Reserved2 = sh64.Reserve2 |
| sh.Reserved3 = sh64.Reserve3 |
| if err := f.pushSection(sh, r); err != nil { |
| return nil, err |
| } |
| } |
| |
| case LcCodeSignature, LcSegmentSplitInfo, LcFunctionStarts, |
| LcDataInCode, LcDylibCodeSignDrs: |
| var hdr LinkEditDataCmd |
| b := bytes.NewReader(cmddat) |
| |
| if err := binary.Read(b, bo, &hdr); err != nil { |
| return nil, err |
| } |
| l := new(LinkEditData) |
| |
| l.LinkEditDataCmd = hdr |
| f.Loads[i] = l |
| |
| case LcEncryptionInfo, LcEncryptionInfo64: |
| var hdr EncryptionInfoCmd |
| b := bytes.NewReader(cmddat) |
| |
| if err := binary.Read(b, bo, &hdr); err != nil { |
| return nil, err |
| } |
| l := new(EncryptionInfo) |
| |
| l.EncryptionInfoCmd = hdr |
| f.Loads[i] = l |
| |
| case LcDyldInfo, LcDyldInfoOnly: |
| var hdr DyldInfoCmd |
| b := bytes.NewReader(cmddat) |
| |
| if err := binary.Read(b, bo, &hdr); err != nil { |
| return nil, err |
| } |
| l := new(DyldInfo) |
| |
| l.DyldInfoCmd = hdr |
| f.Loads[i] = l |
| } |
| if s != nil { |
| s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Filesz)) |
| s.ReaderAt = s.sr |
| } |
| if f.Loads[i].LoadSize(&f.FileTOC) != siz { |
| fmt.Printf("Oops, actual size was %d, calculated was %d, load was %s\n", siz, f.Loads[i].LoadSize(&f.FileTOC), f.Loads[i].String()) |
| panic("oops") |
| } |
| } |
| return f, nil |
| } |
| |
| func (f *File) parseSymtab(symdat, strtab, cmddat []byte, hdr *SymtabCmd, offset int64) (*Symtab, error) { |
| bo := f.ByteOrder |
| symtab := make([]Symbol, hdr.Nsyms) |
| b := bytes.NewReader(symdat) |
| for i := range symtab { |
| var n Nlist64 |
| if f.Magic == Magic64 { |
| if err := binary.Read(b, bo, &n); err != nil { |
| return nil, err |
| } |
| } else { |
| var n32 Nlist32 |
| if err := binary.Read(b, bo, &n32); err != nil { |
| return nil, err |
| } |
| n.Name = n32.Name |
| n.Type = n32.Type |
| n.Sect = n32.Sect |
| n.Desc = n32.Desc |
| n.Value = uint64(n32.Value) |
| } |
| sym := &symtab[i] |
| if n.Name >= uint32(len(strtab)) { |
| return nil, formatError(offset, "invalid name in symbol table, n.Name=%d, len(strtab)=%d", n.Name, len(strtab)) |
| } |
| sym.Name = cstring(strtab[n.Name:]) |
| sym.Type = n.Type |
| sym.Sect = n.Sect |
| sym.Desc = n.Desc |
| sym.Value = n.Value |
| } |
| st := new(Symtab) |
| st.Syms = symtab |
| return st, nil |
| } |
| |
| type relocInfo struct { |
| Addr uint32 |
| Symnum uint32 |
| } |
| |
| func (f *File) pushSection(sh *Section, r io.ReaderAt) error { |
| f.Sections = append(f.Sections, sh) |
| sh.sr = io.NewSectionReader(r, int64(sh.Offset), int64(sh.Size)) |
| sh.ReaderAt = sh.sr |
| |
| if sh.Nreloc > 0 { |
| reldat := make([]byte, int(sh.Nreloc)*8) |
| if _, err := r.ReadAt(reldat, int64(sh.Reloff)); err != nil { |
| return err |
| } |
| b := bytes.NewReader(reldat) |
| |
| bo := f.ByteOrder |
| |
| sh.Relocs = make([]Reloc, sh.Nreloc) |
| for i := range sh.Relocs { |
| rel := &sh.Relocs[i] |
| |
| var ri relocInfo |
| if err := binary.Read(b, bo, &ri); err != nil { |
| return err |
| } |
| |
| if ri.Addr&(1<<31) != 0 { // scattered |
| rel.Addr = ri.Addr & (1<<24 - 1) |
| rel.Type = uint8((ri.Addr >> 24) & (1<<4 - 1)) |
| rel.Len = uint8((ri.Addr >> 28) & (1<<2 - 1)) |
| rel.Pcrel = ri.Addr&(1<<30) != 0 |
| rel.Value = ri.Symnum |
| rel.Scattered = true |
| } else { |
| switch bo { |
| case binary.LittleEndian: |
| rel.Addr = ri.Addr |
| rel.Value = ri.Symnum & (1<<24 - 1) |
| rel.Pcrel = ri.Symnum&(1<<24) != 0 |
| rel.Len = uint8((ri.Symnum >> 25) & (1<<2 - 1)) |
| rel.Extern = ri.Symnum&(1<<27) != 0 |
| rel.Type = uint8((ri.Symnum >> 28) & (1<<4 - 1)) |
| case binary.BigEndian: |
| rel.Addr = ri.Addr |
| rel.Value = ri.Symnum >> 8 |
| rel.Pcrel = ri.Symnum&(1<<7) != 0 |
| rel.Len = uint8((ri.Symnum >> 5) & (1<<2 - 1)) |
| rel.Extern = ri.Symnum&(1<<4) != 0 |
| rel.Type = uint8(ri.Symnum & (1<<4 - 1)) |
| default: |
| panic("unreachable") |
| } |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| func cstring(b []byte) string { |
| i := bytes.IndexByte(b, 0) |
| if i == -1 { |
| i = len(b) |
| } |
| return string(b[0:i]) |
| } |
| |
| // Segment returns the first Segment with the given name, or nil if no such segment exists. |
| func (f *File) Segment(name string) *Segment { |
| for _, l := range f.Loads { |
| if s, ok := l.(*Segment); ok && s.Name == name { |
| return s |
| } |
| } |
| return nil |
| } |
| |
| // Section returns the first section with the given name, or nil if no such |
| // section exists. |
| func (f *File) Section(name string) *Section { |
| for _, s := range f.Sections { |
| if s.Name == name { |
| return s |
| } |
| } |
| return nil |
| } |
| |
| // DWARF returns the DWARF debug information for the Mach-O file. |
| func (f *File) DWARF() (*dwarf.Data, error) { |
| dwarfSuffix := func(s *Section) string { |
| switch { |
| case strings.HasPrefix(s.Name, "__debug_"): |
| return s.Name[8:] |
| case strings.HasPrefix(s.Name, "__zdebug_"): |
| return s.Name[9:] |
| default: |
| return "" |
| } |
| |
| } |
| sectionData := func(s *Section) ([]byte, error) { |
| b, err := s.Data() |
| if err != nil && uint64(len(b)) < s.Size { |
| return nil, err |
| } |
| |
| if len(b) >= 12 && string(b[:4]) == "ZLIB" { |
| dlen := binary.BigEndian.Uint64(b[4:12]) |
| dbuf := make([]byte, dlen) |
| r, err := zlib.NewReader(bytes.NewBuffer(b[12:])) |
| if err != nil { |
| return nil, err |
| } |
| if _, err := io.ReadFull(r, dbuf); err != nil { |
| return nil, err |
| } |
| if err := r.Close(); err != nil { |
| return nil, err |
| } |
| b = dbuf |
| } |
| return b, nil |
| } |
| |
| // There are many other DWARF sections, but these |
| // are the ones the debug/dwarf package uses. |
| // Don't bother loading others. |
| var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil} |
| for _, s := range f.Sections { |
| suffix := dwarfSuffix(s) |
| if suffix == "" { |
| continue |
| } |
| if _, ok := dat[suffix]; !ok { |
| continue |
| } |
| b, err := sectionData(s) |
| if err != nil { |
| return nil, err |
| } |
| dat[suffix] = b |
| } |
| |
| d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"]) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Look for DWARF4 .debug_types sections. |
| for i, s := range f.Sections { |
| suffix := dwarfSuffix(s) |
| if suffix != "types" { |
| continue |
| } |
| |
| b, err := sectionData(s) |
| if err != nil { |
| return nil, err |
| } |
| |
| err = d.AddTypes(fmt.Sprintf("types-%d", i), b) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| return d, nil |
| } |
| |
| // ImportedSymbols returns the names of all symbols |
| // referred to by the binary f that are expected to be |
| // satisfied by other libraries at dynamic load time. |
| func (f *File) ImportedSymbols() ([]string, error) { |
| if f.Dysymtab == nil || f.Symtab == nil { |
| return nil, formatError(0, "missing symbol table, f.Dsymtab=%v, f.Symtab=%v", f.Dysymtab, f.Symtab) |
| } |
| |
| st := f.Symtab |
| dt := f.Dysymtab |
| var all []string |
| for _, s := range st.Syms[dt.Iundefsym : dt.Iundefsym+dt.Nundefsym] { |
| all = append(all, s.Name) |
| } |
| return all, nil |
| } |
| |
| // ImportedLibraries returns the paths of all libraries |
| // referred to by the binary f that are expected to be |
| // linked with the binary at dynamic link time. |
| func (f *File) ImportedLibraries() ([]string, error) { |
| var all []string |
| for _, l := range f.Loads { |
| if lib, ok := l.(*Dylib); ok { |
| all = append(all, lib.Name) |
| } |
| } |
| return all, nil |
| } |
| |
| func RoundUp(x, align uint64) uint64 { |
| return uint64((x + align - 1) & -align) |
| } |