from filetypes.base import *
import malcat
import datetime
import struct
from filetypes.ELF import Region


CPU_ARCH_ABI64 = 0x01000000
CPU_ARCH_ABI64_32 = 0x02000000


CPU_MAP = {
        #0xffffffff: ("Any", [], None),
        1: ("VAX", [], None),
        6: ("MC680x0", [], None),
        7: ("x86", [
                ("All", 3),
                ("Arch1", 4),
            ], malcat.Architecture.X86),
        7 | CPU_ARCH_ABI64: ("x86-64", [
                ("All", 3),
                ("Haswell", 8),
            ], malcat.Architecture.X64),
        10: ("MC98000", [], None),
        11: ("HPPA", [], None),
        12: ("ARM", [], malcat.Architecture.ARMV7),
        12 | CPU_ARCH_ABI64: ("ARM64", [], malcat.Architecture.AARCH64),
        12 | CPU_ARCH_ABI64_32: ("ARM64-32", [], malcat.Architecture.ARMV8),
        13: ("MC88000", [], None),
        14: ("SPARC", [], None),
        15: ("i860", [], None),
        18: ("PowerPC", [], None),
        18 | CPU_ARCH_ABI64: ("PowerPC-64", [], None),
}

class MachOHeader(Struct):

    def parse(self):
        magic = yield Bytes(4, name="Magic")
        cpu = yield self.parser.uint32(name="CpuType", values=[(v[0], k) for k,v in CPU_MAP.items()])
        stenum = CPU_MAP.get(cpu, ("",[],None))[1]
        bf = BitsField(
            NullBits(7),
            Bit(name="LIB64_OR_PTRAUTH"),
            lsb=self.parser.lsb,
            name="CpuFlags")
        st = self.parser.uint24(name="CpuSubType", values=stenum)
        if self.parser.lsb:
            yield st
            yield bf
        else:
            yield bf
            yield st
        yield self.parser.uint32(name="FileType", values=[
            ("MH_EXECUTE", 	0x2),
            ("MH_FVMLIB", 	0x3),
            ("MH_DYLIB", 	0x6),
            ("MH_DYLINKER", 	0x7),
            ("MH_BUNDLE", 	0x8),
            ("MH_OBJECT", 	0x1),
            ("MH_CORE", 		0x4),
            ("MH_PRELOAD", 	0x5),
            ("MH_DYLIB_STUB", 	0x9),
            ("MH_DSYM", 		0xa),
            ("MH_KEXT_BUNDLE", 	0xb),
        ])
        yield self.parser.uint32(name="NumberOfCommands")
        yield self.parser.uint32(name="SizeOfCommands")
        yield BitsField(
                Bit(name="NOUNDEFS", comment="the object file has no undefined references"),
                Bit(name="INCRLINK", comment="the object file is the output of an incremental link against a base file and can't be link edited again"),
                Bit(name="DYLDLINK", comment="the object file is input for the dynamic linker and can't be staticly link edited again"),
                Bit(name="BINDATLOAD", comment="undefined references are bound by the dynamic linker when loaded"),
                Bit(name="PREBOUND", comment="dynamic undefined references prebound"),
                Bit(name="SPLIT_SEGS", comment="read-only and read-write segments split"),
                Bit(name="LAZY_INIT", comment="the shared library init routine is to be run lazily"),
                Bit(name="TWOLEVEL", comment="the image is using two-level name space bindings"),
                Bit(name="FORCE_FLAT", comment="use flat name space bindings"),
                Bit(name="NOMULTIDEFS", comment="no multiple defintions of symbols"),
                Bit(name="NOFIXPREBINDING", comment="do not have dyld notify the prebinding agent about this executable"),
                Bit(name="PREBINDABLE", comment="the binary is not prebound but can have its prebinding redone"),
                Bit(name="ALLMODSBOUND", comment="indicates that this binary binds to all two-level namespace modules of its dependent libraries"),
                Bit(name="SUBSECTIONS_VIA_SYMBOLS", comment="safe to divide up the sections into sub-sections via symbols for dead code stripping"),
                Bit(name="CANONICAL", comment="the binary has been canonicalized via the unprebind operation"),
                Bit(name="WEAK_DEFINES", comment="the final linked image contains external weak symbol"),
                Bit(name="BINDS_TO_WEAK", comment="the final linked image uses weak symbols"),
                Bit(name="ALLOW_STACK_EXECUTION", comment="all stacks in the task will be given stack execution privilege"),
                Bit(name="ROOT_SAFE", comment="is safe for use in processes with uid zero"),
                Bit(name="SETUID_SAFE", comment="safe for use in processes when issetugid() is true"),
                Bit(name="NO_REEXPORTED_DYLIBS", comment="the static linker does not need to examine dependent dylibs to see if any are re-exported"),
                Bit(name="PIE", comment="the OS will load the main executable at a random address"),
                Bit(name="DEAD_STRIPPABLE_DYLIB", comment="the static linker will automatically not create a LC_LOAD_DYLIB load command to the dylib if no symbols are being referenced from the dylib"),
                Bit(name="HAS_TLV_DESCRIPTORS", comment="contains a section of type S_THREAD_LOCAL_VARIABLES"),
                Bit(name="NO_HEAP_EXECUTION", comment="the OS will run the main executable with a non-executable heap even on platforms (e.g. i386) that don't require it"),
                Bit(name="APP_EXTENSION_SAFE", comment="code was linked for use in an application extension"),
                Bit(name="NLIST_OUTOFSYNC_WITH_DYLDINFO", comment="external symbols listed in the nlist symbol table do not include all the symbols listed in the dyld info"),
                Bit(name="SIM_SUPPORT", comment="allow LC_MIN_VERSION_MACOS and LC_BUILD_VERSION load commands"),
                Bit(name="DYLIB_IN_CACHE", comment="dylib is part of the dyld shared cache, rather than loose in the filesystem"),
                lsb=self.parser.lsb,
            name="Flags")
        if self.parser.is64:
            yield Unused(4)
        



