func openMacho(r *os.File) (rawFile, error) { f, err := macho.NewFile(r) if err != nil { return nil, err } return &machoFile{f}, nil }
func openDebugFile(r io.ReaderAt) (debugFile, error) { f, err := macho.NewFile(r) if err != nil { return nil, err } return &file{f}, nil }
// zipExeReaderMacho treats the file as a Mach-O binary // (Mac OS X / Darwin executable) and attempts to find a zip archive. func zipExeReaderMacho(rda io.ReaderAt, size int64) (*zip.Reader, error) { file, err := macho.NewFile(rda) if err != nil { return nil, err } var max int64 for _, load := range file.Loads { seg, ok := load.(*macho.Segment) if ok { // Check if the segment contains a zip file if zfile, err := zip.NewReader(seg, int64(seg.Filesz)); err == nil { return zfile, nil } // Otherwise move end of file pointer end := int64(seg.Offset + seg.Filesz) if end > max { max = end } } } // No zip file within binary, try appended to end section := io.NewSectionReader(rda, max, size-max) return zip.NewReader(section, section.Size()) }
func newSectionReader(rf io.ReaderAt) (ret sectionReader, err error) { ret.macho, err = macho.NewFile(rf) if err != nil { ret.elf, err = elf.NewFile(rf) } return }
// The Go build ID is stored at the beginning of the Mach-O __text segment. // The caller has already opened filename, to get f, and read a few kB out, in data. // Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount // of other junk placed in the file ahead of the main text. func readMachoGoBuildID(filename string, f *os.File, data []byte) (buildid string, err error) { // If the data we want has already been read, don't worry about Mach-O parsing. // This is both an optimization and a hedge against the Mach-O parsing failing // in the future due to, for example, the name of the __text section changing. if b, err := readRawGoBuildID(filename, data); b != "" && err == nil { return b, err } mf, err := macho.NewFile(f) if err != nil { return "", &os.PathError{Path: filename, Op: "parse", Err: err} } sect := mf.Section("__text") if sect == nil { // Every binary has a __text section. Something is wrong. return "", &os.PathError{Path: filename, Op: "parse", Err: fmt.Errorf("cannot find __text section")} } // It should be in the first few bytes, but read a lot just in case, // especially given our past problems on OS X with the build ID moving. // There shouldn't be much difference between reading 4kB and 32kB: // the hard part is getting to the data, not transferring it. n := sect.Size if n > uint64(BuildIDReadSize) { n = uint64(BuildIDReadSize) } buf := make([]byte, n) if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil { return "", err } return readRawGoBuildID(filename, buf) }
func Fuzz(data []byte) int { f, err := macho.NewFile(bytes.NewReader(data)) if err != nil { if f != nil { panic("file is not nil on error") } return 0 } defer f.Close() f.ImportedLibraries() f.ImportedSymbols() f.Section(".text") f.Segment("macho") dw, err := f.DWARF() if err != nil { if dw != nil { panic("dwarf is not nil on error") } return 1 } dr := dw.Reader() for { e, _ := dr.Next() if e == nil { break } } return 2 }
func NewMachOLoader(r io.ReaderAt) (models.Loader, error) { file, err := macho.NewFile(r) if err != nil { return nil, err } var bits int switch file.Magic { case macho.Magic32: bits = 32 case macho.Magic64: bits = 64 default: return nil, errors.New("Unknown ELF class.") } machineName, ok := machoCpuMap[file.Cpu] if !ok { return nil, fmt.Errorf("Unsupported CPU: %s", file.Cpu) } entry, err := findEntry(file, bits) if err != nil { return nil, err } return &MachOLoader{ LoaderHeader: LoaderHeader{ arch: machineName, bits: bits, os: "darwin", entry: entry, }, file: file, }, nil }
func machoSymbols(f *os.File) (syms []Sym, goarch string) { p, err := macho.NewFile(f) if err != nil { errorf("parsing %s: %v", f.Name(), err) return } if p.Symtab == nil { errorf("%s: no symbol table", f.Name()) return } switch p.Cpu { case macho.Cpu386: goarch = "386" case macho.CpuAmd64: goarch = "amd64" case macho.CpuArm: goarch = "arm" } // Build sorted list of addresses of all symbols. // We infer the size of a symbol by looking at where the next symbol begins. var addrs []uint64 for _, s := range p.Symtab.Syms { addrs = append(addrs, s.Value) } sort.Sort(uint64s(addrs)) for _, s := range p.Symtab.Syms { sym := Sym{Name: s.Name, Addr: s.Value, Code: '?'} i := sort.Search(len(addrs), func(x int) bool { return addrs[x] > s.Value }) if i < len(addrs) { sym.Size = int64(addrs[i] - s.Value) } if s.Sect == 0 { sym.Code = 'U' } else if int(s.Sect) <= len(p.Sections) { sect := p.Sections[s.Sect-1] switch sect.Seg { case "__TEXT": sym.Code = 'R' case "__DATA": sym.Code = 'D' } switch sect.Seg + " " + sect.Name { case "__TEXT __text": sym.Code = 'T' case "__DATA __bss", "__DATA __noptrbss": sym.Code = 'B' } } syms = append(syms, sym) } return }
func NewMachOLoader(r io.ReaderAt, archHint string) (models.Loader, error) { var ( file *macho.File fatFile *macho.FatFile err error ) magic := getMagic(r) if bytes.Equal(magic, fatMagic) { fatFile, err = macho.NewFatFile(r) if fatFile != nil { for _, arch := range fatFile.Arches { if machineName, ok := machoCpuMap[arch.Cpu]; ok { if machineName == archHint || archHint == "any" { file = arch.File break } } } if file == nil { return nil, fmt.Errorf("Could not find Mach-O fat binary entry for arch '%s'.", archHint) } } } else { file, err = macho.NewFile(r) } if err != nil { return nil, err } var bits int switch file.Magic { case macho.Magic32: bits = 32 case macho.Magic64: bits = 64 default: return nil, errors.New("Unknown ELF class.") } machineName, ok := machoCpuMap[file.Cpu] if !ok { return nil, fmt.Errorf("Unsupported CPU: %s", file.Cpu) } entry, err := findEntry(file, bits) if err != nil { return nil, err } return &MachOLoader{ LoaderHeader: LoaderHeader{ arch: machineName, bits: bits, os: "darwin", entry: entry, }, file: file, }, nil }
func loadTables(f *os.File) (textStart uint64, symtab, pclntab []byte, err error) { if obj, err := elf.NewFile(f); err == nil { if sect := obj.Section(".text"); sect != nil { textStart = sect.Addr } if sect := obj.Section(".gosymtab"); sect != nil { if symtab, err = sect.Data(); err != nil { return 0, nil, nil, err } } if sect := obj.Section(".gopclntab"); sect != nil { if pclntab, err = sect.Data(); err != nil { return 0, nil, nil, err } } return textStart, symtab, pclntab, nil } if obj, err := macho.NewFile(f); err == nil { if sect := obj.Section("__text"); sect != nil { textStart = sect.Addr } if sect := obj.Section("__gosymtab"); sect != nil { if symtab, err = sect.Data(); err != nil { return 0, nil, nil, err } } if sect := obj.Section("__gopclntab"); sect != nil { if pclntab, err = sect.Data(); err != nil { return 0, nil, nil, err } } return textStart, symtab, pclntab, nil } if obj, err := pe.NewFile(f); err == nil { if sect := obj.Section(".text"); sect != nil { textStart = uint64(sect.VirtualAddress) } if sect := obj.Section(".gosymtab"); sect != nil { if symtab, err = sect.Data(); err != nil { return 0, nil, nil, err } } if sect := obj.Section(".gopclntab"); sect != nil { if pclntab, err = sect.Data(); err != nil { return 0, nil, nil, err } } return textStart, symtab, pclntab, nil } return 0, nil, nil, fmt.Errorf("unrecognized binary format") }
func NewFile(r io.ReaderAt) (binaryx.File, error) { elfBinary, err := elf.NewFile(r) if err == nil { return newFile(&elfx.File{elfBinary}) } machoBinary, err := macho.NewFile(r) if err == nil { return newFile(&machox.File{machoBinary}) } peBinary, err := pe.NewFile(r) if err == nil { return newFile(&pex.File{peBinary}) } return nil, err }
func grabDebugLineSection(p string, t *testing.T) []byte { f, err := os.Open(p) if err != nil { t.Fatal(err) } defer f.Close() ef, err := elf.NewFile(f) if err == nil { data, _ := ef.Section(".debug_line").Data() return data } mf, _ := macho.NewFile(f) data, _ := mf.Section("__debug_line").Data() return data }
func NewDWARFHelper(id string, r io.ReaderAt) (*DWARFHelper, error) { // TODO: detect file format.. if f, err := macho.NewFile(r); err != nil { return nil, err } else { defer f.Close() if df, err := f.DWARF(); err != nil { for i := range f.Sections { log4go.Debug(f.Sections[i]) } return nil, err } else { var ret DWARFHelper ret.df = df ret.id = id return &ret, nil } } }
func (fq *findQueue) worker() { for fp := range fq.queue { blacklisted := false for _, re := range blacklistRegexps { blacklisted = blacklisted || re.MatchString(fp) } if blacklisted { continue } f, err := os.Open(fp) if err != nil { log.Printf("%s: %v", fp, err) continue } fatFile, err := macho.NewFatFile(f) if err == nil { // The file is fat, so dump its architectures. for _, fatArch := range fatFile.Arches { fq.dumpMachOFile(fp, fatArch.File) } fatFile.Close() } else if err == macho.ErrNotFat { // The file isn't fat but may still be MachO. thinFile, err := macho.NewFile(f) if err != nil { log.Printf("%s: %v", fp, err) continue } fq.dumpMachOFile(fp, thinFile) thinFile.Close() } else { f.Close() } } }
// machoCombineDwarf merges dwarf info generated by dsymutil into a macho executable. // With internal linking, DWARF is embedded into the executable, this lets us do the // same for external linking. // inexe is the path to the executable with no DWARF. It must have enough room in the macho // header to add the DWARF sections. (Use ld's -headerpad option) // dsym is the path to the macho file containing DWARF from dsymutil. // outexe is the path where the combined executable should be saved. func machoCombineDwarf(inexe, dsym, outexe string) error { exef, err := os.Open(inexe) if err != nil { return err } dwarff, err := os.Open(dsym) if err != nil { return err } outf, err := os.Create(outexe) if err != nil { return err } outf.Chmod(0755) exem, err := macho.NewFile(exef) if err != nil { return err } dwarfm, err := macho.NewFile(dwarff) if err != nil { return err } // The string table needs to be the last thing in the file // for code signing to work. So we'll need to move the // linkedit section, but all the others can be copied directly. linkseg = exem.Segment("__LINKEDIT") if linkseg == nil { return fmt.Errorf("missing __LINKEDIT segment") } if _, err = exef.Seek(0, 0); err != nil { return err } if _, err := io.CopyN(outf, exef, int64(linkseg.Offset)); err != nil { return err } realdwarf = dwarfm.Segment("__DWARF") if realdwarf == nil { return fmt.Errorf("missing __DWARF segment") } // Now copy the dwarf data into the output. maxalign := uint32(dwarfMinAlign) // for _, sect := range dwarfm.Sections { if sect.Align > maxalign { maxalign = sect.Align } } dwarfstart = machoCalcStart(realdwarf.Offset, linkseg.Offset, maxalign) if _, err = outf.Seek(dwarfstart, 0); err != nil { return err } if _, err = dwarff.Seek(int64(realdwarf.Offset), 0); err != nil { return err } if _, err := io.CopyN(outf, dwarff, int64(realdwarf.Filesz)); err != nil { return err } // And finally the linkedit section. if _, err = exef.Seek(int64(linkseg.Offset), 0); err != nil { return err } linkstart = machoCalcStart(linkseg.Offset, uint64(dwarfstart)+realdwarf.Filesz, pageAlign) linkoffset = uint32(linkstart - int64(linkseg.Offset)) if _, err = outf.Seek(linkstart, 0); err != nil { return err } if _, err := io.Copy(outf, exef); err != nil { return err } // Now we need to update the headers. cmdOffset := unsafe.Sizeof(exem.FileHeader) is64bit := exem.Magic == macho.Magic64 if is64bit { // mach_header_64 has one extra uint32. cmdOffset += unsafe.Sizeof(exem.Magic) } textsect := exem.Section("__text") if linkseg == nil { return fmt.Errorf("missing __text section") } dwarfCmdOffset := int64(cmdOffset) + int64(exem.FileHeader.Cmdsz) availablePadding := int64(textsect.Offset) - dwarfCmdOffset if availablePadding < int64(realdwarf.Len) { return fmt.Errorf("No room to add dwarf info. Need at least %d padding bytes, found %d", realdwarf.Len, availablePadding) } // First, copy the dwarf load command into the header if _, err = outf.Seek(dwarfCmdOffset, 0); err != nil { return err } if _, err := io.CopyN(outf, bytes.NewReader(realdwarf.Raw()), int64(realdwarf.Len)); err != nil { return err } if _, err = outf.Seek(int64(unsafe.Offsetof(exem.FileHeader.Ncmd)), 0); err != nil { return err } if err = binary.Write(outf, exem.ByteOrder, exem.Ncmd+1); err != nil { return err } if err = binary.Write(outf, exem.ByteOrder, exem.Cmdsz+realdwarf.Len); err != nil { return err } reader := loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder} for i := uint32(0); i < exem.Ncmd; i++ { cmd, err := reader.Next() if err != nil { return err } switch cmd.Cmd { case macho.LoadCmdSegment64: err = machoUpdateSegment(reader, &macho.Segment64{}, &macho.Section64{}) case macho.LoadCmdSegment: err = machoUpdateSegment(reader, &macho.Segment32{}, &macho.Section32{}) case LC_DYLD_INFO, LC_DYLD_INFO_ONLY: err = machoUpdateLoadCommand(reader, &dyldInfoCmd{}, "RebaseOff", "BindOff", "WeakBindOff", "LazyBindOff", "ExportOff") case macho.LoadCmdSymtab: err = machoUpdateLoadCommand(reader, &macho.SymtabCmd{}, "Symoff", "Stroff") case macho.LoadCmdDysymtab: err = machoUpdateLoadCommand(reader, &macho.DysymtabCmd{}, "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff") case LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS: err = machoUpdateLoadCommand(reader, &linkEditDataCmd{}, "DataOff") case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64: err = machoUpdateLoadCommand(reader, &encryptionInfoCmd{}, "CryptOff") case macho.LoadCmdDylib, macho.LoadCmdThread, macho.LoadCmdUnixThread, LC_PREBOUND_DYLIB, LC_UUID, LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS, LC_SOURCE_VERSION, LC_MAIN, LC_LOAD_DYLINKER, LC_LOAD_WEAK_DYLIB, LC_REEXPORT_DYLIB, LC_RPATH, LC_ID_DYLIB: // Nothing to update default: err = fmt.Errorf("Unknown load command 0x%x (%s)\n", int(cmd.Cmd), cmd.Cmd) } if err != nil { return err } } return machoUpdateDwarfHeader(&reader) }
// 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 }
func loadTables(f *os.File) (textStart uint64, textData, symtab, pclntab []byte, err error) { if obj, err := elf.NewFile(f); err == nil { if sect := obj.Section(".text"); sect != nil { textStart = sect.Addr textData, _ = sect.Data() } if sect := obj.Section(".gosymtab"); sect != nil { if symtab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } if sect := obj.Section(".gopclntab"); sect != nil { if pclntab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } return textStart, textData, symtab, pclntab, nil } if obj, err := macho.NewFile(f); err == nil { if sect := obj.Section("__text"); sect != nil { textStart = sect.Addr textData, _ = sect.Data() } if sect := obj.Section("__gosymtab"); sect != nil { if symtab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } if sect := obj.Section("__gopclntab"); sect != nil { if pclntab, err = sect.Data(); err != nil { return 0, nil, nil, nil, err } } return textStart, textData, symtab, pclntab, nil } if obj, err := pe.NewFile(f); err == nil { var imageBase uint64 switch oh := obj.OptionalHeader.(type) { case *pe.OptionalHeader32: imageBase = uint64(oh.ImageBase) case *pe.OptionalHeader64: imageBase = oh.ImageBase default: return 0, nil, nil, nil, fmt.Errorf("pe file format not recognized") } if sect := obj.Section(".text"); sect != nil { textStart = imageBase + uint64(sect.VirtualAddress) textData, _ = sect.Data() } if pclntab, err = loadPETable(obj, "pclntab", "epclntab"); err != nil { return 0, nil, nil, nil, err } if symtab, err = loadPETable(obj, "symtab", "esymtab"); err != nil { return 0, nil, nil, nil, err } return textStart, textData, symtab, pclntab, nil } if obj, err := plan9obj.NewFile(f); err == nil { sym, err := findPlan9Symbol(obj, "text") if err != nil { return 0, nil, nil, nil, err } textStart = sym.Value if sect := obj.Section("text"); sect != nil { textData, _ = sect.Data() } if pclntab, err = loadPlan9Table(obj, "pclntab", "epclntab"); err != nil { return 0, nil, nil, nil, err } if symtab, err = loadPlan9Table(obj, "symtab", "esymtab"); err != nil { return 0, nil, nil, nil, err } return textStart, textData, symtab, pclntab, nil } return 0, nil, nil, nil, fmt.Errorf("unrecognized binary format") }