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



def align(val, what, down=False):
    if not what:
        return val
    if val % what:
        if down:
            val -= val % what
        else:
            val += what - (val % what)
    return val


class Region:

    def __init__(self, va, vsize, foff, fsize, name, segment, section, r=True, w=True, x=True):
        self.va = va
        self.vsize = vsize
        self.foff = foff
        self.fsize = fsize
        self.segment = segment
        self.section = section
        self.name = name
        self.r = r
        self.w = w
        self.x = x

    def __repr__(self):
        return f"{self.name}:[#{self.foff:x}-{self.foff+self.fsize:x}[ --> [0x{self.va:x}-0x{self.va+self.vsize:x}["


class ElfHeader(Struct):

    def parse(self):
        yield Bytes(4, name="Magic")
        cls = yield UInt8(name="Class", values=[
            ("ELFCLASS32", 1),
            ("ELFCLASS64", 2),
            ])
        lsb = yield UInt8(name="DataEncoding", values=[
            ("LSB", 1),
            ("MSB", 2),
            ])
        yield UInt8(name="Version", comment="should be set to EV_CURRENT(1)")
        yield Unused(9, name="Reserved")
        if lsb == 1:
            uint16 = UInt16
            uint32 = UInt32
            if cls == 1:
                offset = Offset32
                va = Va32
            else:
                offset = Offset64
                va = Va64
        else:
            uint16 = UInt16BE
            uint32 = UInt32BE
            if cls == 1:
                offset = Offset32BE
                va = Va32BE
            else:
                offset = Offset64BE
                va = Va64BE
        yield uint16(name="Type", comment="executable type", values=[
            ("NONE",        0),
            ("REL",         1),
            ("EXEC",        2),
            ("DYN",         3),
            ("CORE",        4),
            ])
        yield uint16(name="Machine", comment="required architecture", values=[
            ("NONE",                0),
            ("M32",                 1),
            ("SPARC",               2),
            ("Intel 386",           3),
            ("Motorola 68000",      4),
            ("Motorola 88000",      5),
            ("Intel 486",           6),
            ("Intel 860",           7),
            ("MIPS 1",              8),
            ("Amdahl System/370",   9),
            ("MIPS RS3000",        10),
            ("IBM RS6000",         11),
            ("HP PA-RISC",         15),
            ("Fujitsu VPP500",     17),
            ("Enhanced SPARC", 18),
            ("Intel 80960", 19),
            ("PowerPC", 20),
            ("64-bit PowerPC", 21),
            ("IBM System/390", 22),
            ("NEC V800", 36),
            ("Fujitsu FR20", 37),
            ("TRW RH-32", 38),
            ("Motorola RCE", 39),
            ("ARM", 40),
            ("Digital Alpha", 41),
            ("Hitachi SH", 42),
            ("SPARC Version 9", 43),
            ("Siemens TriCore", 44),
            ("Argonaut RISC Core", 45),
            ("Hitachi H8/300", 46),
            ("Hitachi H8/300H", 47),
            ("Hitachi H8S", 48),
            ("Hitachi H8/500", 49),
            ("Intel IA-64", 50),
            ("Stanford MIPS-X", 51),
            ("Motorola ColdFire", 52),
            ("Motorola M68HC12", 53),
            ("Fujitsu MMA", 54),
            ("Siemens PCP", 55),
            ("Sony nCPU", 56),
            ("Denso NDR1", 57),
            ("Motorola Star*Core", 58),
            ("Toyota ME16", 59),
            ("STMicroelectronics ST100", 60),
            ("TinyJ", 61),
            ("AMD x86-64", 62),
            ("Sony DSP", 63),
            ("PDP-10", 64),
            ("PDP-11", 65),
            ("Siemens FX66", 66),
            ("ST9+", 67),
            ("ST7", 68),
            ("MC68HC16", 69),
            ("MC68HC11", 70),
            ("MC68HC08", 71),
            ("MC68HC05", 72),
            ("Graphics SVx", 73),
            ("ST19", 74),
            ("VAX", 75),
            ("Axis Communications", 76),
            ("Infineon Technologies", 77),
            ("Element 14", 78),
            ("LSI Logic", 79),
            ("Donald Knuth's educational 64-bit processor", 80),
            ("Harvard University machine-independent object files", 81),
            ("SiTera Prism", 82),
            ("Atmel AVR", 83),
            ("Fujitsu FR30", 84),
            ("Mitsubishi D10V", 85),
            ("Mitsubishi D30V", 86),
            ("NEC v850", 87),
            ("Mitsubishi M32R", 88),
            ("Matsushita MN10300", 89),
            ("Matsushita MN10200", 90),
            ("picoJava", 91),
            ("OpenRISC", 92),
            ("Tangent-A5", 93),
            ("Tensilica Xtensa", 94),
            ("Alphamosaic VideoCore", 95),
            ("Thompson Multimedia", 96),
            ("National Semiconductor 32000", 97),
            ("Tenor Network TPC", 98),
            ("Trebia SNP 1000", 99),
            ("ST200", 100),
            ("IP2K", 101),
            ("MAX", 102),
            ("CR", 103),
            ("F2MC16", 104),
            ("MSP430", 105),
            ("SE_C33", 107),
            ("SEP", 108),
            ("ARCA", 109),
            ("UNICORE", 110),
            ("EXCESS", 111),
            ("DXP", 112),
            ("CRX", 114),
            ("XGATE", 115),
            ("C166", 116),
            ("M16C", 117),
            ("DSPIC30F", 118),
            ("CE", 119),
            ("M32C", 120),
            ("TSK3000", 131),
            ("RS08", 132),
            ("SHARC", 133),
            ("ECOG2", 134),
            ("SCORE7", 135),
            ("DSP24", 136),
            ("VIDEOCORE3", 137),
            ("LATTICEMICO32", 138),
            ("SE_C17", 139),
            ("TI_C2000", 141),
            ("TI_C5500", 142),
            ("MMDSP_PLUS", 160),
            ("CYPRESS_M8C", 161),
            ("R32C", 162),
            ("TRIMEDIA", 163),
            ("QDSP6", 164),
            ("8051", 165),
            ("STXP7X", 166),
            ("NDS32", 167),
            ("ECOG1", 168),
            ("ECOG1X", 168),
            ("MAXQ30", 169),
            ("XIMO16", 170),
            ("MANIK", 171),
            ("CRAYNV2", 172),
            ("RX", 173),
            ("METAG", 174),
            ("MCST_ELBRUS", 175),
            ("ECOG16", 176),
            ("CR16", 177),
            ("ETPU", 178),
            ("SLE9X", 179),
            ("L10M", 180),
            ("K10M", 181),
            ("AARCH64", 183),
            ("AVR32", 185),
            ("STM8", 186),
            ("TILE64", 187),
            ("CUDA", 190),
            ("CLOUDSHIELD", 192),
            ("COREA_1ST", 193),
            ("COREA_2ND", 194),
            ("ARC_COMPACT2", 195),
            ("OPEN8", 196),
            ("RL78", 197),
            ("VIDEOCORE5", 198),
            ("78KOR", 199),
            ("56800EX", 200),
            ("BA1", 201),
            ("BA2", 202),
            ("XCORE", 203),
            ("MCHP_PIC", 204),
            ("INTEL205", 205),
            ("INTEL206", 206),
            ("INTEL207", 207),
            ("INTEL208", 208),
            ("INTEL209", 209),
            ("KM32", 210),
            ("KMX32", 211),
            ("KMX16", 212),
            ("KMX8", 213),
            ("KVARC", 214),
            ("CDP", 215),
            ("COGE", 216),
            ("COOL", 217),
            ("NORC", 218),
            ("CSR_KALIMBA", 219),
            ("Z80", 220),
            ("VISIUM", 221),
            ("FT32", 222),
            ("MOXIE", 223),
            ("AMDGPU", 224),
            ("RISCV", 243),
            
            ])
        yield uint32(name="Version", comment="should be 1")
        yield va(name="Entry", comment="program entry point")
        yield offset(name="ProgramHeaderOffset", comment="program header table’s file offset in bytes")
        yield offset(name="SectionHeaderOffset", comment="section header table’s file offset in bytes")
        yield uint32(name="Flags", comment="processor-specific flags associated with the file")
        yield uint16(name="ElfHeaderSize", comment="this header's size") 
        yield uint16(name="ProgramHeaderEntrySize", comment="size in bytes of one entry in the file’s program header table; all entries are the same size") 
        yield uint16(name="ProgramHeaderNum", comment="number of entries in the program header table")
        yield uint16(name="SectionHeaderEntrySize", comment="size in bytes of one entry in the file’s program section table; all entries are the same size") 
        yield uint16(name="SectionHeaderNum", comment="number of entries in the section header table")
        yield uint16(name="SectionHeaderStringIndex", comment="section header table index of the entry associated with the section name string table. If the file has no section name string table, this member holds the value SHN_UNDEF")