#################################################### COMMANDS


class LoadCommand(Struct):

    HDR = [] 
    VM_FLAGS = None

    def parse_header(self):
        # optim
        if not LoadCommand.HDR:
            LoadCommand.HDR = [ 
                self.parser.uint32(name="Type", values=[(v[0], k) for k, v in COMMANDS.items()]),
                self.parser.uint32(name="Size")
            ]
        for f in LoadCommand.HDR:
            yield f

    def parse_vm_flags(self, name="Protection"):
        if LoadCommand.VM_FLAGS is None:
            LoadCommand.VM_FLAGS = BitsField(
                Bit(name="VM_PROT_READ"),
                Bit(name="VM_PROT_WRITE"),
                Bit(name="VM_PROT_EXECUTE"),
                Bit(name="VM_PROT_NO_CHANGE"),
                Bit(name="VM_PROT_COPY", comment="copy on write"),
                Bit(name="VM_PROT_WANTS_COPY", comment="Used only by memory_object_data_request upon an object which has specified a copy_call copy strategy"),
                Bit(name="VM_PROT_TRUSTED", comment="caller wants this memory region treated as if it had a valid code signature"),
                Bit(name="VM_PROT_IS_MASK", comment="other protection bits are to be applied as a mask against the actual protection bits"),
                Bit(name="VM_PROT_STRIP_READ", comment="special marker that tells mprotect to not set VM_PROT_READ. We have to do it this way because existing code expects the system to set VM_PROT_READ if VM_PROT_EXECUTE is set"),
                NullBits(23),
                lsb=self.parser.lsb,
                name="Protection")
        LoadCommand.VM_FLAGS.name = name
        yield LoadCommand.VM_FLAGS

    def parse(self):
        cmdf, sizef = self.parse_header()
        yield cmdf
        size = yield sizef
        if size > len(self):
            yield Unused(size - len(self))


