func (f *File) ClassesForImage(imageNames ...string) error {
	var mask uint64 = (1 << 40) - 1  
	var images []*CacheImage
	if len(imageNames) > 0 && len(imageNames[0]) > 0 {
		for _, imageName := range imageNames {
			images = append(images, f.Image(imageName))
		}
	} else {
		images = f.Images
	}
	for _, image := range images {
		m, err := image.GetPartialMacho()
		if err != nil {
			return errors.Wrapf(err, "failed get image %s as MachO", image.Name)
		}
		image.ObjC.ClassRefs = make(map[uint64]*objc.Class)
		for _, secName := range []string{"__objc_classrefs", "__objc_superrefs"} {
			sec := m.Section("__DATA", secName)
			if sec != nil {
				r := io.NewSectionReader(f.r[f.UUID], int64(sec.Offset), int64(sec.Size))
				classPtrs := make([]uint64, sec.Size/8)
				if err := binary.Read(r, f.ByteOrder, &classPtrs); err != nil {
					return err
				}
				for idx, ptr := range classPtrs {
					classPtrs[idx] = ptr & mask  
				}
				for idx, ptr := range classPtrs {
					c, err := f.GetObjCClass(ptr)
					if err != nil {
						return err
					}
					image.ObjC.ClassRefs[ptr] = c
					if len(image.ObjC.ClassRefs[ptr].Name) > 0 {
						f.AddressToSymbol[sec.Addr+uint64(idx*8)] = fmt.Sprintf("class_%s", image.ObjC.ClassRefs[ptr].Name)
						if sym, ok := f.AddressToSymbol[ptr]; ok {
							if len(sym) < len(image.ObjC.ClassRefs[ptr].Name) {
								f.AddressToSymbol[ptr] = image.ObjC.ClassRefs[ptr].Name
							}
						} else {
							f.AddressToSymbol[ptr] = image.ObjC.ClassRefs[ptr].Name
						}
					}
				}
			}
		}
		m.Close()
	}
	return nil
}
// GetAllHeaderRO dumps the header_ro from the optimized string hash
func (f *File) GetAllHeaderRO(print bool) (map[string]uint64, error) {
shash, err := f.getHeaderRoStringHash()
if err != nil {
return nil, errors.Wrapf(err, "failed read header_ro objc_stringhash_t")
}
if print {
f.dumpHeaderROOffsets(shash.Offsets, shash.FileOffset)
}
return f.offsetsToMap(shash.Offsets, shash.FileOffset), nil
}
// GetAllHeaderRW dumps the header_ro from the optimized string hash
func (f *File) GetAllHeaderRW(print bool) (map[string]uint64, error) {
shash, err := f.getHeaderRwStringHash()
if err != nil {
return nil, errors.Wrapf(err, "failed read header_rw objc_stringhash_t")
}
if print {
f.dumpHeaderROOffsets(shash.Offsets, shash.FileOffset)
}
return f.offsetsToMap(shash.Offsets, shash.FileOffset), nil
}
ClassesForImage returns all of the Objective-C classes for a given image
package dyld
import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"sort"
	"strings"
	"github.com/apex/log"
	"github.com/blacktop/go-macho"
	"github.com/blacktop/go-macho/types"
	"github.com/blacktop/go-macho/types/objc"
	"github.com/pkg/errors"
)
type optFlags uint32
const (
	isProduction              optFlags = (1 << 0)  
	noMissingWeakSuperclasses optFlags = (1 << 1)  
)
type objcInfo struct {
	Methods   []objc.Method
	ClassRefs map[uint64]*objc.Class
	SelRefs   map[uint64]*objc.Selector
	CFStrings []objc.CFString
}
type ObjcClassheaderT struct {
	ClsOffset int32
	HiOffset  int32
}
type ClassHeaderV16T uint64
func (h ClassHeaderV16T) IsDuplicate() bool {
	return types.ExtractBits(uint64(h), 0, 1) == 1
}
func (h ClassHeaderV16T) ObjectCacheOffset() uint64 {
	return types.ExtractBits(uint64(h), 1, 47)
}
func (h ClassHeaderV16T) DylibObjCIndex() uint16 {
	return uint16(types.ExtractBits(uint64(h), 48, 16))
}
type Optimization struct {
	Version                                      uint32
	Flags                                        optFlags
	SelectorOptOffset                            int32
	HeaderOptRoOffset                            int32
	UnusedClassOptOffset                         int32
	UnusedProtocolOptOffset                      int32
	HeaderoptRwOffset                            int32
	UnusedProtocolOpt2Offset                     int32
	LargeSharedCachesClassOffset                 int32
	LargeSharedCachesProtocolOffset              int32
	RelativeMethodSelectorBaseAddressCacheOffset uint64
}
func (o Optimization) isPointerAligned() bool {
	return (binary.Size(o) % 8) == 0
}
func (o Optimization) GetClassOffset() int32 {
	if o.Version >= 16 {
		return o.LargeSharedCachesClassOffset
	} else {
		return o.UnusedClassOptOffset
	}
}
func (o Optimization) GetProtocolOffset() int32 {
	if o.Version >= 16 {
		return o.LargeSharedCachesProtocolOffset
	} else if o.Version >= 15 {
		return o.UnusedProtocolOpt2Offset
	} else {
		return o.UnusedProtocolOptOffset
	}
}
func (o Optimization) String() string {
	return fmt.Sprintf(
		"Version                                      = %d\n"+
			"Flags                                        = %d\n"+
			"SelectorOptOffset                            = %#x\n"+
			"HeaderOptRoOffset                            = %#x\n"+
			"ClassOptOffset                               = %#x\n"+
			"ProtocolOptOffset                            = %#x\n"+
			"HeaderOptRwOffset                            = %#x\n"+
			"ProtocolOpt2Offset                           = %#x\n"+
			"LargeSharedCachesClassOffset                 = %#x\n"+
			"LargeSharedCachesProtocolOffset              = %#x\n"+
			"RelativeMethodSelectorBaseAddressCacheOffset = %#x\n"+
			"isPointerAligned                             = %t\n",
		o.Version,
		o.Flags,
		o.SelectorOptOffset,
		o.HeaderOptRoOffset,
		o.UnusedClassOptOffset,
		o.UnusedProtocolOptOffset,
		o.HeaderoptRwOffset,
		o.UnusedProtocolOpt2Offset,
		o.LargeSharedCachesClassOffset,
		o.LargeSharedCachesProtocolOffset,
		o.RelativeMethodSelectorBaseAddressCacheOffset,
		o.isPointerAligned())
}
func (f *File) getLibObjC() (*macho.File, error) {
	image := f.Image("/usr/lib/libobjc.A.dylib")
	m, err := image.GetPartialMacho()
	if err != nil {
		return nil, err
	}
	return m, nil
}
func (f *File) getOptimizations() (*macho.Section, *Optimization, error) {
	libObjC, err := f.getLibObjC()
	if err != nil {
		return nil, nil, err
	}
	if s := libObjC.Section("__TEXT", "__objc_opt_ro"); s != nil {
		dat, err := s.Data()
		if err != nil {
			return nil, nil, fmt.Errorf("failed to read __TEXT.__objc_opt_ro data")
		}
		opt := Optimization{}
		if err := binary.Read(bytes.NewReader(dat), f.ByteOrder, &opt); err != nil {
			return nil, nil, err
		}
		if opt.Version > 16 {
			return nil, nil, fmt.Errorf("objc optimization version should be 16 or less, but found %d", opt.Version)
		}
		return s, &opt, nil
	}
	return nil, nil, fmt.Errorf("unable to find section __TEXT.__objc_opt_ro in /usr/lib/libobjc.A.dylib")
}
func (f *File) getSelectorStringHash() (*StringHash, error) {
	sec, opt, err := f.getOptimizations()
	if err != nil {
		return nil, err
	}
	if opt.SelectorOptOffset == 0 {
		return nil, fmt.Errorf("selopt_offset is 0")
	}
	shash := StringHash{FileOffset: int64(sec.Offset) + int64(opt.SelectorOptOffset), opt: opt}
	if err = shash.Read(io.NewSectionReader(f.r[f.UUID], 0, 1<<63-1)); err != nil {
		return nil, err
	}
	return &shash, nil
}
func (f *File) getClassStringHash() (*StringHash, error) {
	sec, opt, err := f.getOptimizations()
	if err != nil {
		return nil, err
	}
	if opt.GetClassOffset() == 0 {
		return nil, fmt.Errorf("clsopt_offset is 0")
	}
	shash := StringHash{FileOffset: int64(sec.Offset) + int64(opt.GetClassOffset()), opt: opt}
	if err = shash.Read(io.NewSectionReader(f.r[f.UUID], 0, 1<<63-1)); err != nil {
		return nil, err
	}
	return &shash, nil
}
func (f *File) getProtocolStringHash() (*StringHash, error) {
	sec, opt, err := f.getOptimizations()
	if err != nil {
		return nil, err
	}
	if opt.GetProtocolOffset() == 0 {
		return nil, fmt.Errorf("protocolopt_offset is 0")
	}
	shash := StringHash{FileOffset: int64(sec.Offset) + int64(opt.GetProtocolOffset()), opt: opt}
	if err = shash.Read(io.NewSectionReader(f.r[f.UUID], 0, 1<<63-1)); err != nil {
		return nil, err
	}
	return &shash, nil
}
func (f *File) dumpOffsets(offsets []int32, fileOffset int64) {
	sort.Slice(offsets, func(i, j int) bool { return offsets[i] < offsets[j] })
	sr := io.NewSectionReader(f.r[f.UUID], 0, 1<<63-1)
	for _, ptr := range offsets {
		if ptr != 0 {
			sr.Seek(int64(int32(fileOffset)+ptr), io.SeekStart)
			s, err := bufio.NewReader(sr).ReadString('\x00')
			if err != nil {
				log.Error(errors.Wrapf(err, "failed to read selector name at: %d", int32(fileOffset)+ptr).Error())
			}
			addr, _ := f.GetVMAddressForUUID(f.UUID, uint64(int32(fileOffset)+ptr))
			fmt.Printf("    0x%x: %s\n", addr, strings.Trim(s, "\x00"))
		}
	}
}
type objc_headeropt_ro_t struct {
	Count   uint32
	Entsize uint32
	Headers []header_info
}
type header_info struct {
	MhdrOffset int64
	InfoOffset int64
	NlclslistOffset int64
	NlclslistCount  uint64
	NlcatlistOffset int64
	NlcatlistCount  uint64
	CatlistOffset int64
	CatlistCount  uint64
	Catlist2Offset int64
	Catlist2Count  uint64
}
type objc_headeropt_rw_t struct {
	Count   uint32
	Entsize uint32
	Headers []header_info_rw
}
type header_info_rw struct {
	Ptr uint64
}
func (f *File) offsetsToMap(offsets []int32, fileOffset int64) map[string]uint64 {
	objcMap := make(map[string]uint64)
	sort.Slice(offsets, func(i, j int) bool { return offsets[i] < offsets[j] })
	sr := io.NewSectionReader(f.r[f.UUID], 0, 1<<63-1)
	for _, ptr := range offsets {
		if ptr != 0 {
			sr.Seek(int64(int32(fileOffset)+ptr), io.SeekStart)
			s, err := bufio.NewReader(sr).ReadString('\x00')
			if err != nil {
				log.Error(errors.Wrapf(err, "failed to read selector name at: %d", int32(fileOffset)+ptr).Error())
			}
			addr, _ := f.GetVMAddressForUUID(f.UUID, uint64(int32(fileOffset)+ptr))
			objcMap[strings.Trim(s, "\x00")] = addr
			f.AddressToSymbol[addr] = strings.Trim(s, "\x00")
		}
	}
	return objcMap
}
func (f *File) GetAllSelectors(print bool) (map[string]uint64, error) {
	shash, err := f.getSelectorStringHash()
	if err != nil {
		return nil, errors.Wrapf(err, "failed read selector objc_stringhash_t")
	}
	if print {
		f.dumpOffsets(shash.Offsets, shash.FileOffset)
	}
	return f.offsetsToMap(shash.Offsets, shash.FileOffset), nil
}
func (f *File) GetSelectorAddress(selector string) (uint64, error) {
	shash, err := f.getSelectorStringHash()
	if err != nil {
		return 0, errors.Wrapf(err, "failed get selector objc_stringhash_t for %s", selector)
	}
	selIndex, err := shash.getIndex(selector)
	if err != nil {
		return 0, errors.Wrapf(err, "failed get selector address for %s", selector)
	}
	ptr, err := f.GetVMAddressForUUID(f.UUID, uint64(shash.FileOffset+int64(shash.Offsets[selIndex])))
	if err != nil {
		return 0, fmt.Errorf("failed get selector address for %s: %w", selector, err)
	}
	return ptr, nil
}
func (f *File) GetAllClasses(print bool) (map[string]uint64, error) {
	shash, err := f.getClassStringHash()
	if err != nil {
		return nil, errors.Wrapf(err, "failed read class objc_stringhash_t")
	}
	if print {
		f.dumpOffsets(shash.Offsets, shash.FileOffset)
	}
	return f.offsetsToMap(shash.Offsets, shash.FileOffset), nil
}
func (f *File) GetClassAddress(class string) (uint64, error) {
	shash, err := f.getClassStringHash()
	if err != nil {
		return 0, errors.Wrapf(err, "failed read class objc_stringhash_t for %s", class)
	}
	selIndex, err := shash.getIndex(class)
	if err != nil {
		return 0, errors.Wrapf(err, "failed get class address for %s", class)
	}
	ptr, err := f.GetVMAddressForUUID(f.UUID, uint64(shash.FileOffset+int64(shash.Offsets[selIndex])))
	if err != nil {
		return 0, fmt.Errorf("failed get class address for %s: %w", class, err)
	}
	return ptr, nil
}
func (f *File) GetAllProtocols(print bool) (map[string]uint64, error) {
	shash, err := f.getProtocolStringHash()
	if err != nil {
		return nil, errors.Wrapf(err, "failed read selector objc_stringhash_t")
	}
	if print {
		f.dumpOffsets(shash.Offsets, shash.FileOffset)
	}
	return f.offsetsToMap(shash.Offsets, shash.FileOffset), nil
}
func (f *File) GetProtocolAddress(protocol string) (uint64, error) {
	shash, err := f.getProtocolStringHash()
	if err != nil {
		return 0, errors.Wrapf(err, "failed read selector objc_stringhash_t")
	}
	selIndex, err := shash.getIndex(protocol)
	if err != nil {
		return 0, errors.Wrapf(err, "failed get protocol address for %s", protocol)
	}
	ptr, err := f.GetVMAddressForUUID(f.UUID, uint64(shash.FileOffset+int64(shash.Offsets[selIndex])))
	if err != nil {
		return 0, fmt.Errorf("failed get protocol address for %s: %w", protocol, err)
	}
	return ptr, nil
}