class SectionHeader(Struct):

    def parse(self):
        yield self.parser.offset32(name="Name", base=self.parser.strings_base, hint=String(0, zero_terminated=True), comment="index into the section header string table section")
        yield self.parser.uint32(name="Type", values=[
            ("NULL",        0),
            ("PROGBITS",    1),
            ("SYMTAB",      2),
            ("STRTAB",      3),
            ("RELA",        4),
            ("HASH",        5),
            ("DYNAMIC",     6),
            ("NOTE",        7),
            ("NOBITS",      8),
            ("REL",         9),
            ("SHLIB",       10),
            ("DYNSYM",      11),
            ("INIT_ARRAY",  14),
            ("FINI_ARRAY",  15),
            ("PREINIT_ARRAY",  16),
            ("GROUP",       17),
            ("SYMTAB_SHNDX",18),
            ])
        bits = [
                Bit(name="Write", comment="section contains data that should be writable during process execution"),
                Bit(name="Alloc", comment="section occupies memory during process execution"),
                Bit(name="Execute", comment="section contains executable machine instructions"),
                NullBits(1),
                Bit(name="Merged", comment="section may be merged"),
                Bit(name="Strings", comment="section contains strings"),
                Bit(name="InfoLink", comment="Info field is valid"),
                Bit(name="PreserveLinkOrder", comment="requires special ordering for linker"),
                Bit(name="OsSpecific", comment="os-specific processing"),
                Bit(name="GroupMember", comment="section is a member of a section group"),
                Bit(name="Tls", comment="section contains TLS data"),
                Bit(name="Compressed", comment="section contains compressed data"),
            ]
        if self.parser.is64:
            bits.append(NullBits(52))
        else:
            bits.append(NullBits(20))
        yield BitsField(*bits, name="Flags", lsb=self.parser.lsb)
        yield self.parser.va(name="Address", comment="if the section will appear in the memory image of a process, this member gives the address at which the section's first byte should reside")
        yield self.parser.offset(name="Offset", comment="byte offset from the beginning of the file to the first byte in the section. One section type, SHT_NOBITS described below, occupies no space in the file, and this member locates the conceptual placement in the file")
        yield self.parser.uint(name="Size", comment="section's size in bytes")
        yield self.parser.uint32(name="Link", comment="section header table index link, whose interpretation depends on the section type")
        yield self.parser.uint32(name="Info", comment="extra information, whose interpretation depends on the section type")
        yield self.parser.uint(name="Align", comment="section virtual alignment. only 0 and positive integral powers of two are allowed")
        yield self.parser.uint(name="EntrySize", comment="some sections hold a table of fixed-size entries, such as a symbol table. For such a section, this member gives the size in bytes of each entry. The member contains 0 if the section does not hold a table of fixed-size entries")