class SegmentSection(Struct):

    def parse(self):
        yield String(16, zero_terminated=True, name="SectionName")
        yield String(16, zero_terminated=True, name="SegmentName")
        yield self.parser.va(name="VirtualAddress")
        yield self.parser.uint(name="VirtualSize")
        yield self.parser.offset32(name="Offset")
        yield UInt32(name="Align")
        yield UInt32(name="RelocationsOffset")
        yield UInt32(name="NumberOfRelocations")
        st = UInt8(name="Type", values=[
            ("S_REGULAR", 0),
            ("S_ZEROFILL", 1),
            ("S_CSTRING_LITERALS", 2),
            ("S_4BYTE_LITERALS", 3),
            ("S_8BYTE_LITERALS", 4),
            ("S_LITERAL_POINTERS", 5),
            ("S_NON_LAZY_SYMBOL_POINTERS", 0x6),
            ("S_LAZY_SYMBOL_POINTERS", 0x7),
            ("S_SYMBOL_STUBS", 0x8),
            ("S_MOD_INIT_FUNC_POINTERS", 0x9),
            ("S_MOD_TERM_FUNC_POINTERS", 0xa),
            ("S_COALESCED", 0xb),
            ("S_GB_ZEROFILL", 0xc),
            ("S_INTERPOSING", 0xd),
            ("S_16BYTE_LITERALS", 0xe),
            ("S_DTRACE_DOF", 0xf),
            ("S_LAZY_DYLIB_SYMBOL_POINTERS", 0x10),
            ("S_THREAD_LOCAL_REGULAR", 0x11),
            ("S_THREAD_LOCAL_ZEROFILL", 0x12),
            ("S_THREAD_LOCAL_VARIABLES", 0x13),
            ("S_THREAD_LOCAL_VARIABLE_POINTERS", 0x14),
            ("S_THREAD_LOCAL_INIT_FUNCTION_POINTERS", 0x15),
            ("S_INIT_FUNC_OFFSETS", 0x16),
        ])
        sf = BitsField(
            Bit(name="S_ATTR_LOC_RELOC", comment="has local relocation entries"),
            Bit(name="S_ATTR_EXT_RELOC", comment="has external relocation entries"),
            Bit(name="S_ATTR_SOME_INSTRUCTIONS", comment="contains some machine instructions"),
            NullBits(14),
            Bit(name="S_ATTR_DEBUG", comment="debug section. If a segment contains any sections marked with S_ATTR_DEBUG then all sections in that segment must have this attribute"),
            Bit(name="S_ATTR_SELF_MODIFYING_CODE", comment="Used with i386 code stubs written on by dyld"),
            Bit(name="S_ATTR_LIVE_SUPPORT", comment="blocks are live if they reference live blocks"),
            Bit(name="S_ATTR_NO_DEAD_STRIP", comment="no dead stripping"),
            Bit(name="S_ATTR_STRIP_STATIC_SYMS", comment="ok to strip static symbols in this section in files with the MH_DYLDLINK flag"),
            Bit(name="S_ATTR_NO_TOC", comment="section contains coalesced symbols that are not to be in a ranlib table of contents"),
            Bit(name="S_ATTR_PURE_INSTRUCTIONS", comment="section contains only true machine instructions"),
            lsb=self.parser.lsb,
            comment="section flags",
            name="Flags")
        if self.parser.lsb:
            sectype = yield st
            yield sf
        else:
            yield sf
            sectype = yield st
        yield self.parser.uint32(name="IndirectTableIndex", comment="for symbol pointer sections and symbol stubs sections that refer to indirect symbol table entries, this is the index into the indirect table for this section’s entries")
        yield self.parser.uint32(name="EntrySize", comment="size of a stub/offset entry")
        if self.parser.is64:
            yield Unused(4)


class SegmentCommand(LoadCommand):

    def parse(self):
        yield from self.parse_header()
        yield String(16, zero_terminated=True, name="Name", comment="segment name")
        yield self.parser.va(name="VirtualAddress")
        yield self.parser.uint(name="VirtualSize")
        yield self.parser.offset(name="FileOffset")
        yield self.parser.uint(name="FileSize")
        yield from self.parse_vm_flags("MaximumProtection")
        yield from self.parse_vm_flags("InitialProtection")
        ns = yield self.parser.uint32(name="NumberOfSections")
        yield BitsField(
                Bit(name="SG_HIGHVM", comment="segment is for the high part of the VM space"),
                Bit(name="SG_FVMLIB", comment="segment is the VM that is allocated by a fixed VM library"),
                Bit(name="SG_NORELOC", comment="segment has nothing that was relocated in it and nothing relocated to it"),
                Bit(name="SG_PROTECTED_VERSION_1", comment="segment is protected"),
                Bit(name="SG_READ_ONLY", comment="segment is made read-only after fixups"),
                NullBits(27),
                lsb=self.parser.lsb,
                name="Flags")
        if ns:
            yield Array(ns, SegmentSection(), name="Sections")



class Version(LoadCommand):

    def parse(self):
        if self.parser.lsb:
            yield UInt8(name="Patch")
            yield UInt8(name="Minor")
            yield UInt16(name="Major")
        else:
            yield UInt16BE(name="Major")
            yield UInt8(name="Minor")
            yield UInt8(name="Patch")

class VersionCommandTool(Struct):

    def parse(self):
        yield self.parser.uint32(name="Tool", values=[
        ("CLANG", 1),
        ("SWIFT", 2),
        ("LD", 3),
        ])
        yield Version(name="Version")



class VersionCommand(LoadCommand):

    def parse(self):
        yield from self.parse_header()
        yield self.parser.uint32(name="Plateform", values=[
        ("PLATFORM_MACOS", 1),
        ("PLATFORM_IOS", 2),
        ("PLATFORM_TVOS", 3),
        ("PLATFORM_WATCHOS", 4),
        ("PLATFORM_BRIDGEOS", 5),
        ("PLATFORM_MACCATALYST", 6),
        ("PLATFORM_IOSSIMULATOR", 7),
        ("PLATFORM_TVOSSIMULATOR", 8),
        ("PLATFORM_WATCHOSSIMULATOR", 9),
        ("PLATFORM_DRIVERKIT", 10),
        ])
        yield Version(name="MinimumOS")
        yield Version(name="SDK")
        n = yield self.parser.uint32(name="NumberOfTools")
        if n:
            yield Array(n, VersionCommandTool(), name="Tools")

class EntryPointCommand(LoadCommand):

    def parse(self):
        yield from self.parse_header()
        yield Offset64(name="EntryPoint", zero_is_invalid=True)
        yield self.parser.uint64(name="StackSize")


class SymbolTableCommand(LoadCommand):

    def parse(self):
        yield from self.parse_header()
        yield self.parser.offset32(name="SymbolTableOffset")
        yield self.parser.uint32(name="NumberOfSymbols")
        yield self.parser.offset32(name="StringTableOffset")
        yield self.parser.uint32(name="StringTableSize")


class SymbolEntry(Struct):

    def parse(self):
        yield self.parser.offset32(name="Name", base=self.parser.string_table_offset, hint=String(0, True))
        yield UInt8(name="Type")
        yield UInt8(name="SectionIndex")
        yield BitsField(
                Bit(name="LAZY"),
                Bit(name="DEFINED"),
                Bit(name="PRIVATE"),
                Bit(name="ARM_THUMB_DEF"),
                Bit(name="REFERENCED_DYNAMICALLY"),
                Bit(name="DESC_DISCARDED "),
                Bit(name="WEAK_REF"),
                Bit(name="WEAK_DEF"),
                Bit(name="SYMBOL_RESOLVER"),
                Bit(name="ALT_ENTRY"),
                NullBits(6),
                lsb=self.parser.lsb,
                name="Desc")
        yield self.parser.uint64(name="Value")