class ProgramHeader(Struct):

    def parse(self):
        yield self.parser.uint32(name="Type", values=[
            ("UNUSED",      0),
            ("LOAD",        1),
            ("DYNAMIC",     2),
            ("INTERP",      3),
            ("NOTE",        4),
            ("SHLIB",       5),
            ("PHDR",        6),
            ("TLS",         7),
            ("GNU_EH_FRAME",0x6474e550),
            ("GNU_STACK",   0x6474e551),
            ("GNU_UNWIND",  0x6464e550),
            ("TLS",         7),
            ("TLS",         7),
            ])
        flags = BitsField(
                Bit(name="Execute", comment="segment is executable"),
                Bit(name="Write", comment="segment is writable"),
                Bit(name="Read", comment="segment is executable"),
                NullBits(29),
                lsb=self.parser.lsb,
                name="Flags")
        if self.parser.is64:
            yield flags
        yield self.parser.offset(name="Offset", comment="offset from the beginning of the file at which the first byte of the segment resides")
        yield self.parser.va(name="Vaddress", comment="virtual address at which the first byte of the segment resides in memory")
        yield self.parser.va(name="Paddress", comment="on systems for which physical addressing is relevant, this member is reserved for the segment's physical address")
        yield self.parser.uint(name="FileSize", comment="number of bytes in the file image of the segment")
        yield self.parser.uint(name="VirtualSize", comment="number of bytes in the memory image of the segment")
        if not self.parser.is64:
            yield flags
        yield self.parser.uint(name="Align", comment="if the section will appear in the memory image of a process, this member gives the address at which the section's first byte should reside")