class DynamicSymbolTableCommand(LoadCommand):

    def parse(self):
        yield from self.parse_header()
        yield self.parser.uint32(name="FirstLocalSymbol", comment="index of the first symbol in the group of local symbols")
        yield self.parser.uint32(name="NumberOfLocalSymbol", comment="total number of symbols in the group of local symbols")
        yield self.parser.uint32(name="FirstExternalSymbol", comment="index of the first symbol in the group of defined external symbols")
        yield self.parser.uint32(name="NumberOfExternalSymbol", comment="total number of symbols in the group of defined external symbols")
        yield self.parser.uint32(name="FirstUndefinedSymbol", comment="index of the first symbol in the group of undefined external symbols")
        yield self.parser.uint32(name="NumberOfUndefinedSymbol", comment="total number of symbols in the group of undefined external symbols")
        yield self.parser.offset32(name="TableOfContentOffset", zero_is_invalid=True, comment="byte offset from the start of the file to the table of contents data")
        yield self.parser.uint32(name="TableOfContentSize", comment="number of entries in the table of contents")
        yield self.parser.offset32(name="ModuleTableOffset", zero_is_invalid=True, comment="byte offset from the start of the file to the module table data")
        yield self.parser.uint32(name="ModuleTableSize", comment="number of entries in the module table")
        yield self.parser.offset32(name="ExternalReferenceTableOffset", zero_is_invalid=True, comment="byte offset from the start of the file to the external reference table data")
        yield self.parser.uint32(name="ExternalReferenceTableSize", comment="number of entries in the external reference table")
        yield self.parser.offset32(name="IndirectSymbolTableOffset", zero_is_invalid=True, comment="byte offset from the start of the file to the indirect symbol table data")
        yield self.parser.uint32(name="IndirectSymbolTableSize", comment="number of entries in the indirect symbol table")
        yield self.parser.offset32(name="ExternalRelocationTableOffset", zero_is_invalid=True, comment="byte offset from the start of the file to the external relocation table data")
        yield self.parser.uint32(name="ExternalRelocationTableSize", comment="number of entries in the external relocation table")
        yield self.parser.offset32(name="LocalRelocationTableOffset", zero_is_invalid=True, comment="byte offset from the start of the file to the local relocation table data")
        yield self.parser.uint32(name="LocalRelocationTableSize", comment="number of entries in the external local table")

class ExternalReference(LoadCommand):

    def parse(self):
        if self.parser.lsb:
            yield UInt8(name="SymbolTableIndex")
            yield self.parser.uint24(name="SymbolTableIndex")
        else:
            yield self.parser.uint24(name="SymbolTableIndex")
            yield UInt8(name="SymbolTableIndex")
        

class BuildUuidCommand(LoadCommand):

    def parse(self):
        yield from self.parse_header()
        yield GUID(microsoft_order=False, name="BuildId", comment="UUID for an image or for its corresponding dSYM file")


class DylibCommand(LoadCommand):

    def parse(self):
        yield self.parser.uint32(name="Type", values=[(v[0], k) for k, v in COMMANDS.items()])
        sz = yield self.parser.uint32(name="Size")
        yield self.parser.offset32(base=self.offset, hint=String(0, True), name="Name")
        yield self.parser.timestamp(name="Timestamp")
        yield Version(name="CurrentVersion")
        yield Version(name="CompatiblityVersion")
        if len(self) < sz:
            yield String(sz - len(self), zero_terminated=True, name="LibraryName")


class DylinkerCommand(LoadCommand):

    def parse(self):
        yield self.parser.uint32(name="Type", values=[(v[0], k) for k, v in COMMANDS.items()])
        sz = yield self.parser.uint32(name="Size")
        yield self.parser.offset32(base=self.offset, hint=String(0, True), name="Name")
        if len(self) < sz:
            yield String(sz - len(self), zero_terminated=True, name="LinkerName")            


COMMANDS = {
        0x1: ("SEGMENT", SegmentCommand),
        0x2: ("SYMTAB", SymbolTableCommand),
        0x3: ("SYMSEG", None),
        0x4: ("THREAD", None),
        0x5: ("UNIXTHREAD", None),
        0x6: ("LOADFVMLIB", None),
        0x7: ("IDFVMLIB", None),
        0x8: ("IDENT", None),
        0x9: ("FVMFILE", None),
        0xa: ("PREPAGE", None),
        0xb: ("DYSYMTAB", DynamicSymbolTableCommand),
        0xc: ("LOAD_DYLIB", DylibCommand),
        0xd: ("ID_DYLIB", DylibCommand),
        0xe: ("LOAD_DYLINKER", DylinkerCommand),
        0xf: ("ID_DYLINKER", DylinkerCommand),
        0x10: ("PREBOUND_DYLIB", None),
        0x11: ("ROUTINES", None),
        0x12: ("SUB_FRAMEWORK", None),
        0x13: ("SUB_UMBRELLA", None),
        0x14: ("SUB_CLIENT", None),
        0x15: ("SUB_LIBRARY", None),
        0x16: ("TWOLEVEL_HINTS", None),
        0x17: ("PREBIND_CKSUM", None),
        0x18 | 0x80000000: ("LOAD_WEAK_DYLIB", None),
        0x19: ("SEGMENT_64", SegmentCommand),
        0x1a: ("ROUTINES_64", None),
        0x1b: ("UUID", BuildUuidCommand),
        0x1c | 0x80000000: ("RPATH", None),
        0x1d: ("CODE_SIGNATURE", None),
        0x1e: ("SEGMENT_SPLIT_INFO", None),
        0x1f | 0x80000000: ("REEXPORT_DYLIB", None),
        0x20: ("LAZY_LOAD_DYLIB", None),
        0x21: ("ENCRYPTION_INFO", None),
        0x22: ("DYLD_INFO", None),
        0x22 | 0x80000000: ("DYLD_INFO_ONLY", None),
        0x23 | 0x80000000: ("LOAD_UPWARD_DYLIB", None),
        0x24: ("VERSION_MIN_MACOSX", None),
        0x25: ("VERSION_MIN_IPHONEOS", None),
        0x26: ("FUNCTION_STARTS", None),
        0x27: ("DYLD_ENVIRONMENT", None),
        0x28 | 0x80000000: ("MAIN", EntryPointCommand),
        0x29: ("DATA_IN_CODE", None),
        0x2A: ("SOURCE_VERSION", None),
        0x2B: ("DYLIB_CODE_SIGN_DRS", None),
        0x2C: ("ENCRYPTION_INFO_64", None),
        0x2D: ("LINKER_OPTION", None),
        0x2E: ("LINKER_OPTIMIZATION_HINT", None),
        0x2F: ("VERSION_MIN_TVOS", None),
        0x30: ("VERSION_MIN_WATCHOS", None),
        0x31: ("NOTE", None),
        0x32: ("BUILD_VERSION", VersionCommand),
}