class SymbolEntry(Struct):

    def __init__(self, string_base, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.string_base = string_base

    def parse(self):
        yield self.parser.offset32(name="Name", base=self.string_base, hint=String(0, zero_terminated=True), comment="index into the object file's symbol string table")
        yield self.parser.va(name="Value", comment="value of the associated symbol. Depending on the context, this may be an absolute value, an address, and so on")
        yield self.parser.uint(name="Size", comment="many symbols have associated sizes. For example, a data object's size is the number of bytes contained in the object. This member holds 0 if the symbol has no size or an unknown size")
        yield UInt8(name="Info", comment="additional symbol info")
        yield UInt8(name="Other", comment="currently holds 0 and has no defined meaning")
        yield UInt16(name="SectionIndex", comment="holds the relevant section header table index")


class SymbolEntry64(Struct):

    def __init__(self, string_base, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.string_base = string_base

    def parse(self):
        yield self.parser.offset32(name="Name", base=self.string_base, hint=String(0, zero_terminated=True), comment="index into the object file's symbol string table")
        yield UInt8(name="Info", comment="additional symbol info")
        yield UInt8(name="Other", comment="currently holds 0 and has no defined meaning")
        yield UInt16(name="SectionIndex", comment="holds the relevant section header table index")        
        yield self.parser.va(name="Value", comment="value of the associated symbol. Depending on the context, this may be an absolute value, an address, and so on")
        yield self.parser.uint(name="Size", comment="many symbols have associated sizes. For example, a data object's size is the number of bytes contained in the object. This member holds 0 if the symbol has no size or an unknown size")


class Reloc(Struct):

    VALUES_X86 = [
        ("NONE",                0),
        ("386_32",              1),
        ("386_PC32",            2),
        ("386_GOT32",           3),
        ("386_PLT32",           4),
        ("386_COPY",            5),
        ("386_GLOB_DAT",        6),
        ("386_JUMP_SLOT",       7),
        ("386_RELATIVE",        8),
        ("386_GOT_OFF",         9),
        ("386_GOT_PC",         10),
        ("386_32PLT",          11),
        ("386_16",             20),
        ("386_PC16",           21),
        ("386_8",              22),
        ("386_PC8",            23),
        ("386_SIZE32",         38),
    ]

    VALUES_X64 = [
        ("NONE",                  0),
        ("AMD64_64",              1),
        ("AMD64_PC32",            2),
        ("AMD64_GOT32",           3),
        ("AMD64_PLT32",           4),
        ("AMD64_COPY",            5),
        ("AMD64_GLOB_DAT",        6),
        ("AMD64_JUMP_SLOT",       7),
        ("AMD64_RELATIVE",        8),
        ("AMD64_GOTPCREL",        9),
        ("AMD64_32",             10),
        ("AMD64_32S",            11),
        ("AMD64_16",             12),
        ("AMD64_PC16",           13),
        ("AMD64_8",              14),
        ("AMD64_PC8",            15),
        ("AMD64_PC64",           24),
        ("AMD64_GOTOFF64",       25),
        ("AMD64_GOTPC32",        26),
        ("AMD64_SIZE32",         32),
        ("AMD64_SIZE64",         33),
    ]

    def __init__(self, addend, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.addend = addend

    def parse(self):
        yield self.parser.va(name="Address", comment="virtual address of the storage unit affected by the relocation")
        if self.parser.is64:
            if self.parser.lsb:
                yield self.parser.uint32(name="Type", values=Reloc.VALUES_X64)
                yield self.parser.uint32(name="Symbol")
            else:
                yield self.parser.uint32(name="Symbol")
                yield self.parser.uint32(name="Type", values=Reloc.VALUES_X64)
        else:
            if self.parser.lsb:
                yield UInt8(name="Type", values=Reloc.VALUES_X86)
                yield self.parser.uint24(name="Symbol")
            else:
                yield self.parser.uint24(name="Symbol")
                yield UInt8(name="Type", values=Reloc.VALUES_X86)
        if self.addend:
            yield self.parser.int(name="Addend")


class DynEntry(Struct):

    def parse(self):
        yield self.parser.uint(name="Tag", values=[
            ("NULL",        0),
            ("NEEDED",      1),
            ("PLTRELSZ",    2),
            ("PLTGOT",      3),
            ("HASH",        4),
            ("STRTAB",      5),
            ("SYMTAB",      6),
            ("RELA",        7),
            ("RELASZ",      8),
            ("RELAENT",     9),
            ("STRSZ",       10),
            ("SYMENT",      11),
            ("INIT",        12),
            ("FINI",        13),
            ("SONAME",      14),
            ("RPATH",       15),
            ("SYMBOLIC",    16),
            ("REL",         17),
            ("RELSZ",       18),
            ("RELENT",      19),
            ("PLTREL",      20),
            ("DEBUG",       21),
            ("TEXTREL",     22),
            ("JMPREL",      23),
            ("BIND_NOW",    24),
            ("INIT_ARRAY",  25),
            ("FINI_ARRAY",  26),
            ("INIT_ARRAYSZ",27),
            ("FINI_ARRAYSZ",28),
            ("RUN_PATH",    29),
            ("FLAGS",       30),
            ("ENCODING",    32),
            ("MOVETAB",     0x6ffffffe),
            ("VERNEEDNUM",  0x6fffffff),
            ])
        yield self.parser.uint(name="Value")


class ELFAnalyzer(FileTypeAnalyzer):
    category = malcat.FileType.PROGRAM
    name = "ELF"
    regexp = r"\x7fELF[\x01\x02]{2}\x01.{9}(?:[\x01-\x04]\x00|\x00[\x01-\x04])..(?:\x01\x00\x00\x00|\x00\x00\x00\x01)"


    def __init__(self):
        FileTypeAnalyzer.__init__(self)
        self.regions = []
        self.is64 = False


    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 read_string(self, a):
        if a == 0 or a >= self.strings_size:
            return ""
        else:
            return self.read_cstring_ascii(self.strings_base + a, self.strings_size - a)


    def parse_relocs(self):
        for section in self.elf_sections:
            if section["Type"] not in (4,9):
                continue
            addend = section["Type"] == 4
            sname = self.read_string(section["Name"])
            self.jump(section["Offset"])
            ar = yield Array(section["Size"] // section["EntrySize"], Reloc(addend), name="Relocations{}".format(sname), category=Type.FIXUP)
            symbol_section = self.elf_sections[section["Link"]]
            target_section = self.elf_sections[section["Info"]]
            if self.relocatable:
                target_section_va = self.off2va(target_section["Offset"])
            else:
                target_section_va = target_section["Address"]
            symbol_table = self.symbols_tables.get(symbol_section["Offset"], None)
            if symbol_table is None:
                raise FatalError("No symbol table found for {} linked section".format(sname))
            symbol_section_string_base = self.elf_sections[symbol_section["Link"]]["Offset"]
            for rel in ar:
                reltype = rel["Type"]
                if reltype in (7,):
                    if rel["Address"] == 0 or rel["Symbol"] >= symbol_table.count:
                        continue
                    symbol = symbol_table[rel["Symbol"]]
                    symbol_name = self.read_cstring_ascii(symbol["Name"] + symbol_section_string_base)
                    self.add_symbol(rel["Address"], symbol_name, malcat.FileSymbol.IMPORT)
                elif reltype in (1, 2, 4, 0xa, 0x16, 0x17, 0x1e, 0x11b, 0x11a):
                    if rel["Address"] == 0 or rel["Symbol"] >= symbol_table.count:
                        continue
                    symbol = symbol_table[rel["Symbol"]]
                    symbol_name = self.read_cstring_ascii(symbol["Name"] + symbol_section_string_base)
                    section_index = symbol["SectionIndex"]
                    reloc_target = symbol["Value"]
                    if reltype in (0x16, 0x17):
                        address = rel["Address"]
                    else:
                        address = target_section_va + rel["Address"]
                    if reltype in (2, 4):
                        reloc_target += 4
                        off = self.va2off(address)
                        if off is not None:
                            reloc_target += struct.unpack("<i", self.read(off, 4))[0]
                    if "Addend" in rel:
                        reloc_target += rel["Addend"]
                    if self.relocatable and section_index < self.elf_sections.count:
                        symbol_section_target_base = self.elf_sections[section_index]["Offset"]
                        reloc_target += symbol_section_target_base
                    #print(f'#{rel.offset:x} : {address:x} ({rel["Address"]:x}) -> {reloc_target:x} ({symbol_name}) [{reltype:x}]')
                    if reloc_target == 0 and symbol_name:
                        self.add_symbol(address, symbol_name, malcat.FileSymbol.IMPORT)
                    elif reloc_target > 0:
                        self.add_fixup(address, value=reloc_target, size=4)



    def parse_dynamic(self):
         for seg in self.segments:
            if seg["Type"] != 2:
                continue
            if self.is64:
                ssize = 16
            else:
                ssize = 8
            self.jump(seg["Offset"])
            yield Array(seg["FileSize"] // ssize, DynEntry(), name="Dynamic", category=Type.HEADER)
            

    def parse_symbols(self):
        for section in self.elf_sections:
            if section["Type"] not in (11, 2):
                continue
            if section["Link"] == 0 or section["Link"] >= len(self.elf_sections):
                raise ParsingError("Symbol section without link") 
            symbols_strings_section = self.elf_sections[section["Link"]]
            section_name = self.read_string(section["Name"])
            self.jump(section["Offset"])
            if self.is64:
                clas = SymbolEntry64
            else:
                clas = SymbolEntry
            ar = yield Array(section["Size"] // section["EntrySize"], clas(symbols_strings_section["Offset"]), name="Symbols{}".format(section_name), category=Type.DEBUG)
            self.symbols_tables[ar.offset] = ar
            for symbol in ar:
                symtype = {
                    1: malcat.FileSymbol.DATA,
                    2: malcat.FileSymbol.FUNCTION,
                }.get(symbol["Info"] & 0x0F)
                if symtype is not None:
                    symbol_address = symbol["Value"]
                    section_index = symbol["SectionIndex"]
                    if self.relocatable and section_index < self.elf_sections.count:
                        symbol_section = self.elf_sections[section_index]
                        off = self.off2va(symbol_section["Offset"])
                        if off:
                            symbol_address += off
                        else:
                            continue
                            #print(f"Error for symbol #{symbol.offset:x} --> {symbol_section.offset:x}")
                    if symbol["Name"]:
                        symbol_name = self.read_cstring_ascii(symbol["Name"] + symbols_strings_section["Offset"])
                    else:
                        symbol_name = f"symbol_{symbol.offset:x}"
                    if symbol_address != 0:
                        self.add_symbol(symbol_address, symbol_name, symtype)
            self.jump(symbols_strings_section["Offset"])
            yield Bytes(symbols_strings_section["Size"], name="Strings{}".format(section_name), category=Type.DATA)


    def parse_ctors_dtors(self):
        for section in self.elf_sections:
            name = self.read_string(section["Name"])
            self.jump(section["Offset"])
            if name in (".ctors", ".dtors"):
                data = self.read(section["Offset"], section["Size"])
                first = True
                start = None
                stop = None
                for i, element in enumerate(struct.iter_unpack(self.fmtptr, data)):
                    element = element[0]
                    if first and element in (0xffffffff, 0xffffffffffffffff):
                        continue
                    first = False
                    if not element or self.va2off(element) is None:
                        break
                    cur = self.tell() + i * struct.calcsize(self.fmtptr)
                    if start is None:
                        start = cur
                    stop = cur + struct.calcsize(self.fmtptr)
                    self.add_symbol(element, "{}_#{:d}".format(name[1:], i), malcat.FileSymbol.ENTRY)
                if start and start < stop:
                    self.jump(start)
                    yield Array((stop - start) // struct.calcsize(self.fmtptr), self.va(), name=(name==".ctors" and "Constructors" or "Destructors"), category=Type.HEADER)


    def parse_comments(self):
        for section in self.elf_sections:
            name = self.read_string(section["Name"])
            if name in (".comment",):
                try:
                    data = self.read(section["Offset"], section["Size"]).decode("ascii")
                except: break
                self.jump(section["Offset"])
                yield Bytes(section["Size"], name="Comment", category=Type.META)
                key_seen = set()
                meta_seen = set()
                for l in data.replace("\x00", "\n").splitlines():
                    splitted = l.split(":")
                    key = splitted[0].strip()
                    value = ":".join(splitted[1:]).strip()
                    if (key, value) in meta_seen:
                        continue
                    meta_seen.add((key, value))
                    i = 1
                    while key in key_seen:
                        i += 1
                        key = "{}#{:d}".format(splitted[0].strip(), i)
                    if key and value:
                        self.add_metadata(key, value, category="Comments")
                        key_seen.add(key)
                        
    def parse_function_tables(self):
        for section in self.elf_sections:
            if section["Type"] == 0xe:
                prefix = "init"
            elif section["Type"] == 0xf:
                prefix = "fini"
            else:
                continue
            sz = section["Size"]
            if not sz:
                continue
            self.jump(section["Offset"])

            array = yield Array(sz // self.ptrsize, self.va(), name=f"{prefix}_array")
            for i, a in enumerate(array):
                self.add_symbol(a.value, f"{prefix}_array[{i}]", malcat.FileSymbol.ENTRY)




    def parse(self, hint):
        eh = yield ElfHeader(name="ELF", category=Type.HEADER)
        self.is64 = eh["Class"] == 2
        machine = eh.Machine
        flags = eh["Flags"]
        if machine.value in (3, 6):
            self.set_architecture(malcat.Architecture.X86)
        elif machine.value == 62:
            self.set_architecture(malcat.Architecture.X64)
        elif "mips" in machine.enum.lower():
            if self.is64:
                self.set_architecture(malcat.Architecture.MIPS64)
            else:
                self.set_architecture(malcat.Architecture.MIPS32)
        elif machine.value == 40:
            eabi = flags >> 24
            if eabi >= 8:
                self.set_architecture(malcat.Architecture.ARMV8)
            else:
                self.set_architecture(malcat.Architecture.ARMV7)
        elif machine.value == 183:
            self.set_architecture(malcat.Architecture.AARCH64)
        self.set_endianness(is_msb = eh["DataEncoding"] == 2)
        self.relocatable = eh["Type"] == 1
        if eh["DataEncoding"] == 1:
            self.lsb = True
            self.uint16 = UInt16
            self.uint24 = UInt24
            self.uint32 = UInt32
            self.int32 = Int32
            self.uint64 = UInt64
            self.int64 = Int64
            self.offset32 = Offset32
            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.lsb = False
            self.uint16 = UInt16BE
            self.uint24 = UInt24BE
            self.uint32 = UInt32BE
            self.int32 = Int32BE
            self.uint64 = UInt64BE
            self.int64 = Int64BE
            self.offset32 = Offset32BE
            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
        self.strings_base = 0
        self.strings_size = 0
        if eh["Entry"]:
            self.add_symbol(eh["Entry"], "EntryPoint", malcat.FileSymbol.ENTRY)

        self.elf_sections = []
        self.segments = []
        has_sections = False
        has_segments = False
        if eh["SectionHeaderNum"] and eh["SectionHeaderOffset"] and eh["SectionHeaderOffset"] < self.size():
            self.jump(eh["SectionHeaderOffset"])
            # we have to read string table base before parsing sections
            if eh["SectionHeaderStringIndex"] and eh["SectionHeaderStringIndex"] < eh["SectionHeaderNum"]:
                string_section_offset = self.tell() + 64*eh["SectionHeaderStringIndex"]
                if self.is64:
                    string_section_offset = self.tell() + 64 * eh["SectionHeaderStringIndex"] + 24
                    if string_section_offset + 16 <= self.size():
                        self.strings_base, self.strings_size = struct.unpack("{}QQ".format(self.fmtendian), self.read(string_section_offset, 16))
                else:
                    string_section_offset = self.tell() + 40 * eh["SectionHeaderStringIndex"] + 16
                    if string_section_offset + 8 <= self.size():
                        self.strings_base, self.strings_size = struct.unpack("{}II".format(self.fmtendian), self.read(string_section_offset, 8))
                if self.strings_base >= self.size():
                    print("Invalid section strings base")
                    self.strings_base = 0
                    self.strings_size = 0
            try:
                self.elf_sections = yield Array(eh["SectionHeaderNum"], SectionHeader(), name="Sections", category=Type.HEADER)
            except OutOfBoundError:
                print("ELF section outside of file object, not parsing!")
                self.elf_sections = []
            has_sections = True
        if self.strings_size and self.strings_base:
            self.jump(self.strings_base)
            yield Bytes(self.strings_size, name="Strings.sections", category=Type.DATA)

        if eh["ProgramHeaderNum"] and eh["ProgramHeaderOffset"]:
            self.jump(eh["ProgramHeaderOffset"])
            self.segments = yield Array(eh["ProgramHeaderNum"], ProgramHeader(), name="Segments", category=Type.HEADER)
            has_segments = True
        for segment in self.segments:
            if segment["FileSize"] and segment["Offset"] + segment["FileSize"] > align(self.size(), segment["Align"]):
                print("Invalid segment offset, starts after file size")
        if not has_segments and not has_sections:
            raise FatalError("No segment nor section")
        self.confirm()

        # map segment and sections to regions
        last_off = 0
        last_va = 0
        fake_va = 0x10000
        if self.relocatable:
            self.set_imagebase(fake_va)
        while last_off < self.size():
            next_region_off = self.size()
            for r in self.regions:
                if r.foff + r.fsize <= last_off:
                    continue
                next_region_off = r.foff + r.fsize
                if r.foff <= last_off:
                    last_off = next_region_off  # already mapped, skip mapped range
                    continue
            # check which segments and sections map [last_off:next_region_off[
            sections = []
            segments = []
            for section in self.elf_sections:
                if section["Type"] in (0, 8) or section["Size"] == 0:
                    continue
                if section["Offset"] > last_off:
                    next_region_off = min(next_region_off, section["Offset"])
                elif section["Offset"] + section["Size"] > last_off:
                    name = self.read_string(section["Name"])
                    next_region_off = min(next_region_off, section["Offset"] + section["Size"])
                    sections.append((name, section))
            for segi, seg in enumerate(self.segments):
                if seg["Offset"] > last_off:
                    if seg["FileSize"]:
                        next_region_off = min(next_region_off, seg["Offset"])
                elif seg["Offset"] + seg["FileSize"] > last_off:
                    next_region_off = min(next_region_off, seg["Offset"] + seg["FileSize"])
                    segments.append(("segment{:d}".format(segi), seg))
            if sections:
                section = sorted(sections, key=lambda x: x[1]["Size"])[0]
            else:
                section = None
            if segments:
                segment = sorted(segments, key=lambda x: x[1]["FileSize"])[0]
            else:
                segment = None
            if section or segment:
                name = ""
                if section:
                    # sections have name 
                    name = section[0]
                    section = section[1]
                if segment:
                    if not name:
                        name = segment[0]
                    segment = segment[1]
                if segment is not None:
                    r = segment["Flags"]["Read"]
                    w = segment["Flags"]["Write"]
                    x = segment["Flags"]["Execute"]
                    if section is not None:
                        x = x and section["Flags"]["Execute"]
                        w = w and section["Flags"]["Write"]
                    d = False
                    va = segment["Vaddress"] + (last_off - segment["Offset"])
                    vsize = segment["VirtualSize"] - (last_off - segment["Offset"])
                    if section and section["Size"]:
                        vsize = min(vsize, section["Size"])
                else:
                    r = True
                    w = section["Flags"]["Write"]
                    x = section["Flags"]["Execute"]
                    d = not section["Flags"]["Alloc"]
                    if not has_segments:
                        va = fake_va    # last_off
                        vsize = next_region_off - last_off
                        fake_va += vsize
                    else:
                        va = 0
                        vsize = 0
                if not (r or w or x):
                    r = True
                    x = "text" in name
                # just for internal lookup
                self.regions.append(Region(va, vsize, last_off, next_region_off - last_off, name, segment, section, r, w, x))
                # for malcat framework
                self.add_section(name, last_off, next_region_off - last_off, va, vsize, r, w, x, d)
                if name == ".init" and va and vsize:
                    self.add_symbol(va, "_init", malcat.FileSymbol.ENTRY)
                elif name == ".fini" and va and vsize:
                    self.add_symbol(va, "_fini", malcat.FileSymbol.ENTRY)
            last_off = next_region_off
        self.regions = sorted(self.regions, key=lambda x: x.foff)

        #for r in self.regions:
        #    print("{:x} {} #{:x} {} {} {}".format(r.va, r.vsize, r.foff, r.fsize, r.section, r.segment))


        # dynamic
        try:
            yield from self.parse_dynamic()
        except ParsingError as e:
            print(e)

        # symbols
        self.symbols_tables = {}
        try:
            yield from self.parse_symbols()
        except ParsingError as e:
            print(e)

        # relocs
        try:
            yield from self.parse_relocs()
        except ParsingError as e:
            print(e)

        # ctors
        try:
            yield from self.parse_ctors_dtors()
        except ParsingError as e:
            print(e)

        # comments
        try:
            yield from self.parse_comments()
        except ParsingError as e:
            print(e)

        # init_array/fini_array
        try:
            yield from self.parse_function_tables()
        except ParsingError as e:
            print(e)

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