class MachOAnalyzer(FileTypeAnalyzer):
    category = malcat.FileType.PROGRAM
    name = "MACHO"
    regexp = r"(?:\xfe\xed\xfa[\xce\xcf]|[\xce\xcf]\xfa\xed\xfe)"


    def __init__(self):
        FileTypeAnalyzer.__init__(self)
        self.regions = []
        self.is64 = False
        self.string_table_offset = None
        self.macho_segments = []
        self.macho_sections = []
        self.macho_libs = []
        self.symbol_table = []
        self.symbol_sections = []
        self.indirect_symbol_table = []


    def va2off(self, va):
        if not va:
            return None
        for s in self.regions:
            if va >= s.va and va < s.va + s.vsize:
                delta = va - s.va
                if delta < s.fsize:
                    return s.foff + delta
        return None

    def off2va(self, off):
        for s in self.regions:
            if off >= s.foff and off < s.foff + s.fsize:
                delta = off - s.foff
                if delta < s.fsize:
                    return s.va + delta
        return None


    def parse_symbol_table(self, symtab):
        self.string_table_offset = symtab["StringTableOffset"]
        self.jump(self.string_table_offset)
        yield Bytes(symtab["StringTableSize"], name="SymbolsNames", category=Type.DATA)
        nsyms = symtab["NumberOfSymbols"]
        self.jump(symtab["SymbolTableOffset"])
        symbols = yield Array(nsyms, SymbolEntry(), name="Symbols", category=Type.DEBUG)
        for sym in symbols:
            name = sym["Name"]
            if not name:
                continue
            name = self.read_cstring_ascii(self.string_table_offset + name)
            self.symbol_table.append(name)


    def parse_dynamic_symbol_table(self, dyntab):
        toc_offset = dyntab["TableOfContentOffset"] 

        ind_offset = dyntab["IndirectSymbolTableOffset"] 
        ind_size = dyntab["IndirectSymbolTableSize"] 
        if ind_size and ind_offset:
            self.jump(ind_offset)
            self.indirect_symbol_table = yield Array(ind_size, self.uint32(), name="IndirectSymbolTable", category=Type.HEADER)

        ext_offset = dyntab["ExternalRelocationTableOffset"] 
        ext_size = dyntab["ExternalReferenceTableSize"] 
        if ext_size and ext_offset:
            self.jump(ext_offset)
            yield Array(ext_size, ExternalReference(), name="ExternalReferenceTable", category=Type.HEADER)

        
    def parse_segment(self, segment):
        seg_va = segment["VirtualAddress"]
        seg_vsize = segment["VirtualSize"]
        seg_foff = segment["FileOffset"]
        seg_fsize = segment["FileSize"]
        seg_r = segment["MaximumProtection"]["VM_PROT_READ"]
        seg_w = segment["MaximumProtection"]["VM_PROT_WRITE"]
        seg_x = segment["MaximumProtection"]["VM_PROT_EXECUTE"]
        seg_d = segment["MaximumProtection"]["VM_PROT_STRIP_READ"]
        if seg_va == 0 and seg_fsize == 0:
            #pagezero
            self.set_imagebase(seg_vsize)
            return
        if not seg_fsize and not seg_vsize: 
            return
        self.macho_segments.append(segment)
        sections = []
        if "Sections" in segment:
            segment_physical_size_left = seg_fsize
            for section in segment["Sections"]:
                self.macho_sections.append(section)
                r = seg_r 
                w = seg_w
                d = seg_d
                x = seg_x and (section["Flags"]["S_ATTR_PURE_INSTRUCTIONS"] or section["Flags"]["S_ATTR_SOME_INSTRUCTIONS"])
                va = section["VirtualAddress"]
                vsize = section["VirtualSize"]
                off = section["Offset"]
                if off and not section.Type.enum == "S_ZEROFILL":
                    fsize = min(segment_physical_size_left, vsize)
                else:
                    fsize = 0
                segment_physical_size_left -= fsize
                self.regions.append(Region(
                    va, vsize,
                    off, vsize,
                    section["SectionName"],
                    segment, section,
                    ))
                if section["Type"] in (6, 8):
                    self.symbol_sections.append(section)
                self.add_section(section["SectionName"], off, fsize, va, vsize, r=r, w=w, x=x, discardable=d)
        else:
            self.regions.append(Region(
                seg_va, seg_vsize,
                seg_foff, seg_fsize,
                segment["Name"],
                segment, None,
                r=seg_r, 
                w=seg_w, 
                x=seg_x
                ))
            self.add_section(segment["Name"], seg_foff, seg_fsize, seg_va, seg_vsize, r=seg_r, w=seg_w, x=seg_x, discardable=seg_d)

        # todo map segment where no sections


    def parse_symbols(self):
        symbols = []
        for section in self.symbol_sections:
            va = section["VirtualAddress"]
            vsize = section["VirtualSize"]
            is_stub = "STUB" in section.Type.enum
            index_delta = section["IndirectTableIndex"]
            entry_size = section["EntrySize"]
            if not entry_size and "POINTER" in section.Type.enum:
                entry_size = self.is64 and 8 or 4
            if not entry_size:
                raise FatalError(f"section {section['SectionName']} has not entry size set")
            count = vsize // entry_size
            for i, p in enumerate(range(va, va+vsize, entry_size)):
                try:
                    ind1 = i + index_delta
                    if ind1 >= len(self.indirect_symbol_table):
                        raise FatalError(f"Invalid indirect index {ind1:x}")
                    ind2 = self.indirect_symbol_table[ind1]
                    if ind2 >= len(self.symbol_table):
                        raise FatalError(f"Invalid indirect index {ind2:x}")
                    symbol = self.symbol_table[ind2]
                except FatalError as e:
                    print(e)
                    continue


                self.add_symbol(p, symbol, malcat.FileSymbol.IMPORT)





    def parse(self, hint=""):
        magic = self.read(0, 4)
        self.lsb = magic[3] == 0xfe
        if self.lsb:
            self.is64 = magic[0] == 0xcf
        else:
            self.is64 = magic[3] == 0xcf
        if self.lsb:
            self.uint16 = UInt16
            self.uint24 = UInt24
            self.uint32 = UInt32
            self.int32 = Int32
            self.uint64 = UInt64
            self.int64 = Int64
            self.offset32 = Offset32
            self.timestamp = Timestamp
            if not self.is64:
                self.offset = Offset32
                self.va = Va32
                self.fmtptr = "<I"
            else:
                self.offset = Offset64
                self.va = Va64
                self.fmtptr = "<Q"
            self.fmtendian = "<"
        else:
            self.uint16 = UInt16BE
            self.uint24 = UInt24BE
            self.uint32 = UInt32BE
            self.int32 = Int32BE
            self.uint64 = UInt64BE
            self.int64 = Int64BE
            self.offset32 = Offset32BE
            self.timestamp = TimestampBE
            if not self.is64:
                self.offset = Offset32BE
                self.va = Va32BE
                self.fmtptr = ">I"
            else:
                self.offset = Offset64BE
                self.va = Va64BE
                self.fmtptr = ">Q"
            self.fmtendian = ">"
        if self.is64:
            self.uint = self.uint64
            self.int = self.int64
            self.ptrsize = 8
        else:
            self.uint = self.uint32
            self.int = self.int32
            self.ptrsize = 4

        hdr = yield MachOHeader()
        self.set_architecture(CPU_MAP.get(hdr["CpuType"], ("", [], malcat.Architecture.X86))[2])
        self.set_endianness(is_msb = not self.lsb)
        if hdr["NumberOfCommands"] == 0:
            raise FatalError("No command")

        symbol_table = None
        dynamic_symbol_cmd = None
        for i in range(hdr["NumberOfCommands"]):
            cmd_id, = struct.unpack(f"{self.fmtendian}I", self.read(self.tell(), 4))
            clsname, cls = COMMANDS.get(cmd_id, ("Command", None))
            if cls is None:
                cls = LoadCommand
            start = self.tell()
            cmd = yield cls(name=clsname.title().replace("_", ""), parent=hdr)
            cmd_effective_size = self.tell() - start
            if cmd_effective_size < cmd["Size"]:
                self.jump(start + cmd["Size"])
            elif cmd_effective_size > cmd["Size"]:
                raise FatalError(f"Command body too large at #{start}")

            origin = self.tell()
            if cls == SegmentCommand:
                self.parse_segment(cmd)
                self.confirm()
            elif cls == EntryPointCommand:
                ep_offset = cmd["EntryPoint"]
                va = self.off2va(ep_offset)
                if va:
                    self.add_symbol(va, "EntryPoint", malcat.FileSymbol.ENTRY)
            elif cls == SymbolTableCommand:
                yield from self.parse_symbol_table(cmd)
            elif cls == DynamicSymbolTableCommand:
                dynamic_symbol_cmd = cmd
            elif cls == BuildUuidCommand:
                self.add_metadata("BuildID", cmd["BuildId"])
            elif cls == DylinkerCommand and "LinkerName" in cmd:
                self.add_metadata("Linker", cmd["LinkerName"])
            elif cls == DylibCommand:
                self.macho_libs.append(cmd)
            self.jump(origin)
        
        # dynamic symbol table
        try:
            if self.symbols is not None and dynamic_symbol_cmd is not None:
                yield from self.parse_dynamic_symbol_table(dynamic_symbol_cmd)
        except ParsingError as e:
            print(e)
        
        try:
            if self.symbols and self.indirect_symbol_table:
                self.parse_symbols()
        except ParsingError as e:
            print(e)

        self.confirm()


        # golang parsing
        try:
            from filetypes.MACHO_golang import parse_golang
            yield from parse_golang(self)
        except ParsingError: pass
